From 7901dda4552373b2dc99268d37acf2c4ffda2a28 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 2 Jun 2026 09:53:01 +0000 Subject: [PATCH] =?UTF-8?q?Slice=20S0380.167:=20EES=20codes=20WEA/REA/OEA?= =?UTF-8?q?=20=E2=86=92=20electricity=20(electric=20storage=2011-14=20unbl?= =?UTF-8?q?ock)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds three Elmhurst EES (Energy Efficiency Standard) codes to `_ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE` so the mapper can derive the main heating fuel for electric storage / direct-acting certs whose Elmhurst Summary §14.0 does not lodge a "Main Heating Fuel Type" string (same pattern as the solid-fuel block above): "WEA": 30, # electric warm-air storage "REA": 30, # resistive electric (corpus electric 12 SAP 691) "OEA": 30, # other electric (corpus electric 13/14 SAP 701) All route to Table 32 standard-electricity code 30; the cascade resolves the actual price tier (high vs low rate) downstream via `_rdsap_tariff(epc)` keyed off `meter_type`. The corpus carries 4 electric-storage variants on the 18-hour tariff: electric 11 — WEA + SAP 515 (warm-air electric) electric 12 — REA + SAP 691 electric 13 — OEA + SAP 701 electric 14 — OEA + SAP 701 (differs from 13 by emitter / controls) Pre-slice all 4 raised `MissingMainFuelType` per S0380.132. Post-slice all 4 EXACT on first try across all 4 metrics: electric 11: ΔSAP_c +0.0000 Δcost +£0.0000 ΔCO2 −0.0000 ΔPE −0.0000 electric 12: ΔSAP_c +0.0000 Δcost +£0.0000 ΔCO2 −0.0000 ΔPE −0.0000 electric 13: ΔSAP_c +0.0000 Δcost −£0.0000 ΔCO2 +0.0000 ΔPE −0.0000 electric 14: ΔSAP_c +0.0000 Δcost −£0.0000 ΔCO2 +0.0000 ΔPE −0.0000 Closure on first try because the cascade was already wired for the electric-storage path (SAP 10.2 Table 4a codes 515 / 691 / 701, Table 4e Group 4 storage controls, Table 5a pump-gain wet-gate from S0380.160, S0380.144 secondary-fraction by sub-row); only the Elmhurst EES → fuel mapping was missing. Moves electric 11/12/13/14 out of `_BLOCKED_BY_MISSING_MAIN_FUEL_TYPE` into `_EXPECTATIONS` at ±0.0000. Blocked tier now: 11 variants (community heating × 5, no system, oil 2-6). Tests: - test_elmhurst_main_heating_ees_maps_electric_storage_codes_to_electricity - corpus pins: electric 11/12/13/14 expected residuals = ±0.0000 Cascade-OK tier: 30 variants (up from 25), all SAP / cost / CO2 / PE EXACT (< 1e-4) vs Elmhurst worksheet on every metric. Co-Authored-By: Claude Opus 4.7 --- .../tests/test_heating_systems_corpus.py | 12 ++++++--- datatypes/epc/domain/mapper.py | 16 ++++++++++++ .../rdsap/tests/test_cert_to_inputs.py | 26 +++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/backend/documents_parser/tests/test_heating_systems_corpus.py b/backend/documents_parser/tests/test_heating_systems_corpus.py index ad1ba32a..935f8a76 100644 --- a/backend/documents_parser/tests/test_heating_systems_corpus.py +++ b/backend/documents_parser/tests/test_heating_systems_corpus.py @@ -415,6 +415,14 @@ _EXPECTATIONS: tuple[_CorpusExpectation, ...] = ( _CorpusExpectation(variant='electric 7', block='11a', expected_sap_resid=-0.0000, expected_cost_resid_gbp=+0.0000, expected_co2_resid_kg=+0.0000, expected_pe_resid_kwh=+0.0000), _CorpusExpectation(variant='electric 8', block='11a', expected_sap_resid=-0.0000, expected_cost_resid_gbp=-0.0000, expected_co2_resid_kg=+0.0000, expected_pe_resid_kwh=-0.0000), _CorpusExpectation(variant='electric 9', block='11a', expected_sap_resid=+0.0000, expected_cost_resid_gbp=+0.0000, expected_co2_resid_kg=+0.0000, expected_pe_resid_kwh=+0.0000), + # Slice S0380.167 unblocked electric storage 11-14 via EES codes + # WEA / REA / OEA → fuel code 30 (standard electricity). All 4 EXACT + # on first try — the cascade was already wired for electric storage + # paths. + _CorpusExpectation(variant='electric 11', block='11a', expected_sap_resid=+0.0000, expected_cost_resid_gbp=+0.0000, expected_co2_resid_kg=-0.0000, expected_pe_resid_kwh=-0.0000), + _CorpusExpectation(variant='electric 12', block='11a', expected_sap_resid=+0.0000, expected_cost_resid_gbp=+0.0000, expected_co2_resid_kg=-0.0000, expected_pe_resid_kwh=-0.0000), + _CorpusExpectation(variant='electric 13', block='11a', expected_sap_resid=+0.0000, expected_cost_resid_gbp=-0.0000, expected_co2_resid_kg=+0.0000, expected_pe_resid_kwh=-0.0000), + _CorpusExpectation(variant='electric 14', block='11a', expected_sap_resid=+0.0000, expected_cost_resid_gbp=-0.0000, expected_co2_resid_kg=+0.0000, expected_pe_resid_kwh=-0.0000), _CorpusExpectation(variant='gshp', block='11a', expected_sap_resid=-0.0000, expected_cost_resid_gbp=-0.0000, expected_co2_resid_kg=+0.0000, expected_pe_resid_kwh=+0.0000), _CorpusExpectation(variant='oil 1', block='11a', expected_sap_resid=-0.0000, expected_cost_resid_gbp=-0.0000, expected_co2_resid_kg=+0.0000, expected_pe_resid_kwh=+0.0000), _CorpusExpectation(variant='oil pcdb 1', block='11a', expected_sap_resid=+0.0000, expected_cost_resid_gbp=+0.0000, expected_co2_resid_kg=-0.0000, expected_pe_resid_kwh=+0.0000), @@ -471,10 +479,6 @@ _BLOCKED_BY_MISSING_MAIN_FUEL_TYPE: tuple[str, ...] = ( 'community heating 3', 'community heating 4', 'community heating 6', - 'electric 11', - 'electric 12', - 'electric 13', - 'electric 14', 'no system', 'oil 2', 'oil 3', diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index eafcd5ee..2eb8bea4 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -4170,6 +4170,22 @@ _ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE: Final[dict[str, int]] = { # Wood Logs — Table 32 code 20 (4.23 / 0.028 / 1.046). Corpus # variant solid fuel 11 (SAP 634). "RWN": 20, + # Electric storage / direct-acting main heating systems — Table 32 + # code 30 (standard electricity; tariff resolved separately from + # `meter_type` per `_rdsap_tariff`). Three EES codes share the + # electricity fuel route: + # WEA — corpus variant electric 11 (SAP 515 = electric warm-air) + # REA — corpus variant electric 12 (SAP 691) + # OEA — corpus variants electric 13 + 14 (SAP 701) + # The §14.0 "Fuel Type" field is absent on these certs (same + # lodging pattern as the solid-fuel block above); the EES code is + # the only fuel discriminator and unambiguously identifies electric + # storage main heating. Fuel cost / CO2 / PE billed via Table 32 + # standard-electricity codes (30 high-rate, 31/33/35/40 low-rate + # per tariff). + "WEA": 30, + "REA": 30, + "OEA": 30, } diff --git a/domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py b/domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py index 5631d3aa..e3ab5477 100644 --- a/domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py @@ -1845,6 +1845,32 @@ def test_section_12_4_4_summer_immersion_applies_to_back_boiler_combos() -> None ) is False +def test_elmhurst_main_heating_ees_maps_electric_storage_codes_to_electricity() -> None: + # Arrange — Elmhurst Summary §14.0 lodges a 3-letter "Main Heating + # EES Code" alongside the Table 4a "Main Heating SAP Code" but does + # NOT lodge a "Fuel Type" string for electric storage main heating + # systems (same pattern as solid-fuel main heating). The mapper's + # EES → fuel-code dict is the only path to derive the fuel. + # + # The corpus carries 4 electric storage variants (electric 11..14) + # spanning EES codes WEA, REA, OEA + SAP codes 515, 691, 701. All + # bill electricity at the lodged tariff (`meter_type='18 Hour'` for + # the entire corpus); Table 32 standard-electricity code 30 is the + # canonical base fuel — the cascade resolves the actual price tier + # (high vs low rate) downstream via `_rdsap_tariff`. + + from datatypes.epc.domain.mapper import ( + _ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE, # pyright: ignore[reportPrivateUsage] + ) + + # Act / Assert — all 3 electric-storage EES codes route to + # standard-electricity Table 32 code 30. + for ees_code in ("WEA", "REA", "OEA"): + assert _ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE[ees_code] == 30, ( + f"EES code {ees_code} should map to electricity (code 30)" + ) + + def test_elmhurst_main_fuel_to_sap10_maps_bulk_lpg_to_api_code_27() -> None: # Arrange — Elmhurst Summary §14.0 / §15.0 lodges "Bulk LPG" as the # fuel type for PCDB LPG-combi certs (corpus variant pcdb 3 lodges