Slice S0380.169: EES "NON" → electricity (no-system unblock per SAP 10.2 §A.2.2)

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 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-02 10:22:48 +00:00
parent 58a9547210
commit 9ed003a503
3 changed files with 31 additions and 1 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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