Slice 55: Elmhurst party-wall code "CU" maps to cavity unfilled

`_ELMHURST_PARTY_WALL_CODE_TO_SAP10` only recognised the bare "C" and
"S" leading codes. Cert 001479 Main §7 lodges "Party Wall Type: CU
Cavity masonry unfilled" — the leading token is "CU", which fell
through to None and made `u_party_wall` apply the unknown-default
U=0.25 instead of the worksheet's lodged U=0.50.

Add "CU" → 4 (SAP10 WALL_CAVITY); `u_party_wall(4) = 0.5 W/m²K`
matches the worksheet's §3 `Party walls Main … 0.50` row exactly.

This widens the chain residual on cert 001479 (cascade SAP 63.17 →
61.90 vs target 69.0094) — not a regression: pre-slice the cascade
was UNDER-counting party-wall heat loss (U=0.25 vs the lodged 0.50),
which masked over-counting elsewhere. The party-wall U-value is now
worksheet-accurate; remaining 7.1 SAP gap will narrow as the other
mapper gaps (Ext2 exposed floor, roof insulation thickness, secondary
heating SAP code, etc.) land in follow-up slices.

All 10 chain tests green (6 cohort + 2 cert-001479 structural pins).
Pyright net-zero (35-error baseline preserved on mapper.py).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-24 22:26:50 +00:00
parent 4427b58a44
commit c89206fc7f
2 changed files with 22 additions and 1 deletions

View file

@ -268,3 +268,21 @@ def test_summary_001479_mapper_extensions_count_matches_extension_bps() -> None:
# Assert
assert epc.extensions_count == 2
assert len(epc.sap_building_parts) == 3
def test_summary_001479_main_party_wall_construction_is_cavity_unfilled() -> None:
# Arrange — cert 001479 Main §7 Walls lodges "Party Wall Type: CU
# Cavity masonry unfilled". The Elmhurst leading-code map previously
# only knew "S" and "C"; "CU" fell through to None, which made the
# cascade default to U=0.25 instead of the worksheet's lodged U=0.50.
# The fix adds "CU" → SAP10 wall_construction code 4 (WALL_CAVITY),
# which `u_party_wall` resolves to U=0.50 — matching the worksheet's
# §3 `Party walls Main … 0.50` row.
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_001479_PDF)
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
# Act
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
# Assert
assert epc.sap_building_parts[0].party_wall_construction == 4

View file

@ -1852,7 +1852,10 @@ def _elmhurst_wall_construction_int(coded: str) -> Optional[int]:
# all → U=0.0; unknown → U=0.25).
_ELMHURST_PARTY_WALL_CODE_TO_SAP10: Dict[str, int] = {
"S": 3, # Solid masonry / timber / system build → U=0.0
"C": 4, # Cavity (unfilled) → U=0.5; observed in API path
"C": 4, # Cavity (unfilled) → U=0.5
"CU": 4, # Cavity masonry unfilled — same U=0.5 cascade; Elmhurst
# encodes party-wall cavity type with the masonry sub-code
# (CU vs CF filled) — observed first on cert 001479 Main.
}