Model/backend/documents_parser
Khalim Conn-Kowlessar d793ae8851 Slice S0380.160: SAP 10.2 Table 5a wet-pump gate for central heating gain
SAP 10.2 Table 5a (PDF p.177) row "Central heating pump in heated
space" only applies to mains with a water-loop circulation pump.
Footnote a) names two exclusions verbatim ("Does not apply if a
heating system used solely for domestic hot water. ... Not applicable
for electric heat pumps from database."), and the row's name carries
the implicit third: dry mains with no central heating pump (electric
storage heaters, electric direct-acting, solid-fuel room heaters
without back-boilers) — the row simply doesn't list them.

Pre-slice `internal_gains_from_cert` gated only on Note a) (HP
exclusion), applying `central_heating_pump_w(date_category=...)` to
every non-HP main. The default UNKNOWN-date branch added 7 W of pump
gain to (70)m for every dry-system fixture in the controlled-variable
corpus, even though the worksheet (70)m = 0 every month.

Per-line walk on electric 3 (SAP code 401 "Manual charge control"):

  cascade (73)[Jan] = 640.21 W
  worksheet (73)[Jan] = 633.21 W      delta = +7.00 W
  cascade (70)[Jan] = 7.00 W
  worksheet (70)[Jan] = 0.00 W        Table 5a inapplicable

The +7 W winter-month gain lowered cascade SH demand by ~38 kWh/yr
(cascade 11050 vs worksheet 11088). At Table 32 18-hour low-rate
~7.4 p/kWh that's £2.50/yr under-charging — matching the cluster's
uniform Δcost = -£1.96..-£2.80 pattern. Continuous SAP rose ~+0.10
because cost dominates the ECF.

Fix: new `_any_main_system_has_central_heating_pump(epc)` predicate
in `internal_gains.py`, mirroring `cert_to_inputs._is_wet_boiler_main`
(S0380.149 — Table 4f kWh side). Wet if any non-HP main lodges:
  - sap_main_heating_code in {101-141, 151-161, 191-196} (gas/oil/
    solid-fuel/electric boilers per Table 4a/4b),
  - main_heating_index_number (PCDB Table 322 record),
  - main_heating_category in {1, 2} (RdSAP central heating), OR
  - heat_emitter_type in {1, 3} (radiators / fan-coil per Table 4d).

Dead `_all_main_systems_are_heat_pumps` helper removed (the new
predicate subsumes its role).

Cluster closures (10 variants):
  electric 3:    SAP +0.1215 → -0.0000, cost -£2.80 → -£0.00
  electric 5:    SAP +0.1081 → -0.0000, cost -£2.49 → -£0.00
  electric 6:    SAP +0.1081 → -0.0000, cost -£2.49 → -£0.00
  electric 7:    SAP +0.1017 → -0.0000, cost -£2.34 → -£0.00
  electric 8:    SAP +0.0941 → -0.0000, cost -£2.17 → -£0.00
  electric 9:    SAP +0.1199 → -0.0000, cost -£2.76 → -£0.00
  solid fuel 4:  SAP +0.0850 → -0.0000, cost -£1.96 → -£0.00
  solid fuel 9:  SAP +0.1072 → -0.0000, cost -£2.47 → -£0.00
  solid fuel 10: SAP +0.1134 → +0.0000, cost -£2.61 → -£0.00
  solid fuel 11: SAP +0.0912 → +0.0000, cost -£2.10 → +£0.00

Σ |ΔSAP_c| across 25-variant cohort: 1.24 → 0.18. All 10 cluster
variants now join the lighting-PE +48.66 / CO2 +11.95 deferred
cohort (Elmhurst-vs-spec monthly factor quirk, same shape as
electric 1 + solid fuel 5/6/7/8 from prior closures).

Verbatim spec quote (SAP 10.2 Table 5a row 1, PDF p.177):
  "Central heating pump in heated space, 2013 or later  3 a)"
  "Central heating pump in heated space, 2012 or earlier  10 a)"
  "Central heating pump in heated space, unknown date  7 a)"

The row name ("Central heating pump") gates by construction: dry
systems have no central heating pump and the row's three sub-rows
don't apply.

No regressions on the other 31 variants or any golden fixture; the
6 Elmhurst U985 fixtures lodge PCDB index → the new predicate
returns True → pump_w unchanged.

Tests: 904 pass (+1), 0 fail. Pyright net-zero (35 → 35).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 15:40:25 +00:00
..
handler address JTK review comments 2026-04-20 15:11:17 +00:00
tests Slice S0380.160: SAP 10.2 Table 5a wet-pump gate for central heating gain 2026-06-04 15:40:25 +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.140: §4 cylinder storage loss — extractor picks up §16 thermostat lodging + Table 2b note b restricts ×0.9 to boiler/warm-air/HP systems 2026-05-31 19:03:58 +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