From c236aa5836b583b8198a7ace03ce79f7a42f02b1 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 4 Jun 2026 17:07:37 +0000 Subject: [PATCH] =?UTF-8?q?S0380.226:=20map=20Elmhurst=20"Jacket"=20cylind?= =?UTF-8?q?er=20insulation=20=E2=86=92=20loose-jacket=20(code=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Summary-path mapper raised UnmappedElmhurstLabel for a §15.1 "Cylinder Insulation Type: Jacket" lodging — only "Foam" (→1, factory) was mapped. SAP10 cylinder_insulation_type uses 2 for loose jacket (matching the GOV.UK API codes), and SAP 10.2 Table 2 Note 1 gives it a separate ~2× storage-loss factor that the cascade now handles (S0380.224). Add "Jacket" → 2 for cross-mapper parity with the API path and so the loose-jacket storage-loss branch fires on the Summary path. Surfaced by simulated case 19 (a 210 L jacket cylinder + electric storage heaters), which previously couldn't extract at all. §4 suite 2397 passed; mapper.py pyright unchanged at 32. Co-Authored-By: Claude Opus 4.8 --- .../tests/test_summary_pdf_mapper_chain.py | 29 +++++++++++++++++++ datatypes/epc/domain/mapper.py | 6 ++++ 2 files changed, 35 insertions(+) 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 94a3b927..c3a1f4df 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -4330,3 +4330,32 @@ def test_from_elmhurst_site_notes_matches_hand_built_000516() -> None: f"hand-built EpcPropertyData for cohort cert 000516:\n " + "\n ".join(diffs) ) + + +def test_elmhurst_jacket_cylinder_insulation_maps_to_loose_jacket_code_2() -> None: + # Arrange — an Elmhurst §15.1 "Cylinder Insulation Type: Jacket" + # lodging is a loose jacket, which SAP 10.2 Table 2 Note 1 gives a + # separate (higher) storage-loss factor than factory foam. The SAP10 + # `cylinder_insulation_type` enum uses 2 for loose jacket (1 = factory + # foam), matching the GOV.UK API path — so the Summary "Jacket" label + # must resolve to 2 for cross-mapper parity, and so the + # loose-jacket storage-loss branch (S0380.224) fires. Observed on the + # simulated-case-19 worksheet (210 L jacket cylinder + storage heaters). + from datatypes.epc.domain.mapper import _elmhurst_cylinder_insulation_code # pyright: ignore[reportPrivateUsage] + + # Act + code = _elmhurst_cylinder_insulation_code("Jacket", cylinder_present=True) + + # Assert + assert code == 2 + + +def test_elmhurst_foam_cylinder_insulation_still_maps_to_factory_code_1() -> None: + # Arrange — regression guard: the factory-foam label is unchanged. + from datatypes.epc.domain.mapper import _elmhurst_cylinder_insulation_code # pyright: ignore[reportPrivateUsage] + + # Act + code = _elmhurst_cylinder_insulation_code("Foam", cylinder_present=True) + + # Assert + assert code == 1 diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index caf09b6f..98a341b9 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -4710,8 +4710,14 @@ _ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10: Dict[str, int] = { # which SAP 10.2 Table 2 Note 2 treats as factory-applied PU foam). # Other labels (Loose Jacket, None) raise `UnmappedElmhurstLabel` # until a fixture exercises them. +# SAP10 cylinder_insulation_type enum: 1 = factory-applied foam, +# 2 = loose jacket (matching the GOV.UK API codes). SAP 10.2 Table 2 +# Note 1 gives loose jacket a separate, ~2× higher storage-loss factor; +# the cascade's loose-jacket branch is wired (S0380.224), so "Jacket" +# resolves to 2 for cross-mapper parity with the API path. _ELMHURST_CYLINDER_INSULATION_LABEL_TO_SAP10: Dict[str, int] = { "Foam": 1, + "Jacket": 2, }