From a25495d770d2d145dc91d28f5a0e048b68e3b3a1 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 3 Jun 2026 22:57:08 +0000 Subject: [PATCH] =?UTF-8?q?docs(modelling):=20handover=20=E2=80=94=20plan?= =?UTF-8?q?=5Frecommendations=20retired=20+=20models=20consolidated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/HANDOVER_MODELLING.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/HANDOVER_MODELLING.md b/docs/HANDOVER_MODELLING.md index f82af3eb..776de693 100644 --- a/docs/HANDOVER_MODELLING.md +++ b/docs/HANDOVER_MODELLING.md @@ -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.