From be1f69ccd432a05efc6a059b32af0559d8537627 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 16 Jul 2026 16:11:43 +0000 Subject: [PATCH] =?UTF-8?q?Suppress=20floor=20heat=20loss=20for=20internal?= =?UTF-8?q?=20floors=20over=20the=20same=20dwelling=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1625: 'Same dwelling below' → the "(another dwelling below)" party- floor suppression string, mirroring the gov-API code-8 precedent (RdSAP 10 §3: an internal floor between heated storeys has no floor heat loss). Wythenshawe 151→161 computed; 7/10 newly-unblocked land within 0.5 — ratchets re-baselined 0.76/0.72 → 0.75/0.75 for the two tail entries (−3.3/−5.5). Co-Authored-By: Claude Fable 5 --- .../test_pashub_sap_accuracy_wythenshawe.py | 10 ++++++++-- datatypes/epc/domain/mapper.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/backend/documents_parser/tests/test_pashub_sap_accuracy_wythenshawe.py b/backend/documents_parser/tests/test_pashub_sap_accuracy_wythenshawe.py index f070a04cb..c93a8adec 100644 --- a/backend/documents_parser/tests/test_pashub_sap_accuracy_wythenshawe.py +++ b/backend/documents_parser/tests/test_pashub_sap_accuracy_wythenshawe.py @@ -51,8 +51,14 @@ _MANIFEST_PATH = _FIXTURES_DIR / "manifest.json" # 76.2% = 115/151, MAE 0.716; 500767954156 lands within 0.5, 500797465814 is a # −8.8 tail entry). MAE ceiling re-baselined 0.67 → 0.72 per the # coverage-growth convention above. -_MIN_WITHIN_HALF: float = 0.76 -_MAX_SAP_MAE: float = 0.72 +# 2026-07-16 (#1622/#1625): floor type 'Same dwelling below' → "(another +# dwelling below)" party-floor suppression (gov-API code-8 precedent) unblocked +# all 10 fixtures → **161 computed / 7 blocked** (within-0.5 75.8% = 122/161, +# MAE 0.740). 7/10 newly-computed land within 0.5 — the treatment validates; +# −3.3/−5.5 tail entries dilute the aggregates. Floor 0.76 → 0.75, ceiling +# 0.72 → 0.75. +_MIN_WITHIN_HALF: float = 0.75 +_MAX_SAP_MAE: float = 0.75 _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 78d11ac71..b3e6783c9 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -6180,9 +6180,21 @@ _CANONICAL_GROUND_FLOOR_TYPE: Final[str] = "Ground floor" def _pashub_floor_type(label: str) -> str: if label.strip().lower() == _CANONICAL_GROUND_FLOOR_TYPE.lower(): return _CANONICAL_GROUND_FLOOR_TYPE + # "Same dwelling below" = an internal floor between heated storeys of ONE + # dwelling — RdSAP 10 §3 gives it no floor heat loss, mechanically + # identical to a party floor. Reuse the "(another dwelling below)" + # suppression string `heat_transmission.py` substring-matches, exactly as + # the gov-API code-8 precedent does (`_API_FLOOR_HEAT_LOSS_TO_FLOOR_TYPE`). + if label.strip() == _PASHUB_SAME_DWELLING_BELOW_FLOOR_TYPE: + return "(another dwelling below)" return label +# PasHub floor-location label for a floor directly above a heated room of the +# same dwelling (e.g. an upper-storey extension floor over the main building). +_PASHUB_SAME_DWELLING_BELOW_FLOOR_TYPE: Final[str] = "Same dwelling below" + + # PAS Hub lodges the ground-floor exposure as the "Floor type:" label. Only the # two non-ground exposures carry SAP consequence; a ground floor (or an unasked/ # blank field) routes through the default ground-floor cascade. Mirrors the @@ -6202,6 +6214,10 @@ _PASHUB_FLOOR_EXPOSURE: Final[Dict[str, tuple[bool, bool]]] = { # constant U=0.7 is reserved for a floor above a *partially* heated space # (`is_above_partially_heated_space`); the #1602 routing transposed the two. "Semi Exposed (unheated)": (True, False), + # Internal/party floor over a heated room of the same dwelling — no floor + # heat loss (suppressed via `_pashub_floor_type`'s "(another dwelling + # below)" normalization); neither Table 20 nor §5.14 applies. + _PASHUB_SAME_DWELLING_BELOW_FLOOR_TYPE: (False, False), }