From 784e05ebbf4ce1125a95e64ee760416677d7c43d Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sat, 30 May 2026 15:32:51 +0000 Subject: [PATCH] =?UTF-8?q?Slice=20S0380.101:=20HP=20SAP=20code=20211-227/?= =?UTF-8?q?521-527=20=E2=86=92=20main=5Fheating=5Fcategory=3D4=20(SAP=2010?= =?UTF-8?q?.2=20Table=204a)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SAP 10.2 Table 4a (PDF p.165) lists "Heat pumps" as category 4 for SAP main-heating codes: 211-217 — ground/water source heat pumps 221-227 — air source heat pumps (224 = ASHP 2013+, COP 1.70) 521-527 — warm-air heat pumps Cert 000565 Main 1 lodges `Main Heating SAP Code = 224` (ASHP 2013+) with `PCDF boiler Reference = 0` — i.e. no PCDB Table 362 lookup is possible. Pre-slice `_elmhurst_main_heating_category` returned None on this path (the existing PCDB-Table-362-membership check failed), falling through to the cascade's `_DEFAULT_PUMPS_FANS_KWH_PER_YR = 130` (incorrect — HP circulation pump's electricity is inside the system COP per SAP 10.2 Table 4f line "Heat pumps", so the cascade row is 0 kWh/year for category 4). Single-line fix: after the existing PCDB-resolution branches, check `mh.main_heating_sap_code in _HEAT_PUMP_SAP_MAIN_HEATING_CODES` and return category 4 if so. New frozenset of HP codes (subset of the existing `_ELECTRIC_SAP_MAIN_HEATING_CODES`). Transient state at HEAD (cert 000565): - main_heating_category: None → 4 ✓ - pumps_fans cascade: 255.0 → 125.0 kWh/yr (HP base 0 + flue 45 + solar HW 80; MEV +127.5 kWh still missing — wiring lands in S0380.102) - sap_score (int): 29 ✓ EXACT preserved - sap_score_continuous: 28.31 → 28.69 (transient drift +0.39 vs ws; the previously-cancelling +130 over-count is gone, restoring the MEV-under net negative — closes when S0380.102 lands) Cohort safety: cohort certs 000474..000516 are gas-combi with `sap_main_heating_code=None` (PCDB Table 105 boiler identified via the index instead). No cohort cert affected. Cert 0380 + other golden HP fixtures lodge category=4 via the API mapper, also unaffected. Per the spec citation in [[feedback-spec-citation-in-commits]] + the standing TODO at mapper.py:4037-4043, this slice is the category half of the coupled cert 000565 closure arc. Pyright net-zero per touched file. Co-Authored-By: Claude Opus 4.7 --- .../tests/test_summary_pdf_mapper_chain.py | 27 ++++++++++++++ datatypes/epc/domain/mapper.py | 36 +++++++++++++------ 2 files changed, 53 insertions(+), 10 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 6dcde6fb..400dab5d 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -1847,6 +1847,33 @@ def test_summary_000565_ext2_floor_routes_to_u_value_0p22_via_table_20_per_rdsap assert bp_2.floor_insulation_thickness == "200mm" +def test_summary_000565_main_1_ashp_sap_code_224_routes_to_main_heating_category_4_per_sap_table_4a() -> None: + # Arrange — SAP 10.2 Table 4a (PDF p.165) "Main heating systems": + # the category column lists "Heat pumps" as category 4. Codes in + # rows 211-217 (ground/water source HP) and 221-227 (air source HP) + # and 521-527 (warm-air HP) all map to category 4. + # + # Cert 000565 Main 1 lodges `Main Heating SAP Code = 224` (Air + # source heat pump, 2013 or later — SAP 10.2 Appendix N efficiency + # row 224, COP 1.70). Without a PCDB Table 362 record (cert lodges + # `PCDF boiler Reference = 0`) the existing mapper's `_elmhurst_ + # main_heating_category` returns None, which falls through to the + # cascade's `_DEFAULT_PUMPS_FANS_KWH_PER_YR = 130` (incorrect — HP + # circulation pump's electricity is inside the system COP per + # SAP 10.2 Table 4f, so the category 4 row is 0 kWh/year). + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000565_PDF) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + + # Act + epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) + + # Assert + assert epc.sap_heating is not None + main_1 = epc.sap_heating.main_heating_details[0] + assert main_1.sap_main_heating_code == 224 + assert main_1.main_heating_category == 4 + + def test_summary_000565_ext1_floor_above_partially_heated_routes_to_u_value_0p7_per_rdsap_10_section_5_14() -> None: # Arrange — RdSAP 10 §5.14 (PDF p.47) "U-value of floor above a # partially heated space": diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index ef2c16d6..ed1e2bb1 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -3853,6 +3853,22 @@ _ELECTRIC_SAP_MAIN_HEATING_CODES: Final[frozenset[int]] = frozenset( + list(range(521, 528)) ) +# SAP 10.2 Table 4a "Heat pumps" rows — codes whose `main_heating_ +# category` is 4 per the table's category column. Used when the cert +# lodges a Table 4a SAP code but no PCDB Table 362 record (the +# preferred identifier per `_elmhurst_main_heating_category`). Subset +# of `_ELECTRIC_SAP_MAIN_HEATING_CODES`. +# +# 211-217 — ground/water source heat pumps +# 221-227 — air source heat pumps (224 = ASHP 2013+, the cert 000565 +# Main 1 lodging) +# 521-527 — warm-air heat pumps +_HEAT_PUMP_SAP_MAIN_HEATING_CODES: Final[frozenset[int]] = frozenset( + list(range(211, 218)) + + list(range(221, 228)) + + list(range(521, 528)) +) + class UnmappedElmhurstLabel(ValueError): """An Elmhurst Summary lodged a finite-enum label that the mapper @@ -4030,22 +4046,22 @@ def _elmhurst_main_heating_category( 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. + mains/LPG gas is category 2 (gas-fired boilers). - TODO: route Table 4a HP SAP codes (211-227, 521-527) to - category=4 when no PCDB Table 362 record is lodged. Currently - deferred because the correct dispatch needs (a) Table 12a - high/low rate split for HP-on-E7 cost cascade and (b) Table 4f - MEV / flue-fan / solar HW pump components for pumps_fans — - without both, naive category=4 dispatch overshoots cost by - £1.3k and undershoots pumps_fans by 252 kWh on cert 000565. + SAP 10.2 Table 4a (PDF p.165) lists "Heat pumps" as category 4 for + SAP codes 211-217 (ground/water source), 221-227 (air source) and + 521-527 (warm-air). When a cert lodges one of these codes WITHOUT + a PCDB Table 362 record (e.g. cert 000565 Main 1: SAP code 224 + + `PCDF boiler Reference = 0`), category 4 still applies — the + cascade's pumps_fans path then routes through the HP-specific 0 + kWh/yr row (Table 4f) rather than the 130 kWh/yr default base. """ 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 + if mh.main_heating_sap_code in _HEAT_PUMP_SAP_MAIN_HEATING_CODES: + return _ELMHURST_HEATING_CATEGORY_HEAT_PUMP return None