Model/docs/baseline-downgrade-followups.md
Khalim Conn-Kowlessar 5fc8bd3b3d docs: record predicted-property display fixes (item 4) 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:54:53 +00:00

5.9 KiB
Raw Blame History

Follow-ups from the full-SAP baseline-downgrade work (ADR-0037)

Open items surfaced while fixing the full-SAP mapper / portfolio-796 downgrades. Each is separate from the full-SAP fix (fix/baseline-downgrades).

1. RdSAP ventilation mapping is inconsistent across schemas

Severity: accuracy bug for MEV/MVHR dwellings. The mappers handle sap_ventilation four different ways:

  • 17_0 + full-SAP — build via _sap_17_1_ventilation, which maps mechanical_ventilation_kind.
  • 21_0_1 — rich inline SapVentilation(...) incl. the kind.
  • 17_1 / 18_0 / 20_0_0 — built SapVentilation(sheltered_sides=…) and dropped the kind. Fixed in #1333 (mirror _api_mechanical_ventilation_kind).
  • 19_0 / 21_0_0 — set no sap_ventilation at all → the EpcPropertyData default (None). They dropped the entire ventilation block (sheltered sides + kind), not just the kind. Fixed in feat/rdsap-19-21-ventilation: both now construct SapVentilation(...) mirroring 21.0.1, but only for the fields each schema lodges — the top-level mechanical_ventilation enum (→ kind) and sheltered_sides (from built_form). Neither lodges the fan/flue/vent counts, so those stay unset (RdSAP Table-5 age defaults — see counts note below).

Either way, an MEV/MVHR cert (mechanical_ventilation ≠ 0) is treated as natural by the affected mappers — wrong §2 ventilation cascade (and heat recovery). Natural certs (code 0/5None) are unaffected.

  • For natural-ventilation certs (mechanical_ventilation = 0, e.g. UPRN 100020603823 / property 726605) it's benign — unmapped → None defaults to natural in the §2 cascade.
  • For MEV/MVHR certs (mechanical_ventilation ≠ 0) the calculator treats the dwelling as naturally ventilated, getting the SAP §2 ventilation cascade (and any heat recovery) wrong → mis-scored SAP.

The granular counts (fans/flues/vents) are not a bug: older RdSAP open-data certs don't lodge them, and the calc correctly uses RdSAP Table-5 age defaults. percent_draughtproofed is mapped (top-level) and read by the calc.

19.0 / 21.0.0 fix (done, feat/rdsap-19-21-ventilation): both build SapVentilation(sheltered_sides=…, mechanical_ventilation_kind=…) mirroring 21.0.1. Guarded by the extended mapper-level MVHR test + a sheltered_sides regression, with the corpus (held 73.3% / MAE 0.774 — it's 21.0.1-only so the change isn't exercised by it) and the 6002-case mapper-corpus staying green. Outstanding SAP-accuracy fast-follow: an Elmhurst-anchored MEV/MVHR RealCertExpectation (the corpus is natural-vent-dominated, so the kind change isn't exercised by it). Blast radius (still to quantify): count portfolio-796 certs on schema 19.0 / 21.0.0 with mechanical_ventilation ≠ 0.

2. FE "Main Fuel: Unknown" is FE-side, not a Model mapper gap

main_fuel_type (the gov fuel code) is populated Model-side — UPRN 10093412452 (709791) → 1, UPRN 100020603823 (726605) → 26 — and is persisted (epc_main_heating_detail.main_fuel_type). So the panel's "Main Fuel: Unknown" is the front-end either not mapping the code → fuel name or reading a field we don't populate. Needs an FE-repo (Drizzle/Next) check, not a Model change.

3. Survivor clusters from the post-fix population sweep

scripts/hyde/case_a_population_sweep.py: the representative sample rebaselined cleanly (0 survivors), but the worst-old-drop sample held 28 survivors (lodged A/B → new C/D, 1525 SAP) in tight UPRN clusters (new-build blocks), spanning multiple schemas (16.0/16.1/17.1/18.0.0) and heating types. No single mapper-gap signature → likely genuine SAP-2012→10.2 drops for very-high-lodged new-builds, but triage one in Elmhurst to confirm genuine vs a residual calc issue before trusting the cohort.

4. Predicted-property display path (e.g. property 721167)

721167 has no lodged EPC (predicted). Its Heating-Control / Main-Fuel / Ventilation Unknowns come from the prediction synthesis path (domain/epc_prediction/epc_prediction.py), not the lodged-cert mappers and not the landlord-override overlay (overlays_from is simulation-time only, ADR-0032 — it never writes the persisted/displayed EPC). Investigated and fixed on fix/predicted-property-display-unknowns:

  • Heating Control — FIXED (Model). _apply_heating_donor copied the donor's calc sap_heating cluster but left the display rows (main_heating, main_heating_controls) on the size-representative structural template — incoherent with the donated system, and "Unknown" whenever the template lodged no control row (exactly 721167: no main_heating_controls energy element persisted). Now carries the donor's display heating + control alongside the calc cluster.
  • Ventilation — FIXED (Model). The prediction never synthesised ventilation — it kept the template's sap_ventilation, so a predicted dwelling in an MEV/MVHR neighbourhood was scored + displayed as natural. New _apply_ventilation_mode modes mechanical_ventilation_kind across the cohort (recency/geo-weighted, mirroring glazing). Natural-vent cohorts mode to None and stay natural (§2 default) — so a genuinely natural dwelling like 721167 correctly stays natural; the fix moves only real MEV/MVHR neighbourhoods.
  • Main Fuel — NOT a prediction gap. epc_main_heating_detail.main_fuel_type is persisted for predicted properties (721167 → 26 = mains gas, carried from the heating donor). The "Unknown" is the same FE code→name gap as item 2 (the Drizzle/Next repo not mapping the fuel code), not a Model issue.

Both fixes are display-only for the calculator (component-accuracy gate, corpus harness, and prediction e2e all green); persistence already carries main_heating_controls (energy element) and ventilation_mechanical_ventilation_kind, so the synthesised values reach the passport on the next prediction run.