Commit graph

7 commits

Author SHA1 Message Date
Jun-te Kim
5737923622 32 and delete in plan 2026-06-23 15:01:34 +00:00
Khalim Conn-Kowlessar
9ef97be958 refactor(modelling): type measure_type fields as MeasureType
Tighten the recommendation/plan vocabulary off generic str:
MeasureOption.measure_type and PlanMeasure.measure_type are now MeasureType
(also _GlazingTarget.measure_type, MeasureDependency.triggers ->
frozenset[MeasureType], and the optimiser's chosen/required-type locals).
Because MeasureType is a StrEnum the change is transparent to persistence
(the `recommendation` varchar column), the optimiser group-by key, and every
`== "solar_pv"`-style comparison — so pyright now enforces the enum at every
construction site with no runtime behaviour change.

The catalogue boundary stays str: ProductRepository.get(measure_type: str)
and Product.measure_type are unchanged (they map arbitrary DB/JSON rows), so
the fake product repos in tests need no edit. Test construction helpers coerce
their str arg via MeasureType(...); direct constructions use members.

Suite green: tests/domain/modelling + orchestration + harness 253 pass + 3
xfail; pyright clean on production + tests (pre-existing moto + property-
override-rowcount baselines untouched).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 20:13:31 +00:00
Khalim Conn-Kowlessar
01c2c3910e refactor(modelling): rename the cluster SQLModel classes …Row → …Model
Standardise the modelling persistence classes on the …Model suffix (PlanModel,
RecommendationModel, RecommendationMaterialModel) — matching the epc_property
precedent and the legacy names the rest of backend/ already imports, so the
shim's plan re-export becomes literal (no alias) and the eventual shim deletion
needs zero renames. The …Row→…Model sweep for the non-cluster tables
(Property/Task/Material/…) waits until their live legacy …Model counterparts
are retired, to avoid reintroducing dual-definition collisions. No behaviour
change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 22:42:21 +00:00
Khalim Conn-Kowlessar
c1c7b06f09 refactor(modelling): consolidate plan/recommendation models into infrastructure
Move the live plan, recommendation, recommendation_materials and (retiring)
plan_recommendations tables into a new infrastructure/postgres/modelling/
subpackage as single SQLModel definitions (the epc_property pattern), absorbing
the rebuild's partial PlanRow/RecommendationRow mirrors and carrying full
legacy column parity plus recommendation.plan_id. Out-of-cluster references are
plain indexed ints (mirror convention); the live FKs are owned by the Drizzle
schema. backend/app/db/models/recommendations.py becomes a re-export shim
(ScenarioModel/InstalledMeasure stay for a later slice).

Fix the export conftest to create SQLModel-first (so Base funding_package's FK
to the now-SQLModel plan resolves) and skip the redundant drop_all on its
function-scoped throwaway DB (the epc enum type is now shared across both
metadatas). Resolves the pre-existing dual-definition collision: the rebuild
and legacy export suites are now co-runnable. No behaviour change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:00:14 +00:00
Khalim Conn-Kowlessar
7e79c30af1 feat(modelling): Plan Measure carries per-measure kwh/cost savings
`PlanMeasure` grows optional `kwh_savings` (delivered energy) and
`energy_cost_savings` (£) — its slice of the telescoping bill cascade, signed
so positive is a saving and `None` until billing runs. `RecommendationRow`
declares the matching live `recommendation.kwh_savings` /
`energy_cost_savings` columns and maps them in `from_domain` (None → NULL).
The vestigial `recommendation.energy_savings` stays undeclared (legacy = 0).
No FE migration — the columns already exist on the live table (ADR-0014 / 0017).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:58:06 +00:00
Khalim Conn-Kowlessar
84ec6da032 refactor(modelling): group domain/modelling into generators/scoring/optimisation
domain/modelling/ had grown to 15 flat modules. Group the behavioural ones into
subpackages — generators/ (wall/roof/floor Recommendation Generators), scoring/
(overlay applicator, package scorer, role-1/3 scoring), optimisation/ (optimiser
+ measure dependency) — and leave the shared value-object vocabulary
(recommendation, plan, scenario, product, contingencies, simulation) flat at the
top, since it is imported everywhere. Pure move + import-path rewrite across 89
import sites; no behaviour change. 136 pass, pyright strict clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:48:36 +00:00
Khalim Conn-Kowlessar
d66f7eed84 feat(modelling): plan/recommendation SQLModel mirrors + PlanRepository (#1157)
Slice 3 of #1157. Persists a Plan and its Plan Measures to the live
plan / recommendation tables via SQLModel mirrors (ADR-0017).

- infrastructure/postgres/plan_table.py: PlanRow (`plan`) + RecommendationRow
  (`recommendation`) mirrors. RecommendationRow adds the new `plan_id` FK
  (ON DELETE CASCADE) linking each Plan Measure to its Plan, replacing the
  plan_recommendations m2m for new writes. from_domain mappers convert CO2
  kg → tonnes to match the live column contract and derive post_epc_rating
  from the rounded SAP. Only the impact + cost + identity columns the tracer
  fills are declared; energy/bill, U-value, valuation, labour, plan_type are
  left to later slices.
- PlanRepository port + PlanPostgresRepository.save(plan, *, property_id,
  scenario_id, portfolio_id, is_default) -> plan id. Idempotent replace:
  deleting the Plan cascades to its recommendation rows via plan_id, so a
  re-run overwrites (ADR-0012). No commit — the UoW owns the transaction.

2 tests (persist + idempotent re-run); pyright strict clean; 73 pass across
repositories/modelling/orchestration with no regressions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 11:51:02 +00:00