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