From 09b3bc98ba8a4e1f304320ef5665264b6395dae1 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 30 Jun 2026 22:19:54 +0000 Subject: [PATCH] Document the nullable lodged_* migration for the FE/Drizzle owner Specifies the ALTER ... DROP NOT NULL on the four lodged_* columns (#1361) and the ordering constraint: it must land before the backend writes NULL or runs the backfill, else the predicted-Property INSERT aborts the batch. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../property-baseline-performance-table.md | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/migrations/property-baseline-performance-table.md b/docs/migrations/property-baseline-performance-table.md index 0aebba832..efd1ec10b 100644 --- a/docs/migrations/property-baseline-performance-table.md +++ b/docs/migrations/property-baseline-performance-table.md @@ -18,10 +18,10 @@ straight lift-and-shift of the columns below. |---|---|---| | `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 | +| `lodged_sap_score` | int, **nullable** | Lodged Performance — gov register, off the Effective EPC. **NULL for a predicted Property** (no lodged cert — #1361, see below) | +| `lodged_epc_band` | text, **nullable** | the `Epc` enum, stored as its string value (e.g. `"C"`) | +| `lodged_co2_emissions_t_per_yr` | float, **nullable** | tonnes CO₂/yr (whole dwelling) | +| `lodged_primary_energy_intensity_kwh_per_m2_yr` | int, **nullable** | 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) | @@ -30,6 +30,21 @@ straight lift-and-shift of the columns below. | `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`. | +### Lodged half is nullable (#1361 Class B, 2026-06-30) + +The four `lodged_*` columns are **nullable**. A **predicted** Property (EPC Prediction, ADR-0029/0031: +no lodged cert, its `EpcPropertyData` synthesised from neighbours) has **no Lodged Performance** — the +backend now writes `lodged_* = NULL` for it and persists only the Effective half (ADR-0004 amendment). +Reading the synthesised EPC's recorded fields as a lodged figure had been manufacturing a phantom (a +neighbour's SAP), which the migration + a one-time backfill remove. + +**FE-owned Drizzle migration required:** `ALTER TABLE property_baseline_performance ALTER COLUMN +lodged_sap_score DROP NOT NULL` (and the same for `lodged_epc_band`, `lodged_co2_emissions_t_per_yr`, +`lodged_primary_energy_intensity_kwh_per_m2_yr`). This **must land before** the backend deploys the +orchestrator change or runs `scripts/null_predicted_lodged_performance.py` — a `NULL` write against a +`NOT NULL` column aborts the batch (ADR-0012). The backfill NULLs the four columns on the ~12,236 +existing predicted-source rows; Effective, the bill block, and `rebaseline_reason` are left intact. + ### 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