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 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-02 10:13:23 +00:00
parent 15da2d3970
commit 2c8c299fde

View file

@ -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.