Model/backend/documents_parser
Khalim Conn-Kowlessar 4d004790db Slice S0380.136: route _is_electric_main / _is_electric_water via the canonical T32-first normaliser (dual-fuel closure)
`_is_electric_main` and `_is_electric_water` hand-rolled a literal set
check `code in {10, 25, 29}` ∪ `{30..40}` to classify a fuel code as
electricity. The set conflated two enums:

  - {10, 25, 29}    — API enum codes (epc_codes.csv row main_fuel):
                        10 = electricity (backwards compat)
                        25 = electricity (community)
                        29 = electricity (not community)
  - {30, 31, ..., 40} — Table 32 codes (RdSAP 10 spec p.95):
                          30 = standard tariff
                          31/32 = 7-hour low/high
                          33/34 = 10-hour low/high
                          35 = 24-hour heating
                          38/40 = 18-hour high/low

API enum codes 1-29 collide with Table 32 codes 1-29 for unrelated
fuels — API 10 = "electricity" vs Table 32 10 = "dual fuel (mineral +
wood)". S0380.135's EES dispatch sets `main_fuel_type` to Table 32
codes (BDI → 10 for dual fuel), so a dual-fuel main was silently
mis-classified as electric. The `_space_heating_fuel_cost_gbp_per_kwh`
tariff branch then re-routed solid fuel 6's space heating cost through
the 18-hour-low electric rate (5.50 p/kWh) instead of dual-fuel 3.99
p/kWh — solid fuel 6 SAP residual −7.38 → −11.37 in S0380.135.

The fix promotes the existing `table_32._is_electric_code` to public
`is_electric_fuel_code` and routes both `_is_electric_main` and
`_is_electric_water` through it. The canonical helper normalises a
fuel code via T32-first then API-translate fallback (same convention
as `unit_price_p_per_kwh`), so a Table-32-code-10 dual-fuel main
classifies as non-electric correctly.

Subtle behaviour change: API enum code 25 ("electricity (community)")
maps via API_FUEL_TO_TABLE_32 to Table 32 code 41 ("heat from electric
heat pump (community)") which is a heat network billed at the heat-
network rate (4.24 p/kWh single rate), not at the off-peak electric
tariff. Pre-S0380.136 the literal-set check would have treated this
as direct electric and applied the Table 12a high/low-rate split —
that was wrong; community heat networks don't have an off-peak split.
The new canonical helper correctly excludes code 41 from
_ELECTRIC_FUEL_CODES.

Heating-systems corpus impact:

  solid fuel 6 (Dual Fuel Anthracite Wood, SAP 160):
    ΔSAP  −11.3731 → +1.9493   (now in cluster with other solid-fuel)
    Δcost +£268.44 → −£44.91
    ΔPE   unchanged (PE wasn't affected by the cost mis-routing)

No other corpus variants moved — none have `main_fuel_type` in the
ambiguous API/T32 collision range that was previously mis-classified.

Extended handover suite: 879 pass / 0 fail (+2 from new AAA tests
covering both `_is_electric_main` and `_is_electric_water` dual-fuel
non-electric classification + API code 29 → electric / API code 25 →
heat-network non-electric semantics).

Pyright net-zero on touched files (43 → 43).

No golden fixture impact — no golden cert lodges `main_fuel_type=10`
(dual fuel) on the cascade path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 13:37:14 +00:00
..
handler address JTK review comments 2026-04-20 15:11:17 +00:00
tests Slice S0380.136: route _is_electric_main / _is_electric_water via the canonical T32-first normaliser (dual-fuel closure) 2026-05-31 13:37:14 +00:00
__init__.py Map to RdSapSiteNotes from site notes JSON 🟥 2026-04-16 13:54:03 +00:00
db_writer.py include updating epc_property_data to pashub to ara workflow 2026-04-29 09:55:14 +00:00
elmhurst_extractor.py Slice S0380.133: derive solid-fuel main fuel from §14.0 EES Code 2026-05-31 10:04:28 +00:00
extractor.py Handle wall thickness "Unmeasurable" 🟩 2026-04-30 16:41:16 +00:00
local_runner.py update local runner to work for elmhurst 2026-04-24 14:01:36 +00:00
parser.py load ecmk site notes to db 2026-04-29 11:20:47 +00:00
pdf.py update local runner to work for elmhurst 2026-04-24 14:01:36 +00:00