From f878bf51a3d3f30f786c2b25a4f0bc87d480b225 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 27 May 2026 21:52:15 +0000 Subject: [PATCH] =?UTF-8?q?Slice=20S0380.14:=20add=20'Large'=20=E2=86=92?= =?UTF-8?q?=20cylinder=5Fsize=3D4=20(closes=20cert=209418=20Daikin)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐ŸŽฏ Closes the 7th and final ASHP cohort cert. Summary path now mirrors the API path's complete cohort closure at the ยฑ0.07 spec precision floor. Cert 9418-3062-8205-3566-7200 (Summary_000902.pdf): Daikin Altherma EDLQ05CAV3 (PCDB 102421 โ€” distinct from the rest of the cohort's Mitsubishi 104568), end-terrace house, TWO 1.64 kWp PV arrays (N+S), 210 L cylinder, `heating_duration_code='24'` (continuous heating). Worksheet "SAP value" lodges 84.6305. Single-line fix to `_ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10`: + "Large": 4, extending Slice S0380.6's "Medium" โ†’ 3 mapping to also cover the "Large" cylinder. Without it `_elmhurst_cylinder_size_code('Large', True)` returned None โ†’ cascade routed off the HP-with-cylinder HW path โ†’ HW kWh under by 466 (Summary 1404 vs API 1871 vs worksheet-implied 1871 via (64)/(216) divide). Forcing function: cert 9418 first-attempt Summary SAP closes from ฮ” +2.5973 (lookup miss) to ฮ” **+0.0296** โ€” within ยฑ0.07. The PV multi-array Slice S0380.9 work was already sufficient for cert 9418's two-array PV layout (1.64 kWp N + 1.64 kWp S surfaced correctly first-try). ASHP cohort closure: 7/7 at spec floor: cert ฮ” vs worksheet 0380 +0.0594 0350 +0.0458 2225 +0.0441 2636 +0.0323 3800 +0.0442 9285 +0.0502 9418 +0.0296 โ† this slice โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ mean +0.0437 Identical disposition to the API path's cohort closure at slice 102f (commit c0086660). Both paths now sit at the documented Appendix N3.6 PSR-interpolation precision floor. Added two tests: - `test_summary_9418_large_cylinder_routes_to_code_4` โ€” unit-level pin on the new mapping. - `test_summary_9418_full_chain_sap_within_spec_floor_of_worksheet` โ€” chain test at ยฑ0.07. Pyright net-zero on both edited files (mapper.py 32 baseline). Regression suite: 686 pass + 10 fail (= handover baseline 669 + 10 + 19 new GREEN tests across Slices S0380.2..S0380.14). Spec refs: - SAP 10.2 Table 2a โ€” cylinder volume factor (52) keyed on volume_l; 210 L = 0.8x range factor (vs 160 L = 0.9086). - BRE PCDB Table 362 โ€” Daikin EDLQ05CAV3 (id 102421) is the cohort's second HP record alongside Mitsubishi PUZ-WM50VHA (id 104568). - Cert 9418 worksheet `dr87-0001-000902.pdf` "Cylinder Volume 210.00". Co-Authored-By: Claude Opus 4.7 --- .../tests/test_summary_pdf_mapper_chain.py | 45 +++++++++++++++++++ datatypes/epc/domain/mapper.py | 9 ++-- 2 files changed, 50 insertions(+), 4 deletions(-) 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 a9ad62b0..2b7cd2e6 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -64,6 +64,7 @@ _SUMMARY_000901_PDF = _FIXTURES / "Summary_000901.pdf" # cert 3800 _SUMMARY_000904_PDF = _FIXTURES / "Summary_000904.pdf" # cert 9285 _SUMMARY_000900_PDF = _FIXTURES / "Summary_000900.pdf" # cert 2225 _SUMMARY_000898_PDF = _FIXTURES / "Summary_000898.pdf" # cert 2636 +_SUMMARY_000902_PDF = _FIXTURES / "Summary_000902.pdf" # cert 9418 # GOV.UK EPB API JSON for cert 001479 โ€” the API-path counterpart of the # Summary_001479.pdf fixture. Together they drive the API โ‰ก Summary @@ -816,6 +817,50 @@ def test_summary_2636_full_chain_sap_within_spec_floor_of_worksheet() -> None: assert abs(result.sap_score_continuous - worksheet_unrounded_sap) < _ASHP_COHORT_CHAIN_TOLERANCE +def test_summary_9418_large_cylinder_routes_to_code_4() -> None: + # Arrange โ€” cert 9418-3062-8205-3566-7200's Summary ยง15.1 lodges + # "Cylinder Size: Large". The dr87 worksheet lodges "Cylinder + # Volume 210.00" L, and the cascade lookup + # `_CYLINDER_SIZE_CODE_TO_LITRES = {3: 160.0, 4: 210.0}` maps code + # 4 โ†’ 210 L. Cert 9418 is the first cohort cert to exercise the + # "Large" cylinder lodging (every other cohort cert is "Medium"). + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000902_PDF) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + + # Act + epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) + + # Assert + assert epc.sap_heating.cylinder_size == 4 + + +def test_summary_9418_full_chain_sap_within_spec_floor_of_worksheet() -> None: + # Arrange โ€” cert 9418-3062-8205-3566-7200 (Summary_000902.pdf): + # **Daikin EDLQ05CAV3 ASHP** (PCDB index 102421 โ€” distinct from + # the rest of the cohort's Mitsubishi 104568), end-terrace house + # with TWO 1.64 kWp PV arrays (N + S), 210 L cylinder. + # `heating_duration_code='24'` per Table N4 (continuous heating). + # Worksheet "SAP value" lodges 84.6305. + # + # Closes the cohort: the final ASHP cert. The only Summary-mapper + # gap was the missing "Large" โ†’ 4 mapping in + # `_ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10` (Slice S0380.14, this + # commit) โ€” multi-array PV + Large-cylinder were the variants + # cert 9418 uniquely exercises. + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000902_PDF) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) + + # Act + result = calculate_sap_from_inputs( + cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES) + ) + + # Assert โ€” ยฑ0.07 ASHP-cohort spec-floor tolerance. + worksheet_unrounded_sap = 84.6305 + assert abs(result.sap_score_continuous - worksheet_unrounded_sap) < _ASHP_COHORT_CHAIN_TOLERANCE + + def test_summary_3800_full_chain_sap_within_spec_floor_of_worksheet() -> None: # Arrange โ€” cert 3800-8515-0922-3398-3563 (Summary_000901.pdf / # dr87-0001-000901.pdf) is the third ASHP cohort cert to close on diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 743551d6..3b6869c0 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -3375,12 +3375,13 @@ _ELMHURST_GAS_BOILER_FUEL_TYPES: frozenset[str] = frozenset({ # Elmhurst Summary ยง15.1 "Cylinder Size" labels mapped to the SAP10 # cascade enum that `domain/sap10_calculator/rdsap/cert_to_inputs.py` -# `_CYLINDER_SIZE_CODE_TO_LITRES` keys ({3: 160.0, 4: 210.0}). Only the -# "Medium" lodging is exercised by the cohort (cert 0380); other size -# labels (Small / Large / Very Large) are deferred until a fixture -# exercises them. +# `_CYLINDER_SIZE_CODE_TO_LITRES` keys ({3: 160.0, 4: 210.0}). Exercised +# by the cohort: "Medium" (cert 0380 et al โ€” 160 L) and "Large" (cert +# 9418 โ€” 210 L). "Small" and "Very Large" labels are deferred until a +# fixture exercises them. _ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10: Dict[str, int] = { "Medium": 3, + "Large": 4, }