Model/datatypes/epc
Khalim Conn-Kowlessar c848607718 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-05-31 08:59:48 +00:00
..
domain Slice S0380.130: route Elmhurst oil mains via §15.0 Water Heating Fuel Type 2026-05-31 08:59:48 +00:00
loaders demo generated for use in address2uprn 2026-05-08 14:48:15 +00:00
schema Slice S0380.48: surface real-API pv_batteries[].battery_capacity (5 kWh) 2026-05-28 19:17:02 +00:00
search bolstering testing 2026-04-28 13:46:09 +00:00
surveys Slice S0380.102: Wire MEV decentralised cascade into pumps_fans (SAP 10.2 §2.6.4 + Table 4f line 230a) 2026-05-30 15:45:55 +00:00
__init__.py testing out rebaselining 2026-02-12 22:25:03 +00:00
construction_age_band.py testing out rebaselining 2026-02-12 22:25:03 +00:00
efficiency.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
floor.py preparing partiy class 2026-02-05 08:54:27 +00:00
fuel.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
heating_controls.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
hotwater.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
main_heating.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
property_type_built_form.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
roof.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
walls.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
windows.py testing out rebaselining 2026-02-12 22:25:03 +00:00