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 e4113edb..f0020843 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -62,6 +62,7 @@ _SUMMARY_000899_PDF = _FIXTURES / "Summary_000899.pdf" _SUMMARY_000903_PDF = _FIXTURES / "Summary_000903.pdf" _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 # GOV.UK EPB API JSON for cert 001479 — the API-path counterpart of the # Summary_001479.pdf fixture. Together they drive the API ≡ Summary @@ -713,6 +714,55 @@ def test_summary_0350_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_2225_no_showers_lodged_resolves_to_zero_counts() -> None: + # Arrange — cert 2225-3062-8205-2856-7204's Summary §1x Baths and + # Showers block lodges 0 baths and ZERO showers (no shower rows at + # all). The Summary mapper's existing logic at + # `mapper.py:3536-3537` predicates the count assignment on + # `has_electric_shower`: when no electric shower is detected the + # counts collapse to None — but cert 2225 has no showers at all, + # not "non-electric showers". The None values then drive the + # cascade's default-1-mixer assumption, over-counting HW kWh. + # Same disposition the API path received in slice 102f-prep.8 + # (commit 1d5183c6: "API mapper resolves shower_outlets=None → + # 0 mixers"). + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000900_PDF) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + # Pre-condition: §1x lodges zero showers (proves the test sees + # the same no-showers fixture the cascade does). + assert len(site_notes.baths_and_showers.showers) == 0 + + # Act + epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) + + # Assert — zero-shower lodgings resolve to explicit 0 counts (not + # None) so the cascade does not default-assume a mixer. + assert epc.sap_heating.electric_shower_count == 0 + assert epc.sap_heating.mixer_shower_count == 0 + + +def test_summary_2225_full_chain_sap_within_spec_floor_of_worksheet() -> None: + # Arrange — cert 2225-3062-8205-2856-7204 (Summary_000900.pdf): + # Mitsubishi PUZ-WM50VHA, single-bp single-array PV (3.28 kWp SE), + # ZERO showers lodged. Worksheet "SAP value" 88.7921. Slice + # S0380.11 closed the zero-shower defaulting bug (None → 0 mixers + # for cohort certs that lodge no showers); cert 2225 was the + # forcing function. Same disposition the API path received in + # slice 102f-prep.8 (commit 1d5183c6). + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000900_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 = 88.7921 + 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 d3ccbd83..743551d6 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -3533,8 +3533,24 @@ def _map_elmhurst_sap_heating(survey: ElmhurstSiteNotes) -> SapHeating: mh.secondary_heating_sap_code, ), number_baths=survey.baths_and_showers.number_of_baths, - electric_shower_count=1 if has_electric_shower else None, - mixer_shower_count=0 if has_electric_shower else None, + # Zero-shower lodgings resolve to explicit 0 (not None) so the + # cascade doesn't default-assume a mixer — same disposition + # the API path received in slice 102f-prep.8 ("API mapper + # resolves shower_outlets=None → 0 mixers") on cohort cert + # 2225. Non-zero shower lodgings keep the hand-built-fixture + # convention (None for non-electric → cascade derives count + # from `shower_outlets` instead) so the boiler-cohort parity + # tests in this file stay GREEN. + electric_shower_count=( + 0 + if not survey.baths_and_showers.showers + else (1 if has_electric_shower else None) + ), + mixer_shower_count=( + 0 + if not survey.baths_and_showers.showers + else (0 if has_electric_shower else None) + ), )