From 2c8c299fde6c6ea142f365fc7ff2b33404c98d32 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 2 Jun 2026 10:13:23 +0000 Subject: [PATCH] docs(migration): add the Bill Derivation block to the property_baseline table (ADR-0014) Slice 5b: update the FE-owned migration spec so the other repo can create the bill columns in parallel. - Bill block: per-section delivered kWh + cost (heating, hot water, lighting, appliances, cooking, pumps/fans, cooling) + standing_charges_gbp, seg_credit_gbp, total_annual_bill_gbp, fuel_rates_period. - space_heating_kwh / water_heating_kwh (RHI recorded demand) marked SUPERSEDED by heating_kwh / hot_water_kwh (calculator delivered fuel); kept until the bill populates, then dropped. - Cooling section kept (mostly 0 but affects the bill, cheap to store). - Records the calculator-load-bearing posture (effective_* may differ from lodged_* for pre-10.2) and that columns are defined now / populated when the SapResult->EnergyBreakdown adapter + BillDerivation wiring land. Co-Authored-By: Claude Opus 4.8 --- .../property-baseline-performance-table.md | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/docs/migrations/property-baseline-performance-table.md b/docs/migrations/property-baseline-performance-table.md index 33e2171a..d4846843 100644 --- a/docs/migrations/property-baseline-performance-table.md +++ b/docs/migrations/property-baseline-performance-table.md @@ -27,17 +27,45 @@ straight lift-and-shift of the columns below. | `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 | off `renewable_heat_incentive`; deterministic (ADR-0006) | -| `water_heating_kwh` | float | off `renewable_heat_incentive` | +| `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`. | -This slice has no ML rebaselining, so `effective_* == lodged_*` and `rebaseline_reason = 'none'` -for every row written (a pre-SAP10 cert raises rather than persisting a wrong-but-plausible row — -see #1135). The `effective_*` columns exist now so the table shape is stable when ML lands. +### Bill block (ADR-0014) — the energy bill, composed per section -## Deferred (follow-up — EPC Energy Derivation + Fuel Rates) +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. -`fuel_split` and `bills` are **not** in this table yet. They are produced by -`EpcEnergyDerivationService`, which needs a current **Fuel Rates** source (Ofgem-cap ETL) that does -not exist yet. They land together in the follow-up so this table is not migrated twice. Likely -shape: a `bills`-style block (per-fuel kWh + standing charge + SEG) — to be specified in that -slice's migration note. +| Column | Type | Notes | +|---|---|---| +| `fuel_rates_period` | text | which Fuel Rates snapshot priced this bill (e.g. `"2026-04 to 2026-06"`) — provenance | +| `heating_kwh` | float | delivered fuel kWh (main + secondary heating) | +| `heating_cost_gbp` | float | priced at the heating fuel's current rate | +| `hot_water_kwh` | float | | +| `hot_water_cost_gbp` | float | | +| `lighting_kwh` | float | | +| `lighting_cost_gbp` | float | | +| `appliances_kwh` | float | unregulated load — **0 until the appliances/cooking fields land on `SapResult`** (ADR-0014 TODO) | +| `appliances_cost_gbp` | float | | +| `cooking_kwh` | float | unregulated load — 0 until `SapResult` carries it | +| `cooking_cost_gbp` | float | | +| `pumps_fans_kwh` | float | | +| `pumps_fans_cost_gbp` | float | | +| `cooling_kwh` | float | mostly 0 in UK homes; carried for completeness as it affects the bill | +| `cooling_cost_gbp` | float | | +| `standing_charges_gbp` | float | daily standing charge × 365, once per distinct metered fuel (off-gas fuels have none) | +| `seg_credit_gbp` | float | SEG export credit on PV (subtracted) | +| `total_annual_bill_gbp` | float | Σ section costs + standing charges − SEG | + +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 **defined now so the FE can create them**, but are populated only once the +`SapResult` → `EnergyBreakdown` adapter + `BillDerivation` wiring land (gated on the appliances / +cooking `SapResult` fields). Until then the SQLModel mirror in `infrastructure/postgres/` adds these +columns as nullable; the Drizzle migration can create them nullable in parallel.