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 557ea037..a4fb4229 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -492,6 +492,28 @@ def test_summary_0330_full_chain_sap_matches_worksheet_pdf_exactly() -> None: assert abs(result.sap_score_continuous - worksheet_unrounded_sap) < 1e-4 +def test_summary_0380_main_heating_category_is_heat_pump() -> None: + # Arrange — cert 0380's Summary lodges main heating as a PCDB- + # indexed Mitsubishi PUZ-WM50VHA (idx 104568), which lives in + # PCDB Table 362 (heat pumps only). The Elmhurst mapper must + # surface `main_heating_category=4` so the cascade routes the + # cert through the Appendix N3.6/N3.7 heat-pump path instead of + # falling through to the default boiler-ish branches that key off + # `main_heating_category in {1, 2}`. Spec ref: SAP 10.2 Table 4a + # (main heating category code 4 = heat pump). + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000899_PDF) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + + # Act + epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) + + # Assert + assert epc.sap_heating.main_heating_details, "no main heating details surfaced" + main = epc.sap_heating.main_heating_details[0] + assert main.main_heating_index_number == 104568 + assert main.main_heating_category == 4 + + def test_summary_0380_full_chain_sap_matches_worksheet_pdf_exactly() -> None: # Arrange — cert 0380-2471-3250-2596-8761 (Summary_000899.pdf / # dr87-0001-000899.pdf) is the first heat-pump cert under per-cert diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 2be18112..05af513f 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -61,6 +61,7 @@ from datatypes.epc.schema.rdsap_schema_21_0_1 import ( RdSapSchema21_0_1, EnergyElement as EnergyElement_21_0_1, ) +from domain.sap10_calculator.tables.pcdb import heat_pump_record from datatypes.epc.surveys.elmhurst_site_notes import ( AlternativeWall as ElmhurstAlternativeWall, BuildingPartDimensions as ElmhurstBuildingPartDimensions, @@ -3332,14 +3333,16 @@ def _elmhurst_sap_control_code(sap_control: str) -> Optional[int]: return int(m.group(1)) if m else None -# SAP10.2 Table 4a main-heating-category codes. Currently only the -# gas-fired-boiler branch is exercised by the Elmhurst cohort — the -# cascade reads `main_heating_category` to key the §4f pumps+fans table -# (160 kWh/yr for cat 2 = 115 central heating pump + 45 flue fan) and to -# detect heat-network mains (cat 6). Other categories (heat pumps, -# warm-air, electric storage, oil/biomass) are deferred until a fixture -# exercises them. +# SAP10.2 Table 4a main-heating-category codes. The cascade reads +# `main_heating_category` to key the §4f pumps+fans table (160 kWh/yr +# for cat 2 = 115 central heating pump + 45 flue fan), to detect +# heat-network mains (cat 6), and to gate the Appendix N3.6/N3.7 +# heat-pump path (cat 4 — `cert_to_inputs.py` line 1896/2005/2057/ +# 2104 all branch on `main_heating_category == 4`). Other categories +# (warm-air, electric storage, oil/biomass) are deferred until a +# fixture exercises them. _ELMHURST_HEATING_CATEGORY_GAS_BOILER: Final[int] = 2 +_ELMHURST_HEATING_CATEGORY_HEAT_PUMP: Final[int] = 4 _ELMHURST_GAS_BOILER_FUEL_TYPES: frozenset[str] = frozenset({ "Mains gas", "LPG bottled", @@ -3352,9 +3355,14 @@ def _elmhurst_main_heating_category( mh: ElmhurstMainHeating, pcdb_index: Optional[int] ) -> Optional[int]: """Derive the SAP10.2 Table 4a main-heating-category from Elmhurst- - lodged data. A PCDB-referenced boiler on mains/LPG gas is category 2 - (gas-fired boilers); other system types fall through to None so the - cascade applies its default pumps_fans 130 kWh/yr until extended.""" + lodged data. A PCDB index that resolves to a Table 362 record is a + heat pump (category 4) — Table 362 lists heat pumps only, so + membership is the authoritative signal. A PCDB-referenced boiler on + mains/LPG gas is category 2 (gas-fired boilers). Other system types + fall through to None so the cascade applies its default pumps_fans + 130 kWh/yr until extended.""" + if pcdb_index is not None and heat_pump_record(pcdb_index) is not None: + return _ELMHURST_HEATING_CATEGORY_HEAT_PUMP if pcdb_index is not None and mh.fuel_type in _ELMHURST_GAS_BOILER_FUEL_TYPES: return _ELMHURST_HEATING_CATEGORY_GAS_BOILER return None