diff --git a/backend/documents_parser/tests/test_pashub_sap_accuracy.py b/backend/documents_parser/tests/test_pashub_sap_accuracy.py index d660d8747..34141c47b 100644 --- a/backend/documents_parser/tests/test_pashub_sap_accuracy.py +++ b/backend/documents_parser/tests/test_pashub_sap_accuracy.py @@ -81,7 +81,10 @@ _MANIFEST_PATH = _FIXTURES_DIR / "manifest.json" # gap) — see docs/HANDOVER_PASHUB_838_PROBLEM_PROPERTIES.md. Re-baselined to the # full-coverage aggregate; ratchet back DOWN as those two are resolved. _MIN_WITHIN_HALF: float = 0.12 -_MAX_SAP_MAE: float = 2.71 +# Ratcheted 2.71 -> 2.64 by #1590 bug 2 (roof "Insulation At: None" -> explicit +# zero thickness): observed MAE 2.632 — the two ~7-SAP roof over-raters +# (507665533138 / 507670893756) correcting is exactly 14/205 ≈ 0.07 of MAE. +_MAX_SAP_MAE: float = 2.64 _KNOWN_GAP_REASON = ( "pashub `from_site_notes` mapper does not int-code a main-heating " diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 8ce2fc426..1c38228a1 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -5961,6 +5961,14 @@ def _map_roof( ) -> tuple[Optional[str], Optional[Union[str, int]]]: if roof is None: return None, None + # "Insulation At: None" is the assessor explicitly recording ZERO loft + # insulation (no thickness line follows — there is nothing to measure). + # Lodge an explicit 0 so `u_roof` takes the Table 16 uninsulated row + # (2.30 W/m²K) instead of falling through, on thickness=None, to the + # age-band "assumed insulated" default (issue #1590 bug 2). A blank + # `insulation_at` stays None — "not surveyed", not "no insulation". + if roof.insulation_at == "None": + return roof.insulation_at, 0 thickness: Optional[Union[str, int]] = ( roof.insulation_thickness_mm if roof.insulation_thickness_mm is not None