The `from_rdsap_schema_17_0` path hardcoded `sap_heating.secondary_heating_type`
and `secondary_fuel_type` to None, unlike every other API path (19.0/20.0/…),
which read them from `schema.sap_heating`. The gov EPC API *does* populate a
secondary heating SAP code for a genuine fixed secondary (e.g. 691 "electric
room heaters", 694), so the hardcode silently discarded it.
Because `recommend_secondary_heating_removal` gates purely on
`sap_heating.secondary_heating_type`, any 17.0 cert carrying a fixed secondary
would never get its removal recommendation. Surfaced while auditing portfolio
814's recommendations; 814 has 17.0 certs but none with a fixed secondary, so
the defect was latent there — this restores parity with the other paths so it
cannot bite.
Mirrors the 19.0 path exactly (secondary_fuel_type via `_api_secondary_fuel_type`).
Adds a regression guard that injects a fixed secondary into a real 17.0 cert and
asserts the code survives the mapping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One-time script (dry-run default, --apply in a transaction, idempotent) mapping a
flat roof's stated depth (flat: Nmm) onto the nearest Table 16 rung member so the
overlay scores it by thickness, not the age-band default. Updates property_overrides
(TEXT) immediately; the roof_type pgEnum cache writes are deferred until the FE adds
the FLAT_*MM members. Dry-run against the audited DB reports 102 rows, deferring
Flat 50/100/150 mm.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One-time script (dry-run default, --apply in a transaction, idempotent) that maps
every party-ceiling override (another/same dwelling or premises above) currently
stored as an external roof value onto the party-ceiling member, reusing the live
roof_party_ceiling_guard so the backfill and the classifier cannot drift. Updates
property_overrides (TEXT, the modelling read path) and the roof classifier cache;
dry-run against the audited DB reports 106 rows. No FE migration — the party-
ceiling values already exist in the roof pgEnum.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wires GuardedColumnClassifier(roof_party_ceiling_guard, <llm>) into the roof_type
column so a party-ceiling marker (another/same dwelling or premises above), with or
without a trailing depth, deterministically resolves to its party-ceiling member
(no overlay, ~0 heat loss) instead of the LLM occasionally reading it as a pitched
loft (#1376).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ADR-0041: flat roofs ARE scored by insulation thickness via RdSAP Table 16
col (1) ('joists at ceiling level and flat roof'); the RoofType taxonomy lacks
flat-thickness members so the depth is discarded — fix needs new FLAT_*MM members
(FE-owned pgEnum). ADR-0042: a landlord glazing override reconciles against the
cert's per-window sap_windows composition rather than flattening every window to
one type; mixed-and-close defers to the cert, mixed-and-far is flagged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
One-time script (dry-run default, --apply in a transaction, idempotent) that
NULLs the four lodged_* columns on every predicted-source baseline row — a
predicted EPC exists and no lodged one does — leaving Effective, the bill block,
and rebaseline_reason intact. Dry-run against the audited DB reports 12,236 rows.
Must run after the FE-owned Drizzle ALTER ... DROP NOT NULL lands.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The four lodged_* columns are nullable as a unit; from_domain writes them all
NULL when a predicted Property has no Lodged Performance, and to_domain
reconstructs lodged=None (lodged_sap_score is the read discriminator, mirroring
the bill block). The production lodged_* columns are FE-owned (Drizzle) and need
a companion ALTER ... DROP NOT NULL migration before the backend writes NULL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The PropertyBaselineOrchestrator now reads no Lodged Performance off a predicted
Property's neighbour-synthesised EPC: lodged is None when source_path is
'predicted', so no phantom lodged figure is manufactured. The Effective half is
unchanged. Rebaseliner port + PropertyBaselinePerformance.lodged widen to
Optional; the pristine-cert paths assert non-None (lodged is None only for a
predicted Property, which is always physical_state_changed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Amends ADR-0004's both-halves-always-populated invariant: EPC Prediction
borrows a neighbour's recorded figures, so a predicted Property has no Lodged
Performance — its lodged half is absent (NULL), only the Effective half is
persisted. Sharpens the CONTEXT.md glossary for Lodged / Baseline Performance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deterministic catalogue remap (no LLM) that re-resolves the stored archetype for
the canonical RdSAP heating descriptions the under-populated taxonomy
mis-classified. Dry-run by default; --apply writes inside a transaction;
idempotent. Updates property_overrides.override_value (TEXT — what the modelling
reads, the actual band-G fix: 3,028 rows incl. 2,210 community boilers + 770
panel/convector room heaters). Cache (the Drizzle-owned main_heating_system
pgEnum) updates are deferred per-value until the FE-repo enum migration adds the
new archetypes — surfaced explicitly by the script.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers the 10 audited "community CHP and boilers" properties (302) alongside the
2,210 "community boilers" (301) — both were mis-bucketed to Gas CPSU.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generalise the room-heater category-10 + conservative-control drag to cover both
solid and oil families; oil's natural fuel is heating oil (28).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>