mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
The PropertyBaselineOrchestrator now reads the current Fuel Rates snapshot once per batch, builds a BillDerivation, and prices each scored property's SapResult -> EnergyBreakdown into a Bill carried on PropertyBaselinePerformance (None only on the stub no-calculator path). The Bill is flattened onto nullable bill_* flat columns (per-section kwh+cost, standing charges, SEG credit, total) on the postgres table, with bill_total_annual_bill_gbp as the not-null discriminator on read-back. Section absent from the bill stays None, not 0. Updated all four orchestrator construction sites to inject the FuelRatesRepository port (handler + three test sites), and the FE migration doc to reflect the prefixed columns and that they are now populated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
78 lines
5.2 KiB
Markdown
78 lines
5.2 KiB
Markdown
# `property_baseline_performance` table — FE-owned migration
|
||
|
||
**Context:** Slice 6 (Hestia-Homes/Model#1135) of the `ara_first_run` rebuild. The
|
||
`PropertyBaselineOrchestrator` establishes a Property's **Baseline Performance** (ADR-0004) and persists it
|
||
via a new `PropertyBaselineRepository` port. This is a brand-new table — no predecessor.
|
||
|
||
Per ADR-0004's amendment, the lodged/effective pair does **not** land on `property_details_epc`
|
||
(which is being retired as too coupled to the legacy EPC-API schema). It lands here, as its own
|
||
aggregate's table.
|
||
|
||
The SQLModel row 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)** — a
|
||
straight lift-and-shift of the columns below.
|
||
|
||
## `property_baseline_performance` — one row per Property
|
||
|
||
| Column | Type | Notes |
|
||
|---|---|---|
|
||
| `id` | serial PK | |
|
||
| `property_id` | int, FK → `property.id`, **unique** | one Baseline Performance per Property |
|
||
| `lodged_sap_score` | int | Lodged Performance — gov register, off the Effective EPC |
|
||
| `lodged_epc_band` | text | the `Epc` enum, stored as its string value (e.g. `"C"`) |
|
||
| `lodged_co2_emissions_t_per_yr` | float | tonnes CO₂/yr (whole dwelling) |
|
||
| `lodged_primary_energy_intensity_kwh_per_m2_yr` | int | PEUI (kWh/m²/yr); **not** "heat demand" — see CONTEXT.md |
|
||
| `effective_sap_score` | int | Effective Performance — what modelling scored against |
|
||
| `effective_epc_band` | text | |
|
||
| `effective_co2_emissions_t_per_yr` | float | tonnes CO₂/yr (whole dwelling) |
|
||
| `effective_primary_energy_intensity_kwh_per_m2_yr` | int | kWh/m²/yr |
|
||
| `rebaseline_reason` | text | `none` \| `pre_sap10` \| `physical_state_changed` \| `both` |
|
||
| `space_heating_kwh` | float | EPC `renewable_heat_incentive` recorded demand. **Superseded** by `heating_kwh` (delivered) when the bill block populates; kept until then to avoid an empty-kWh gap, dropped in the population slice. |
|
||
| `water_heating_kwh` | float | EPC `renewable_heat_incentive`; **superseded** by `hot_water_kwh`. |
|
||
|
||
### Bill block (ADR-0014) — the energy bill, composed per section
|
||
|
||
Produced by **Bill Derivation**: the calculator's **delivered** kWh per end use priced at current
|
||
**Fuel Rates** (a committed snapshot, not SAP's standardised prices), per section + the total.
|
||
Per-section kWh is *delivered fuel* (demand ÷ efficiency — what the household pays for), distinct
|
||
from the recorded-demand `space_heating_kwh`/`water_heating_kwh` above which it supersedes.
|
||
|
||
All columns below are **nullable** (every one is `Optional[float]`, default `None`) and **FE-owned
|
||
(Drizzle)**. The `bill_` prefix is deliberate: it keeps the per-section columns from clashing with
|
||
the recorded-demand `space_heating_kwh` / `water_heating_kwh` above. The whole block is `None` for
|
||
one row together when no calculator ran (the stub path produced no `SapResult` to price); a section
|
||
absent from the bill leaves its two columns `None` (not `0` — it was not billed). `to_domain` uses
|
||
`bill_total_annual_bill_gbp IS NOT NULL` as the discriminator for "a bill was persisted".
|
||
|
||
| Column | Type | Notes |
|
||
|---|---|---|
|
||
| `bill_heating_kwh` | float, nullable | delivered fuel kWh (main + main-2 + secondary heating) |
|
||
| `bill_heating_cost_gbp` | float, nullable | priced at the heating fuel's current rate |
|
||
| `bill_hot_water_kwh` | float, nullable | |
|
||
| `bill_hot_water_cost_gbp` | float, nullable | |
|
||
| `bill_lighting_kwh` | float, nullable | |
|
||
| `bill_lighting_cost_gbp` | float, nullable | |
|
||
| `bill_appliances_kwh` | float, nullable | unregulated load — `None` until the appliances field lands on `SapResult` |
|
||
| `bill_appliances_cost_gbp` | float, nullable | |
|
||
| `bill_cooking_kwh` | float, nullable | unregulated load — `None` until `SapResult` carries it |
|
||
| `bill_cooking_cost_gbp` | float, nullable | |
|
||
| `bill_pumps_fans_kwh` | float, nullable | |
|
||
| `bill_pumps_fans_cost_gbp` | float, nullable | |
|
||
| `bill_cooling_kwh` | float, nullable | mostly absent in UK homes; carried for completeness as it affects the bill |
|
||
| `bill_cooling_cost_gbp` | float, nullable | |
|
||
| `bill_standing_charges_gbp` | float, nullable | daily standing charge × 365, once per distinct metered fuel (off-gas fuels have none) |
|
||
| `bill_seg_credit_gbp` | float, nullable | SEG export credit on PV (subtracted) |
|
||
| `bill_total_annual_bill_gbp` | float, nullable | Σ section costs + standing charges − SEG; the not-null discriminator for a persisted bill |
|
||
|
||
The calculator is **load-bearing** (ADR-0013 amendment): for `sap_version < 10.2` the `effective_*`
|
||
columns hold the calculator's output (so `effective_* != lodged_*` legitimately); at/above 10.2 they
|
||
mirror the lodged figures and divergence is logged. A cert the calculator cannot score aborts the
|
||
batch rather than persisting a wrong row.
|
||
|
||
### Population timing
|
||
|
||
The bill columns are now **populated**: the `PropertyBaselineOrchestrator` reads the current Fuel
|
||
Rates snapshot, builds a `BillDerivation`, and prices every scored property's `SapResult` →
|
||
`EnergyBreakdown` into a `Bill` that `from_domain` flattens onto these columns. They stay `None`
|
||
together only on the stub (no-calculator) path. The appliances / cooking sections remain `None`
|
||
until those fields land on `SapResult`. The Drizzle migration creates all `bill_*` columns nullable.
|