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
parent 589a8631b7
commit 7901dda455
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 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',

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
# 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,
}

View file

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