Outcome of the /grill-with-docs session scoping #1157. - CONTEXT.md: add **Plan Measure** (the persisted selected Option + role-3 attribution + cost); Recommendation stays the candidate. Remove Scenario Phase / Plan Phase / Rolled-over Options — multi-phase is deferred. Reshape Scenario + Plan to single-phase; fix relationships, dialogue, and the "phase" ambiguity note. - ADR-0005: rewritten to Deferred (multi-phase was speculative prospective-client work; single-phase now; future plan_phase back-fill path preserved). Stray phase refs cleaned in ADR-0016 / ADR-0009. - ADR-0017 (new): Plan persistence — reuse the live plan/recommendation tables via SQLModel mirrors + a PlanRepository on the UoW; add recommendation.plan_id, retire the plan_recommendations m2m; flat post-retrofit on plan; idempotent replace; CO2 in tonnes. Unselected alternatives + bills noted as deferred directions. - docs/migrations/recommendation-plan-id.md: the FE-owned Drizzle change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 KiB
recommendation.plan_id — FE-owned migration
Context: #1157 of the Modelling-stage rebuild. The ModellingOrchestrator persists a Plan and its selected Plan Measures (rows of the live recommendation table). To link a measure to its Plan it adds recommendation.plan_id, replacing the plan_recommendations many-to-many join for new writes (the m2m's cascade delete is pathologically slow — see ADR-0017).
The SQLModel mirror is defined in infrastructure/postgres/ so the ephemeral-Postgres tests build it via SQLModel.metadata.create_all. The production migration is FE-owned (Drizzle ORM).
Change
Add one column to the existing recommendation table:
| Column | Type | Notes |
|---|---|---|
plan_id |
bigint, FK → plan.id, ON DELETE CASCADE, indexed |
the Plan this measure belongs to. Nullable during transition (legacy rows predate it); new writes always set it. |
- Index
plan_id— the orchestrator's idempotent replace deletes a Plan and relies on the cascade to remove its measures; reads fetch a Plan's measures byplan_id. ON DELETE CASCADEis what makes "delete the Plan → its measures go too" a single statement, replacing the m2m cleanup.
Transition / sequencing
- Add
plan_id(nullable) — this migration. NewModellingOrchestratorwrites populate it; legacy writers and existing rows are unaffected. - Cut legacy readers off
plan_recommendationsontoplan_id(separate work, not in #1157). - Drop
plan_recommendationsonce no reader remains (separate migration).
Existing live recommendation rows keep plan_id = NULL until/unless re-modelled; they remain reachable via the legacy plan_recommendations join during the transition.
Not changed here
No new columns for contingency (per-measure contingency stays summed into plan.contingency_cost, matching legacy), no phase column (multi-phase deferred, ADR-0005), and the energy/bill columns are populated by a later Bill Derivation slice (ADR-0017).