From 5402dd17e18a9569849ec85faeaeb923a35313a0 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 28 May 2026 09:46:44 +0000 Subject: [PATCH] =?UTF-8?q?Slice=20S0380.24:=20SAP=20code=20631=20?= =?UTF-8?q?=E2=86=92=20house=20coal=20secondary=20fuel=20=E2=80=94=20close?= =?UTF-8?q?s=20cert=202102=20-15.81=20=E2=86=92=20+5e-5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per SAP 10.2 spec page 165 Table 4a Category 10 (Room heaters), the 600-range secondary-heating SAP codes split by fuel: 601-613: Gas (mains gas / LPG / biogas) — column A is mains gas. 621-625: Liquid fuel room heaters (oil / bioethanol). 631-634: Solid fuel room heaters (open fire, closed room heater with/without boiler) — house coal is the modal default. 691-699: Electric room heaters. `_elmhurst_secondary_fuel_from_sap_code` previously mapped the entire 601-630 range to mains gas (API code 26). Two bugs: 1. Codes 621-625 are oil heaters, not gas. (Cohort hasn't surfaced an oil-secondary cert yet — deferred until a fixture exercises.) 2. Codes 631-634 are solid fuel, not gas, and weren't in the range at all. Cascade fell through to the secondary-fuel-None default (standard electricity at 13.19 p/kWh), over-charging cert 2102's "Open fire in grate" secondary by ~£340/yr. Narrow the gas range to 601-613 (per the spec) and add 631-634 → API fuel code 11 (Coal in `_ELMHURST_MAIN_FUEL_TO_SAP10`) → Table 32 direct lookup returns 3.67 p/kWh (house coal), matching worksheet (242) "Space heating - secondary 3585.2401 × 3.6700 = 131.58". Cohort-2 outcome (38 certs, Summary path): exact (<1e-4): 20 → **21** (+1: cert 2102 -15.81 → +5e-5) ±5+: 1 → **0** (last big-gap closed) Cert 2102 verified end-to-end: - secondary_heating_type=631 → secondary_fuel_type=11 → 3.67 p/kWh - Cascade SAP 63.8732 vs worksheet 63.8732 (delta +5e-5) - Cascade total fuel cost £787.03 = worksheet £787.03 exactly Pyright net-zero on both touched files (mapper.py 32→32, test 0→0). Tests: 703 → 704 pass (+1 new SAP-code-631 secondary-fuel routing test), 10 expected fails unchanged. Co-Authored-By: Claude Opus 4.7 --- .../tests/test_summary_pdf_mapper_chain.py | 27 +++++++++++++++++++ datatypes/epc/domain/mapper.py | 27 +++++++++++++++---- 2 files changed, 49 insertions(+), 5 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 164063c6..f8a79eef 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -313,6 +313,33 @@ def test_summary_001479_secondary_heating_routes_mains_gas_fuel() -> None: assert epc.sap_heating.secondary_fuel_type == 26 +def test_summary_2102_secondary_heating_routes_house_coal_for_open_fire() -> None: + # Arrange — cohort-2 cert 2102-3018-0205-7886-5204 §14.1 lodges + # "Secondary Heating Code: SAP code 631" — "Open fire in grate" + # per SAP 10.2 Table 4a Category 10 (Room heaters), solid fuel + # column. Without the per-code routing the cascade defaults to + # standard electricity at 13.19 p/kWh and over-charges secondary + # heating by ~£340/yr, pushing SAP -15.81 below the worksheet's + # 63.87. Worksheet line (242) "Space heating - secondary 3585.24 + # × 3.6700 = 131.58" confirms house-coal pricing (Table 32 fuel + # code 11 = 3.67 p/kWh). + cert_dir = Path( + "sap worksheets/additional with api 2/2102-3018-0205-7886-5204" + ) + summary_pdf = next(cert_dir.glob("Summary_*.pdf")) + pages = _summary_pdf_to_textract_style_pages(summary_pdf) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + + # Act + epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) + + # Assert + assert epc.sap_heating.secondary_heating_type == 631 + # 11 = "Coal" in `_ELMHURST_MAIN_FUEL_TO_SAP10` → Table 32 lookup + # returns 3.67 p/kWh (house coal). + assert epc.sap_heating.secondary_fuel_type == 11 + + def test_summary_9501_flat_has_no_built_form_in_summary_pdf() -> None: # Arrange — cert 9501 (Summary_000784.pdf) is a flat. The Elmhurst # Summary's §1.0 "Property type" section lodges the built-form diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index ce8162ed..194d6d7d 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -3350,14 +3350,31 @@ def _elmhurst_secondary_fuel_from_sap_code( fitting live effect gas fire") but not the fuel int separately; the cascade's `_secondary_fuel_cost_gbp_per_kwh` defaults to standard electricity when `secondary_fuel_type` is None — correct for the - portable-electric default but wrong for cert 001479's mains-gas fire. - Returns 26 (mains gas) for SAP codes in the 600-630 range; None for - other codes (cascade default fires, matching cohort 000490 SAP code - 691 electric panel).""" + portable-electric default but wrong for fuel-fired room heaters. + + SAP 10.2 Table 4a Category 10 ("Room heaters") code blocks: + 601-613: Gas (mains gas / LPG / biogas) — column A is mains gas; + column B for LPG. Cohort default is mains gas + (`_ELMHURST_MAIN_FUEL_TO_SAP10["Mains gas"] = 26`). + 621-625: Liquid fuel room heaters (oil / bioethanol). Cohort + not yet exercised; deferred until a fixture surfaces. + 631-634: Solid fuel room heaters (open fire, closed room + heater with/without boiler). House coal is the modal + default per Table 12 secondary rate (3.67 p/kWh). + 691-699: Electric room heaters. Cascade default (None) routes + to standard electricity (13.19 p/kWh). + + Cohort cert 2102-3018-0205-7886-5204 surfaces the 631 ("Open fire + in grate") path — pre-slice the cascade defaulted to electricity + at 13.19 p/kWh, over-charging secondary by ~£340/yr and pushing + SAP -15.81 below the worksheet's 63.87. + """ if sap_code is None: return None - if 601 <= sap_code <= 630: + if 601 <= sap_code <= 613: return 26 # Mains gas, matching `_ELMHURST_MAIN_FUEL_TO_SAP10` + if 631 <= sap_code <= 634: + return 11 # House coal (Coal in `_ELMHURST_MAIN_FUEL_TO_SAP10`) return None