mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
fix(cost): PCDB heat pump without SAP code bills Table 12a ASHP_APP_N split
A heat pump that resolves via its PCDB Table 362 index alone (API path, data_source=1, no Table-4a SAP code) had sap_main_heating_code=None, so `_table_12a_system_for_main` fell through the 211-227/521-524 code-range gate to None → the "100% off-peak low-rate" fallback. On a Dual meter (RdSAP §12 Rule 3 routes heat pumps to the 10-hour tariff) this billed space heating at 7.50 p/kWh instead of the SAP 10.2 Table 12a Grid 1 (PDF p.191) ASHP/GSHP-from-database row: 0.80 high-rate fraction → 0.80×14.68 + 0.20×7.50 = 13.244 p/kWh. The collapse over-credited the whole cat-4 heat-pump cluster. Fix: route any main with a PCDB heat-pump record to ASHP_APP_N regardless of SAP code (a Table 362 record IS an Appendix-N heat pump by definition). ASHP_APP_N and GSHP_APP_N share the 0.80 SH fraction at 7h/10h, so ASHP_APP_N is the canonical Appendix-N row for the SH split. cat-4 cluster (20 certs): within-0.5 45%→50%, mean signed +1.43→+0.06, mean|err| 3.81→2.43; cert 9472 +15.0→+6.4, 2789 +13.4→+6.8. Headline 45.0%→45.1%, mean|err| 1.757→1.727. Regression green (only the pre-existing test_total_floor_area fails); pyright net-zero. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fb350036b1
commit
e41a0bc0d7
2 changed files with 51 additions and 1 deletions
|
|
@ -2140,12 +2140,27 @@ def _table_12a_system_for_main(
|
|||
# all callers already pre-gate on electric, this is belt-and-braces.
|
||||
if main.main_heating_category == 10 and _is_electric_main(main):
|
||||
return Table12aSystem.OTHER_DIRECT_ACTING_ELECTRIC
|
||||
# A PCDB Table 362 record IS a heat pump by definition (the Appendix-N
|
||||
# efficiency cascade keys off it), whether or not a Table-4a SAP code
|
||||
# (211-227 / 521-524) was ALSO lodged. API-path heat pumps resolve via
|
||||
# the PCDB index alone (data_source=1, sap_main_heating_code None), so
|
||||
# the code-range gate below misses them and they fell through to None
|
||||
# → the "100% off-peak low-rate" fallback, OVER-crediting the cat-4
|
||||
# cluster on Dual meters (cert 9472 +15.0 SAP). Route any PCDB heat
|
||||
# pump to ASHP_APP_N: SAP 10.2 Table 12a Grid 1 (PDF p.191) gives the
|
||||
# ASHP/GSHP Appendix-N rows the same 0.80 SH high-rate fraction at
|
||||
# 7-hour and 10-hour, so ASHP_APP_N is the canonical Appendix-N row
|
||||
# for the space-heating cost split.
|
||||
if has_pcdb_hp:
|
||||
return Table12aSystem.ASHP_APP_N
|
||||
# ASHP — Table 4a rows 211-217 (earlier generations) + 221-227
|
||||
# (2013+) cover the air-source space. Warm-air ASHPs are 521-524.
|
||||
# Reached only when no PCDB record is present (handled above), so the
|
||||
# "from database" variant never applies here → ASHP_OTHER.
|
||||
if code is not None and (
|
||||
211 <= code <= 217 or 221 <= code <= 227 or 521 <= code <= 524
|
||||
):
|
||||
return Table12aSystem.ASHP_APP_N if has_pcdb_hp else Table12aSystem.ASHP_OTHER
|
||||
return Table12aSystem.ASHP_OTHER
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3212,6 +3212,41 @@ def test_space_heating_electric_room_heater_off_peak_bills_at_direct_acting_high
|
|||
assert abs(gas_rate - 0.0550) > 1e-6
|
||||
|
||||
|
||||
def test_space_heating_pcdb_heat_pump_without_sap_code_bills_at_app_n_high_rate() -> None:
|
||||
# Arrange — an API-path heat pump resolves via its PCDB Table 362
|
||||
# index alone (data_source=1, no Table-4a SAP code lodged), so
|
||||
# `sap_main_heating_code` is None. SAP 10.2 Table 12a Grid 1 (PDF
|
||||
# p.191) puts an Appendix-N heat pump on the ASHP/GSHP "from database"
|
||||
# row: SH high-rate fraction 0.80 at both 7-hour and 10-hour. The
|
||||
# code-range gate in `_table_12a_system_for_main` (211-227 / 521-524)
|
||||
# missed the PCDB-only heat pump, so it fell through to the "100%
|
||||
# low-rate" fallback (10-hour low 7.50 p, £0.0750), under-charging
|
||||
# space heating by ~5.74 p/kWh and OVER-rating the cat-4 heat-pump
|
||||
# cluster (1,000-cert API sample: 20 certs, mean signed +1.43; cert
|
||||
# 9472 +15.0). The fix routes any main with a PCDB heat-pump record
|
||||
# to ASHP_APP_N regardless of SAP code. Mirror of the cat-10 room-
|
||||
# heater fix above.
|
||||
from domain.sap10_calculator.tables.table_12a import Tariff
|
||||
pcdb_heat_pump_main = MainHeatingDetail(
|
||||
has_fghrs=False,
|
||||
main_fuel_type=29, # electricity (heat pump), API enum
|
||||
heat_emitter_type=1,
|
||||
emitter_temperature=0,
|
||||
main_heating_control=2210,
|
||||
main_heating_category=4, # heat pump
|
||||
sap_main_heating_code=None, # API path: PCDB index only, no SAP code
|
||||
main_heating_index_number=104351, # Vaillant aroTHERM, PCDB Table 362
|
||||
)
|
||||
|
||||
# Act — 10-hour off-peak tariff (RdSAP §12 Rule 3 routes heat pumps here).
|
||||
rate_ten_hour = _space_heating_fuel_cost_gbp_per_kwh(
|
||||
pcdb_heat_pump_main, Tariff.TEN_HOUR, prices=SAP_10_2_SPEC_PRICES,
|
||||
)
|
||||
|
||||
# Assert — ASHP_APP_N 10-hour: 0.80 × 14.68 p + 0.20 × 7.50 p = 13.244 p.
|
||||
assert abs(rate_ten_hour - 0.13244) <= 1e-6
|
||||
|
||||
|
||||
def test_heat_network_dlf_full_table_12c_age_band_coverage() -> None:
|
||||
# Arrange — SAP 10.2 Table 12c (page 193) heat-network Distribution
|
||||
# Loss Factor by dwelling age band A..M. None → K-or-newer
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue