RdSAP 10 Specification (10-06-2025) §5.2.5: "If a dwelling or part of a
dwelling has commercial premises above record as another dwelling
above" — so code 9 ("another premises above") must get the same
zero-heat-loss treatment as code 7 ("another dwelling above"), per the
spec's own "there is no heat loss through the roof of a building part
that has the same dwelling above or another dwelling above."
The previous fix mapped both to None, which only avoided the mapper
crash — it didn't achieve zero heat loss. heat_transmission.py's
per-part suppression (part_roof_is_party) only fires when
roof_construction_type CONTAINS the literal string "another dwelling
above"; that string was only ever set on the Elmhurst ingestion path.
The API path mapped 7 (and, until now, 9) to None, so the suppression
never fired for any API-derived cert — has_exposed_roof (the
dwelling_type-label-only fallback) was the sole gate, with no way to
know about a per-part party-ceiling lodgement. Both codes now map to
"(another dwelling above)" so the existing suppression logic fires on
both ingestion paths, matching what
domain/epc/property_overrides/roof_party_ceiling_guard.py already does
independently for the landlord-override pipeline (same spec citation).
Verified against the motivating cert (uprn 10013320122): roof_w_per_k
now correctly computes to 0.0. Full RdSAP-21.0.1 corpus (1000 certs)
re-run: within-0.5 unchanged at 77.8%, MAE improves marginally
(0.6364 vs 0.637 ceiling) — no regression.
ADR-0048's intended end-state (#1444 Part C): every mapped archetype now
resolves confident companions, so an incomplete set can only mean a new
archetype landed without choosing them — fail loudly at overlay time.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The last real archetype on the companion-gap log; the log test now
simulates a future incomplete archetype (warm-air heat pump, Group 5
control deliberately unmapped).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Supersedes the "direct-acting electric drags no control/category" pin:
None does not mean unset — it means inherit the replaced system's value
(ADR-0048 bug class), verified suppressing HHRSH on portfolio 796.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#1444 Parts A+B for codes 191/192, diagnosed on portfolio 796: 30 of the
first 40 live overrides inherit a replaced system's category (6/4/7) —
suppressing HHRSH or billing direct-acting heat as storage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same party-ceiling semantics as the already-mapped code 7 ("another
dwelling above" / "same dwelling above") — the unit has no exposed
roof, so there's nothing for the cascade to compute a U-value for.
Confirmed against the live cert (uprn 10013320122): roofs[].description
= "(another premises above)", roof_insulation_location = "ND" (the
codebase's established "no exposed roof" signal).
Surfaced by 1 failed modelling_e2e subtask on portfolio 817
(task_source='modelling_e2e', job_completed > '2026-07-03 12:50:37.555+01:00').
Some lodged certs omit the sap_heating.has_fixed_air_conditioning key,
which the generic from_dict loader treats as a required field and
rejects outright. Two prior commits (26651ca71, 8074f4152) fixed the
duplicate top-level field of the same name but left this nested copy
untouched; a third pass (44991bed0) missed it too. The mapper already
reads it defensively (== "true", which is False on None), so this is
a safe default.
Surfaced by 7 failed modelling_e2e subtasks on portfolio 817.