Suppress floor heat loss for internal floors over the same dwelling 🟩

#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 <noreply@anthropic.com>
This commit is contained in:
Daniel Roth 2026-07-16 16:11:43 +00:00
parent 6356adb523
commit be1f69ccd4
2 changed files with 24 additions and 2 deletions

View file

@ -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 "

View file

@ -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),
}