Model/datatypes
Khalim Conn-Kowlessar c28b061cfb Slice S0380.130: route Elmhurst oil mains via §15.0 Water Heating Fuel Type
Elmhurst Summary §14.0 Main Heating1 leaves "Fuel Type" empty for
Table 4b liquid-fuel boilers (heating oil / HVO / FAME / B30K /
bioethanol — SAP codes 120-141). Unlike gas boilers (codes 101-119)
where Elmhurst explicitly lodges "Mains gas", liquid-fuel boilers
take the fuel from §15.0 "Water Heating Fuel Type" since the same
boiler heats space + water.

Pre-slice:
  - `_elmhurst_main_fuel_int(mh.fuel_type)` returned None for the
    empty §14.0 fuel string.
  - The electric-SAP-code inference (`_ELECTRIC_SAP_MAIN_HEATING_CODES`)
    didn't fire because SAP 127 is a Table 4b oil boiler, not electric.
  - `main_fuel_type` fell through to the raw empty string.
  - `cert_to_inputs._main_fuel_code` returned None.
  - `table_32.unit_price_p_per_kwh(None)` defaulted to mains gas
    (3.48 p/kWh).
  - The cascade therefore priced ~13.7k kWh/yr of oil space + water
    heating at the gas tariff — a 56% under-count vs the worksheet's
    Table 32 oil rate.

Two complementary fixes:

1. Add "Heating oil" → 28 ("oil (not community)" per epc_codes.csv
   row main_fuel,28) to `_ELMHURST_MAIN_FUEL_TO_SAP10`. The existing
   `API_FUEL_TO_TABLE_32` then routes API 28 → Table 32 code 4
   (heating oil — 7.64 p/kWh / 0.298 kg CO2/kWh / 1.180 PE factor
   per RdSAP 10 spec p.95). This fix handles pcdb 1 directly because
   pcdb 1 lodges §14.0 "Fuel Type: Heating oil" explicitly.

2. Thread a §15.0-fuel fallback for the main_fuel inference: when
   `mh.fuel_type` is empty AND `mh.main_heating_sap_code` is in the
   Table 4b liquid-fuel range (120-141 per SAP 10.2 Table 4b
   "Seasonal efficiency for gas and liquid fuel boilers"), use the
   §15.0 water_heating_fuel as the main fuel too. Gated on the SAP
   code range so this can't accidentally fire on solid-fuel-mains
   + electric-HW certs (where §15.0 lodges "Electricity" for the
   immersion but the SH fuel is the solid fuel implicit in the SAP
   code). This fix handles oil 1 + oil pcdb 1/2/3 (where §14.0 is
   silent but §15.0 lodges "Heating oil").

Residual shifts at HEAD post-slice (5 variants legitimately re-pinned):

  oil 1       +13.67 SAP → -9.70 SAP (cascade now over-counts at the
                          spec's 7.64 p/kWh — vs worksheet's 5.44)
  oil pcdb 1/2 +11.17 → -11.63
  oil pcdb 3  +11.87 → -10.87
  pcdb 1      +21.90 → -9.41

Remaining negative residuals are the price-spec-vs-worksheet gap
queued for slice S0380.131 (5.44 vs 7.64 p/kWh oil). The mapper now
correctly identifies the fuel; what's left is the cascade tariff.

The other 36 corpus variants are unchanged — restricting the §15.0
fallback to SAP 120-141 keeps solid-fuel-mains and electric-mains
certs at their existing pins.

Extended handover suite at HEAD post-slice: **874 pass, 0 fail**
(was 873 + 1 new AAA test).

Pyright net-zero on touched files (45 → 45 — pre-existing errors
unrelated).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 16:28:48 +00:00
..
epc Slice S0380.130: route Elmhurst oil mains via §15.0 Water Heating Fuel Type 2026-06-01 16:28:48 +00:00
magicplan typing and renaming 🟪 2026-05-07 13:26:49 +00:00
__init__.py further breaking up code 2023-07-20 12:24:34 +01:00
datatypes.py further breaking up code 2023-07-20 12:24:34 +01:00
enums.py completed build of new demo portfolio - some fixed required still 2023-11-20 14:42:16 +00:00