docs(modelling): handover — plan_recommendations retired + models consolidated

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-03 22:57:08 +00:00
parent 6f0dcc0455
commit a25495d770

View file

@ -1,6 +1,6 @@
# HANDOVER — Modelling stage rebuild
**Branch:** `feature/bill-derivation` (worktree `/workspaces/home/hestia-worktrees/model-assemble-new-backend`). **HEAD:** `b976c3ab`.
**Branch:** `feature/bill-derivation` (worktree `/workspaces/home/hestia-worktrees/model-assemble-new-backend`). **HEAD:** `6f0dcc04`.
**PRD:** GitHub `Hestia-Homes/Model#1152`, sliced into #1153#1161. **All slices #1153#1161 closed.**
## Issue status
@ -111,6 +111,22 @@ Filled `recommendation.kwh_savings` + `energy_cost_savings` via the **telescopin
Key property: `MeasureImpact.energy_savings_kwh_per_yr` is *primary* energy and does **not** feed `kwh_savings``kwh_savings` is **delivered** energy from the Bill section kWh. Carries ADR-0014's appliances+cooking-stubbed-at-0 limitation.
## Retire `plan_recommendations` + consolidate models (`b76d0f81``6f0dcc04`) — DONE
Designed in `/grill-with-docs` + `/grill-me`. The live `plan`/`recommendation` tables are read **directly by the Drizzle FE**, so this was a two-repo expand/contract. **FE-visibility goal met:** Plans and their measures now link solely by `recommendation.plan_id`; the m2m is gone. 9 slices, all green + pyright-strict-clean, and the rebuild + legacy suites are now **co-runnable** (the consolidation fixed a pre-existing dual-definition collision).
- **`b76d0f81`** — migration spec ([docs/migrations/recommendation-plan-id.md](migrations/recommendation-plan-id.md): add `plan_id` → backfill → dual-write → cut reads → drop; backfill-before-reads + dual-write are the load-bearing rules since the FE can't deploy atomically) + ADR-0017 amendment.
- **`c1c7b06f`** — consolidate `plan`/`recommendation`/`recommendation_materials` into **`infrastructure/postgres/modelling/`** as single SQLModel defs (absorbing the partial `PlanRow`/`RecommendationRow` mirrors, full column parity + `plan_id`). `backend/app/db/models/recommendations.py` → re-export shim. Export conftest: create SQLModel-first / skip the redundant `drop_all` (the `epc` enum type is now shared across both metadatas).
- **`27fcc5b1`** — legacy writers set `recommendation.plan_id` (dual-write).
- **`af5dbe32`** — cut all three readers (`portfolio_functions`, `Outputs`, `export/property_scenarios`) onto `plan_id`.
- **`b97d0688`** — drop the m2m: writes, `delete_property_batch` cleanup, the `PlanRecommendationRow` model, the `test_export` fixtures.
- **`01c2c391`** — rename the cluster `…Row`**`…Model`** (matches the `epc_property` precedent + the legacy names `backend/` already imports, so the shim's plan re-export is literal). The non-cluster `…Row` tables stay until their live legacy `…Model` counterparts retire (renaming now would re-create dual-definition collisions).
- **`2fbd7147`** — move `PortfolioGoal` to **`domain/modelling/portfolio_goal.py`** (domain vocab; infra→domain is the normal direction); `portfolio.py` keeps a re-export.
- **`c18968ba`** — consolidate `scenario` + `installed_measure` (full-parity `ScenarioModel`/`InstalledMeasureModel` + `MeasureType`). **`ScenarioModel.goal` is the `PortfolioGoal` enum** (legacy planning branches on it); the repo's `to_domain` maps it to its value, so `Scenario.goal` is now the value `"Increasing EPC"` consistent with the orchestrator — fixing the latent name-vs-value bug the old `str` column masked.
- **`6f0dcc04`** — characterization test for the FE aggregation `aggregate_portfolio_recommendations` (was untested), pinning the `plan_id` join.
**Gotchas for the next agent:** the modelling SQLModel classes are `…Model` and live in `infrastructure/postgres/modelling/` (NOT the old flat `plan_table.py`/`scenario_table.py` — deleted); `backend/app/db/models/recommendations.py` is now a pure shim. Out-of-cluster columns are plain ints (no FK) per the mirror convention. **`PortfolioGoal` lives in `domain/modelling/`** now. The `etl/`+`sfr/` reporting scripts still reference the m2m and are **deferred** (out of scope). The live DB changes (add `plan_id`, backfill, drop `plan_recommendations`) are the **FE-owned Drizzle** migrations in the migration doc — this branch is the backend end-state.
## What's left
**Deferred fronts** (open, post-#1161): exclusion-filtering of the candidate pool (deferred from #1160); persist **unselected alternatives** (`default=False` rows linked via `plan_id`) for the swap-in UX — open ADR-0016 question: what impact figure they carry; promote `ProductRepository` to the DB+file composite; non-EPC goal objectives (Energy Savings, Reducing CO2) in the optimiser. Possible extension of the ventilation trigger set to roof insulation (now a one-line data edit in `MEASURES_NEEDING_VENTILATION`); and making the dependency builder lazy (thunk) so the Product is only fetched when a trigger is actually selected.