Slice S0380.167: EES codes WEA/REA/OEA → electricity (electric storage 11-14 unblock)

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 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-02 09:53:01 +00:00 committed by Jun-te Kim
parent 1491899412
commit a124c2bc68
3 changed files with 50 additions and 4 deletions

View file

@ -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 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 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), _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='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 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), _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 3',
'community heating 4', 'community heating 4',
'community heating 6', 'community heating 6',
'electric 11',
'electric 12',
'electric 13',
'electric 14',
'no system', 'no system',
'oil 2', 'oil 2',
'oil 3', 'oil 3',

View file

@ -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 # Wood Logs — Table 32 code 20 (4.23 / 0.028 / 1.046). Corpus
# variant solid fuel 11 (SAP 634). # variant solid fuel 11 (SAP 634).
"RWN": 20, "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,
} }

View file

@ -1845,6 +1845,32 @@ def test_section_12_4_4_summer_immersion_applies_to_back_boiler_combos() -> None
) is False ) 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: 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 # Arrange — Elmhurst Summary §14.0 / §15.0 lodges "Bulk LPG" as the
# fuel type for PCDB LPG-combi certs (corpus variant pcdb 3 lodges # fuel type for PCDB LPG-combi certs (corpus variant pcdb 3 lodges