feat(db): schema 0003 planning tables #66
No reviewers
Labels
No labels
in-progress
in-review
ready-for-agent
seam-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
patrick/Polaris!66
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/db-schema-planning"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
PH1.2 — Schema 0003: planning tables and typed mapped classes
Closes #10
Second half of row PH1.1 -> PH1.2 in
docs/12-implementation-plan.md(PH1.1 / migration0002merged as #58). Strictly linear Alembic chain — this is the only migration PR open right now.What this adds
Migration
0003(backend/alembic/versions/0003_planning_tables.py) plus matching typed SQLAlchemy mapped classes (backend/src/polaris/db/models/) for every table in docs/08-data-model.md's "Planning" and "Execution and learning" sections:solver_run,plan_version,plan_version_date,assignment,unassigned_job,plan_lock,validation_finding,service_feedback,reliability_score,notification.Highlights (see docs/08-data-model.md for the full spec each of these implements):
assignment: all five documented indexes —(plan_version_id, shift_id, sequence_no),(job_id),(shift_id, status), plus the "Useful indexes" section's(shift_id, planned_start)and(plan_version_id, review).plan_lock:AuditColumns+Auditable(status transitionsactive -> cleared/expired, same shape asjob/shift/site_suitability), with one namedCHECKconstraint per lock type enforcing exactly the required columns for that type (pinneedsjob_id+shift_id+presence_window_id;reject_windowneedsjob_id+site_id+date+start_at+end_at— deliberately notpresence_window_id, per E21's "matched on (job, site, date, overlapping time range), not on window id"; etc. — full reasoning in the module docstring, including the two lock types the narrative docs never spell out,avoid_site_weekdayandnot_on_shift).plan_version_date: composite PK(plan_version_id, date)(doc lists noid) + a partial unique index ondatewhereis_active, for "exactly one active version per date".reliability_score: unique(site_id, vehicle_id, weekday)withNULLS NOT DISTINCT(Postgres 15+) so two "all vehicles, all weekdays" rows for the same site collide.solver_run,plan_version,assignment,plan_version_date,unassigned_job,service_feedback,validation_finding): noupdated_at/deleted_at, noAuditablemixin — matchesingest_record's PH1.1 precedent for the tables hard rule #1 names as append-only.plan_version/assignment/solver_runstatus transitions are audited by thepolaris.domainservice performing them (hard rule #3), not by generic before/after diffing here — there's nopolaris.domainyet for this ticket to wire that into.Verification
alembic upgrade head/downgrade -1/upgrade +1round-trips 0003 cleanly; full chainbase -> head -> basemigrates cleanly (newtest_0003_upgrade_then_downgrade_minus_one_round_tripsintests/integration/db/test_migrations.py, run against a realpostgis/postgis:17-3.4container via testcontainers).tests/integration/db/test_planning_models.pyround-trips the whole PH1.2 aggregate through the ORM and exercises theplan_lockper-type CHECK constraints, theplan_version_datepartial-unique, and thereliability_scoreNULLS NOT DISTINCTunique against real Postgres.mypy --strictandruffclean; full backend suite green (142 passed, 5 skipped — the skips are the pre-existing Keycloak compose-stack smoke tests, unrelated).Seam note (mirroring PH1.1's issue #57 precedent)
Two pre-existing tests outside this ticket's owned paths encoded "not there yet" assumptions that this migration resolves by design, so I fixed them directly rather than leave a regression, and I'm opening a seam-request issue for traceability:
tests/integration/db/test_migrations.py: the PH1.1 test that downgraded-1fromheadto check0002's round-trip now needs to target0002explicitly, sinceheadis0003. Added the0003equivalent alongside it.tests/integration/api/test_metrics_db.py: PH6.6's/metricssolver_rungauges (api/metrics.py) were tested againstsolver_runnot existing (has_table(...)returnsFalse); now that it exists, rewrote that test to cover the empty-table (headers + zero gauge, no per-status line) and populated-table rendering instead.api/metrics.pyitself needed no change — its docstring already anticipated this ("once the migration lands these lines start appearing with no further change here").Neither
db/audit.py,api/main.py, nor any other sealed/owned file was touched.🤖 Generated with Claude Code
https://claude.ai/code/session_01LoNrSy7Reyp7evkfcdHeLX