From 9ed003a5031202def40e8fe1df5d7705b97cc730 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 2 Jun 2026 10:22:48 +0000 Subject: [PATCH] =?UTF-8?q?Slice=20S0380.169:=20EES=20"NON"=20=E2=86=92=20?= =?UTF-8?q?electricity=20(no-system=20unblock=20per=20SAP=2010.2=20=C2=A7A?= =?UTF-8?q?.2.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `"NON": 30` to `_ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE` so the mapper can derive the main heating fuel for the Elmhurst "no main heating system" lodging (§14.0 Main Heating EES = NON + SAP code 699 + §14.1 Heating Type = None). SAP 10.2 §A.2.2: "When no main heating system is identified, the calculation is for the assumed system consisting of portable electric heaters." Routes the fuel to Table 32 standard-electricity code 30 (tariff resolved separately from `meter_type` per `_rdsap_tariff`). Pre-slice the cascade raised `MissingMainFuelType` per S0380.132. Post-slice the cascade closes most of the way: no system: ΔSAP_c +1.18, Δcost −£27, ΔCO2 −50, ΔPE −562 The residuals are cascade-side (likely §A.2.2 portable-electric efficiency / responsiveness / control-type defaults differ slightly from Elmhurst) — pinned at observed values as forcing function for follow-up. Moves `no system` out of `_BLOCKED_BY_MISSING_MAIN_FUEL_TYPE` into `_EXPECTATIONS`. Blocked tier now: 5 community-heating variants. Tests: - test_elmhurst_main_heating_ees_maps_no_system_code_to_electricity - corpus pin: no system expected residuals at observed values 916 pass / 0 fail. Co-Authored-By: Claude Opus 4.7 --- .../tests/test_heating_systems_corpus.py | 10 +++++++++- datatypes/epc/domain/mapper.py | 5 +++++ .../rdsap/tests/test_cert_to_inputs.py | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/backend/documents_parser/tests/test_heating_systems_corpus.py b/backend/documents_parser/tests/test_heating_systems_corpus.py index f698741d..f569b4c7 100644 --- a/backend/documents_parser/tests/test_heating_systems_corpus.py +++ b/backend/documents_parser/tests/test_heating_systems_corpus.py @@ -467,6 +467,15 @@ _EXPECTATIONS: tuple[_CorpusExpectation, ...] = ( # EXACT on first try — the cascade was fully wired for the gas/oil/ # LPG path; only the Elmhurst label mapping was missing. _CorpusExpectation(variant='pcdb 3', 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.169 unblocked `no system` (Elmhurst §14.0 Main Heating + # EES = NON + SAP code 699). Per SAP 10.2 §A.2.2 the spec assumes + # portable electric heaters when no main heating is identified; + # cascade routes via `"NON": 30` in the EES → fuel dict (standard + # electricity). Cascade closes most of the way but carries a small + # residual (SAP +1.18, cost −£27 / CO2 −50 / PE −562) — likely a + # cascade-side §A.2.2 efficiency or tariff-routing gap; pinned as + # forcing function for follow-up. + _CorpusExpectation(variant='no system', block='11a', expected_sap_resid=+1.1783, expected_cost_resid_gbp=-27.1485, expected_co2_resid_kg=-49.8272, expected_pe_resid_kwh=-562.4367), ) @@ -491,7 +500,6 @@ _BLOCKED_BY_MISSING_MAIN_FUEL_TYPE: tuple[str, ...] = ( 'community heating 3', 'community heating 4', 'community heating 6', - 'no system', # Slice S0380.133 unblocked all 10 solid-fuel variants via the # §14.0 EES-code-driven fuel derivation; they now appear in # `_EXPECTATIONS` above with their post-derivation residual pins. diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 540b185f..8347ce52 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -4194,6 +4194,11 @@ _ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE: Final[dict[str, int]] = { "WEA": 30, "REA": 30, "OEA": 30, + # "No heating system" lodging — Elmhurst §14.0 Main Heating EES = + # NON + SAP code 699. SAP 10.2 §A.2.2 assumes portable electric + # heaters when no heating system is identified, so the fuel routes + # to standard electricity (code 30). Corpus variant "no system". + "NON": 30, # Bio-liquid main heating fuels — Table 12 / Table 32 codes verbatim # (the bio-liquid Table 32 codes 71/73/75/76 are not collided by any # API enum value, so they pass through `unit_price_p_per_kwh` etc. 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 4a8c3486..436cc3e5 100644 --- a/domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py @@ -1896,6 +1896,23 @@ def test_elmhurst_main_fuel_to_sap10_maps_bio_liquid_water_heating_labels() -> N assert _ELMHURST_MAIN_FUEL_TO_SAP10["Bio-liquid HVO from used cooking oil"] == 71 +def test_elmhurst_main_heating_ees_maps_no_system_code_to_electricity() -> None: + # Arrange — SAP 10.2 §A.2.2 (PDF p.189 area) "When no main heating + # system is identified, the calculation is for the assumed system + # consisting of portable electric heaters." Elmhurst lodges this as + # §14.0 Main Heating EES = "NON" + SAP code 699. The cascade routes + # via the EES → fuel dict (no §14.0 "Fuel Type" string is lodged + # for the "no system" variant — same lodging pattern as the solid- + # fuel and electric-storage blocks). + + from datatypes.epc.domain.mapper import ( + _ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE, # pyright: ignore[reportPrivateUsage] + ) + + # Act / Assert + assert _ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE["NON"] == 30 + + 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