From 1ce1a6974bdd9dead03f05a72ee662084d19721f Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 28 May 2026 23:08:37 +0000 Subject: [PATCH] docs: flag deferred HP-on-E7 Table 12a + Table 4f pumps_fans cascade gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cert 000565 reveals a coupling between two SAP 10.2 cascade gaps that prevents an isolated fix to either: 1. `_space_heating_fuel_cost_gbp_per_kwh` applies the E7 low-rate override to any electric main on a Dual meter. Per Table 12a, heat pumps on E7 use a ~33% high / 67% low split (cert 000565 empirically) — NOT 100% low. The current binary all-low/all-high biases space-heating cost £-1.1k / £+1.3k respectively. 2. `_PUMPS_FANS_KWH_BY_MAIN_CATEGORY[4] = 0` for HPs (Table 4f says the circulation pump is in the COP). But certs with MEV / flue fans / solar HW pumps have those components added on top — cert 000565's worksheet pin = 127.5 MEV + 45 flue + 80 solar = 252.5 kWh, none of which the cascade currently sums. Probed a fix that derives `main_heating_category=4` from `sap_main_heating_code in {211-227, 521-527}` (the Table 4a HP rows) and exempts category=4 from the off-peak override. The mapper change is architecturally correct but coupling to (1) + (2) leaves residuals worse at HEAD than at the prior commit — so both edits are reverted and the spec rationale is folded into TODO docstrings on the two helpers: - `_elmhurst_main_heating_category` (mapper) — flags the deferred HP SAP code route + the two cascade prerequisites - `_space_heating_fuel_cost_gbp_per_kwh` (cascade) — flags the Table 12a high/low split as a future cascade slice Cohort regression check: 192 pass + 10 expected 000565 fails — identical baseline to S0380.59. Docs-only, pyright net-zero. Co-Authored-By: Claude Opus 4.7 --- datatypes/epc/domain/mapper.py | 11 ++++++++++- domain/sap10_calculator/rdsap/cert_to_inputs.py | 8 +++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index c663e2fe..d56ffaaa 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -3835,7 +3835,16 @@ def _elmhurst_main_heating_category( membership is the authoritative signal. A PCDB-referenced boiler on mains/LPG gas is category 2 (gas-fired boilers). Other system types fall through to None so the cascade applies its default pumps_fans - 130 kWh/yr until extended.""" + 130 kWh/yr until extended. + + TODO: route Table 4a HP SAP codes (211-227, 521-527) to + category=4 when no PCDB Table 362 record is lodged. Currently + deferred because the correct dispatch needs (a) Table 12a + high/low rate split for HP-on-E7 cost cascade and (b) Table 4f + MEV / flue-fan / solar HW pump components for pumps_fans — + without both, naive category=4 dispatch overshoots cost by + £1.3k and undershoots pumps_fans by 252 kWh on cert 000565. + """ if pcdb_index is not None and heat_pump_record(pcdb_index) is not None: return _ELMHURST_HEATING_CATEGORY_HEAT_PUMP if pcdb_index is not None and mh.fuel_type in _ELMHURST_GAS_BOILER_FUEL_TYPES: diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index bd33cecc..c45932c5 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -757,7 +757,13 @@ def _space_heating_fuel_cost_gbp_per_kwh( """Space heating bills at the main fuel's rate. When the dwelling is on an off-peak tariff (meter_type != standard) AND the main fuel is electricity, bill at the off-peak rate instead. Trusts the cert's - meter_type rather than inferring tariff from heating code.""" + meter_type rather than inferring tariff from heating code. + + TODO: SAP 10.2 Table 12a applies a per-system high/low rate split + rather than the binary all-low / all-high implemented here. For + HP carriers on E7 the split is ~33% high / 67% low (cert 000565 + empirically implies that split); single-rate biases the cost + £±1k vs the worksheet. Table 12a needs its own cascade slice.""" if _is_electric_main(main) and _is_off_peak_meter(meter_type, fuel_is_electric=True): return prices.e7_low_rate_p_per_kwh * _PENCE_TO_GBP return _fuel_cost_gbp_per_kwh(main, prices)