Slice S0380.24: SAP code 631 → house coal secondary fuel — closes cert 2102 -15.81 → +5e-5

Per SAP 10.2 spec page 165 Table 4a Category 10 (Room heaters), the
600-range secondary-heating SAP codes split by fuel:
  601-613: Gas (mains gas / LPG / biogas) — column A is mains gas.
  621-625: Liquid fuel room heaters (oil / bioethanol).
  631-634: Solid fuel room heaters (open fire, closed room heater
           with/without boiler) — house coal is the modal default.
  691-699: Electric room heaters.

`_elmhurst_secondary_fuel_from_sap_code` previously mapped the entire
601-630 range to mains gas (API code 26). Two bugs:
  1. Codes 621-625 are oil heaters, not gas. (Cohort hasn't surfaced
     an oil-secondary cert yet — deferred until a fixture exercises.)
  2. Codes 631-634 are solid fuel, not gas, and weren't in the range
     at all. Cascade fell through to the secondary-fuel-None default
     (standard electricity at 13.19 p/kWh), over-charging cert 2102's
     "Open fire in grate" secondary by ~£340/yr.

Narrow the gas range to 601-613 (per the spec) and add 631-634 → API
fuel code 11 (Coal in `_ELMHURST_MAIN_FUEL_TO_SAP10`) → Table 32
direct lookup returns 3.67 p/kWh (house coal), matching worksheet
(242) "Space heating - secondary 3585.2401 × 3.6700 = 131.58".

Cohort-2 outcome (38 certs, Summary path):
  exact (<1e-4): 20 → **21**  (+1: cert 2102 -15.81 → +5e-5)
  ±5+:           1 → **0**    (last big-gap closed)

Cert 2102 verified end-to-end:
  - secondary_heating_type=631 → secondary_fuel_type=11 → 3.67 p/kWh
  - Cascade SAP 63.8732 vs worksheet 63.8732 (delta +5e-5)
  - Cascade total fuel cost £787.03 = worksheet £787.03 exactly

Pyright net-zero on both touched files (mapper.py 32→32, test 0→0).

Tests: 703 → 704 pass (+1 new SAP-code-631 secondary-fuel routing
test), 10 expected fails unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-28 09:46:44 +00:00 committed by Jun-te Kim
parent 9a091234cf
commit 5402dd17e1
2 changed files with 49 additions and 5 deletions

View file

@ -313,6 +313,33 @@ def test_summary_001479_secondary_heating_routes_mains_gas_fuel() -> None:
assert epc.sap_heating.secondary_fuel_type == 26
def test_summary_2102_secondary_heating_routes_house_coal_for_open_fire() -> None:
# Arrange — cohort-2 cert 2102-3018-0205-7886-5204 §14.1 lodges
# "Secondary Heating Code: SAP code 631" — "Open fire in grate"
# per SAP 10.2 Table 4a Category 10 (Room heaters), solid fuel
# column. Without the per-code routing the cascade defaults to
# standard electricity at 13.19 p/kWh and over-charges secondary
# heating by ~£340/yr, pushing SAP -15.81 below the worksheet's
# 63.87. Worksheet line (242) "Space heating - secondary 3585.24
# × 3.6700 = 131.58" confirms house-coal pricing (Table 32 fuel
# code 11 = 3.67 p/kWh).
cert_dir = Path(
"sap worksheets/additional with api 2/2102-3018-0205-7886-5204"
)
summary_pdf = next(cert_dir.glob("Summary_*.pdf"))
pages = _summary_pdf_to_textract_style_pages(summary_pdf)
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
# Act
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
# Assert
assert epc.sap_heating.secondary_heating_type == 631
# 11 = "Coal" in `_ELMHURST_MAIN_FUEL_TO_SAP10` → Table 32 lookup
# returns 3.67 p/kWh (house coal).
assert epc.sap_heating.secondary_fuel_type == 11
def test_summary_9501_flat_has_no_built_form_in_summary_pdf() -> None:
# Arrange — cert 9501 (Summary_000784.pdf) is a flat. The Elmhurst
# Summary's §1.0 "Property type" section lodges the built-form

View file

@ -3350,14 +3350,31 @@ def _elmhurst_secondary_fuel_from_sap_code(
fitting live effect gas fire") but not the fuel int separately; the
cascade's `_secondary_fuel_cost_gbp_per_kwh` defaults to standard
electricity when `secondary_fuel_type` is None correct for the
portable-electric default but wrong for cert 001479's mains-gas fire.
Returns 26 (mains gas) for SAP codes in the 600-630 range; None for
other codes (cascade default fires, matching cohort 000490 SAP code
691 electric panel)."""
portable-electric default but wrong for fuel-fired room heaters.
SAP 10.2 Table 4a Category 10 ("Room heaters") code blocks:
601-613: Gas (mains gas / LPG / biogas) column A is mains gas;
column B for LPG. Cohort default is mains gas
(`_ELMHURST_MAIN_FUEL_TO_SAP10["Mains gas"] = 26`).
621-625: Liquid fuel room heaters (oil / bioethanol). Cohort
not yet exercised; deferred until a fixture surfaces.
631-634: Solid fuel room heaters (open fire, closed room
heater with/without boiler). House coal is the modal
default per Table 12 secondary rate (3.67 p/kWh).
691-699: Electric room heaters. Cascade default (None) routes
to standard electricity (13.19 p/kWh).
Cohort cert 2102-3018-0205-7886-5204 surfaces the 631 ("Open fire
in grate") path — pre-slice the cascade defaulted to electricity
at 13.19 p/kWh, over-charging secondary by ~£340/yr and pushing
SAP -15.81 below the worksheet's 63.87.
"""
if sap_code is None:
return None
if 601 <= sap_code <= 630:
if 601 <= sap_code <= 613:
return 26 # Mains gas, matching `_ELMHURST_MAIN_FUEL_TO_SAP10`
if 631 <= sap_code <= 634:
return 11 # House coal (Coal in `_ELMHURST_MAIN_FUEL_TO_SAP10`)
return None