From c89206fc7f2abc4bcfa0b808e1a5510d8770dcb2 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sun, 24 May 2026 22:26:50 +0000 Subject: [PATCH] Slice 55: Elmhurst party-wall code "CU" maps to cavity unfilled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_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 --- .../tests/test_summary_pdf_mapper_chain.py | 18 ++++++++++++++++++ datatypes/epc/domain/mapper.py | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py index 539ced6f..2e74aa30 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -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 diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 1dda5e4f..07a461e7 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -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. }