Model/datatypes
Khalim Conn-Kowlessar 35ea664db8 Slice S0380.149: Table 4f — circulation pump dispatch by pump age + wet-boiler gate
SAP 10.2 Table 4f (PDF p.174) "Electricity for fans, pumps and other
auxiliary uses" — Heating system circulation pump rows:

  Circulation pump, 2013 or later                 41 kWh/yr
  Circulation pump, 2012 or earlier              165 kWh/yr
  Circulation pump, unknown date                 115 kWh/yr

Pre-slice the cascade hardcoded `_PUMPS_FANS_KWH_BY_MAIN_CATEGORY[2]
= 160 kWh/yr` (115 Unknown CH + 45 gas flue fan) for category=2 gas
boilers and fell through to `_DEFAULT_PUMPS_FANS_KWH_PER_YR = 130`
for any other category. Both shortcuts ignored the per-cert
`central_heating_pump_age` lodging AND incorrectly applied
circulation pump electricity to dry electric storage / direct-acting
/ room heater systems (no primary water loop).

Implementation:

  - Mapper: `_elmhurst_pump_age_int` now recognises both "Pre 2013"
    and "2012 or earlier" string forms as the SAP10 enum 1 (Pre 2013).
    Pre-slice "2012 or earlier" silently returned 2 (2013 or later)
    on the entire oil corpus, mis-applying the 41 kWh post-2013
    circulation pump to certs that lodge "2012 or earlier" via
    Elmhurst Summary §14 "Heat pump age".
  - New `_is_wet_boiler_main(main)` gate: identifies wet-boiler
    systems by Table 4a/4b code range (101-141 gas/oil, 151-161
    solid fuel, 191-196 electric boilers), PCDB Table 322 record,
    or category ∈ {1, 2} fallback. Heat pumps (cat 4) return False
    per Table 4f note "Not applicable for electric heat pumps from
    database". Electric storage / direct / room heater codes
    (401-499, 601-699) return False — they have no primary loop.
  - New `_table_4f_circulation_pump_kwh(main)` dispatches on
    `central_heating_pump_age`:
        None / 0 → 115 kWh (Unknown date)
        1        → 165 kWh (Pre 2013 / 2012 or earlier)
        2        →  41 kWh (2013 or later)
  - New `_table_4f_main_1_gas_boiler_flue_fan_kwh(main)` extracts
    the gas-flue-fan 45 kWh logic from the old category dispatch.
    Gated on `_is_wet_boiler_main` + gas fuel + fan_flue_present.
  - Remove `_PUMPS_FANS_KWH_BY_MAIN_CATEGORY` and
    `_DEFAULT_PUMPS_FANS_KWH_PER_YR` constants (the new helpers
    replace both).

Worksheet evidence for the wet-boiler gate:

  electric 1 (code 191 electric boiler):   ws (230c) = 41 kWh ✓
  electric 5 (code 402 electric storage):  ws (231)  =  0 kWh ✗
  solid fuel 2 (code 158 anthracite):      ws (230c) = 41 kWh ✓
  solid fuel 9 (code 636 wood stove):      ws (231)  =  0 kWh ✗
  oil 1 (code 127 condensing oil):         ws (230c) = 165 kWh ✓
  oil pcdb 3 (PCDB 18573):                 ws (230c) = 41 kWh ✓

Cascade impact across heating-systems corpus (vs S0380.148 state):

  | Variant        | SAP Δ        | Cause |
  |----------------|--------------|-------|
  | oil 1          | +0.60→+0.40  | 165 + 100 = 265 ≡ worksheet exact |
  | oil pcdb 1/2   | -0.15→+0.36  | 41 + 100 = 141 ≡ ws exact |
  | oil pcdb 3     | +0.59→+0.39  | same |
  | pcdb 1         | -0.03→+0.50  | 41 + 100 = 141 ≡ ws (was over) |
  | electric 1     | -0.06→+0.45  | 41 (wet electric boiler) |
  | electric 3-9   | -0.1..-1.4→  | 0 (dry storage/UFH) |
  |                | +0.5..+0.6   | was 130 default; now 0 |
  | solid fuel 2-8 | various      | 41 (boilers) — partial closures |
  | solid fuel 9-11| -0.2→+0.5    | 0 (room heaters) — was 130 |

Re-pins reflect spec-correct application. Per
[[feedback-software-no-special-handling]]: pre-slice near-zero pins
were masking pre-existing offsetting cascade gaps; spec correctness
unmasks them.

Golden fixtures impact:

  - cert 0240 (dual oil combi, pump_age=0 Unknown): PE +2.52→+2.18
  - cert 0390 (Firebird PCDF oil, pump_age=0): PE -28.08→-28.27
  - cert 6035 (gas combi, pump_age=2 post-2013): PE +47.29→+46.42

Cert 6035 closer to zero (post-2013 41 kWh < pre-slice 115 unknown).
Cert 0240/0390 small shifts from removing the gas-cat-2 hardcoded
160 path for oil mains.

Tests:
  - test_sap_table_4f_circulation_pump_dispatches_per_central_heating_
    pump_age — asserts oil 1 inputs.pumps_fans_kwh_per_yr == 265
    (165 Pre 2013 + 100 liquid fuel) ± 1.0.
  - test_sap_table_4f_liquid_fuel_boiler_flue_fan_and_fuel_pump_adds_
    100_kwh (S0380.148) still passes.

Extended handover suite: 892 pass, 0 fail. Pyright net-improved
(removed unused `main_category` variable, file 33→32 errors).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 09:14:11 +00:00
..
epc Slice S0380.149: Table 4f — circulation pump dispatch by pump age + wet-boiler gate 2026-06-01 09:14:11 +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