mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
fix(cost): HP-DHW from PCDB heat pump bills Table 12a ASHP_APP_N WH split
When DHW is heated by the main heat pump (WHC 901/902/914 = "from main
system") and the main carries a PCDB Table 362 record,
`_hot_water_fuel_cost_gbp_per_kwh` billed the electric HW at 100% off-peak
low rate (its long-standing TODO). SAP 10.2 Table 12a Grid 1 WH column
(PDF p.191) puts HP-DHW on the ASHP/GSHP-from-database row: 0.70
high-rate fraction at 7-hour and 10-hour → 0.70×14.68 + 0.30×7.50 =
12.526 p/kWh (10-hour), not 7.50 p. The low-rate collapse over-credited
the cat-4 HP-DHW cluster.
Fix: pass the cert WHC into the helper and, for HP-DHW (WHC ∈ {901,902,
914} + PCDB-HP main), bill at the ASHP_APP_N WH blended rate. Electric
IMMERSION (WHC 903) is a different Table 12a row (off-peak immersion 0.17
/ Table 13) and stays on the 100%-low-rate fallback until that slice
lands.
cat-4 cluster (20 certs): mean|err| 2.43→2.11, mean signed +0.06→-0.52
(now per-cert scatter, no systematic bias); cert 9472 +6.4→+3.2, 2789
+6.8→+4.0, 4135 +2.7→within 0.5. Headline mean|err| 1.727→1.720.
Regression green (2447 pass incl. golden 6035 + ASHP cohort at 1e-4);
pyright net-zero.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e41a0bc0d7
commit
2bc73fb08d
2 changed files with 67 additions and 2 deletions
|
|
@ -103,6 +103,7 @@ from domain.sap10_calculator.tables.table_12a import (
|
|||
rdsap_tariff_for_cert,
|
||||
space_heating_high_rate_fraction,
|
||||
tariff_from_meter_type,
|
||||
water_heating_high_rate_fraction,
|
||||
)
|
||||
from domain.sap10_calculator.tables.table_32 import (
|
||||
additional_standing_charges_gbp,
|
||||
|
|
@ -2231,6 +2232,7 @@ def _hot_water_fuel_cost_gbp_per_kwh(
|
|||
tariff: Tariff,
|
||||
prices: PriceTable,
|
||||
*,
|
||||
water_heating_code: Optional[int] = None,
|
||||
inherit_main_for_community_heating: bool = False,
|
||||
) -> float:
|
||||
"""Hot water bills at the *water-heating* fuel's rate. When the
|
||||
|
|
@ -2239,8 +2241,16 @@ def _hot_water_fuel_cost_gbp_per_kwh(
|
|||
water fuel is a non-electric fuel (gas / oil / LPG), tariff is
|
||||
not consulted — those fuels are single-rate per Table 32. For
|
||||
cert 000565 HW routes to gas combi via WHC 914 → tariff branch
|
||||
not taken. TODO: Table 12a Grid 1 WH high-rate-fraction split for
|
||||
electric WH on off-peak (currently uses 100% low rate).
|
||||
not taken.
|
||||
|
||||
HP-DHW exception: when DHW is heated by the main system (WHC
|
||||
∈ {901, 902, 914}) and that main is a PCDB Table 362 heat pump, the
|
||||
HW bills per SAP 10.2 Table 12a Grid 1 WH column (PDF p.191) — the
|
||||
ASHP/GSHP-from-database row carries a 0.70 high-rate fraction at
|
||||
7-hour and 10-hour, NOT 100% off-peak low rate. Electric IMMERSION
|
||||
(WHC 903) is a different Table 12a row (off-peak immersion 0.17 /
|
||||
Table 13) and stays on the 100%-low-rate fallback until that slice
|
||||
lands.
|
||||
|
||||
`inherit_main_for_community_heating`: per S0380.173, when WHC
|
||||
∈ {901, 902, 914} AND main is a heat network, ignore the cert-
|
||||
|
|
@ -2253,6 +2263,18 @@ def _hot_water_fuel_cost_gbp_per_kwh(
|
|||
return _fuel_cost_gbp_per_kwh(main, prices)
|
||||
water_electric = _is_electric_water(water_heating_fuel)
|
||||
if water_electric and tariff is not Tariff.STANDARD:
|
||||
if (
|
||||
water_heating_code in _WATER_INHERIT_FROM_MAIN_CODES
|
||||
and main is not None
|
||||
and main.main_heating_index_number is not None
|
||||
and heat_pump_record(main.main_heating_index_number) is not None
|
||||
):
|
||||
high_rate, low_rate = _tariff_high_low_rates_p_per_kwh(tariff)
|
||||
high_frac = water_heating_high_rate_fraction(
|
||||
Table12aSystem.ASHP_APP_N, tariff
|
||||
)
|
||||
blended = high_frac * high_rate + (1.0 - high_frac) * low_rate
|
||||
return blended * _PENCE_TO_GBP
|
||||
return _off_peak_low_rate_gbp_per_kwh(tariff)
|
||||
if water_heating_fuel is not None:
|
||||
return prices.unit_price_p_per_kwh(water_heating_fuel) * _PENCE_TO_GBP
|
||||
|
|
@ -6963,6 +6985,7 @@ def cert_to_inputs(
|
|||
_water_heating_main(epc),
|
||||
_rdsap_tariff(epc),
|
||||
prices,
|
||||
water_heating_code=epc.sap_heating.water_heating_code,
|
||||
inherit_main_for_community_heating=_community_hw_inherit,
|
||||
)
|
||||
hw_co2_factor = _hot_water_co2_factor_kg_per_kwh(
|
||||
|
|
|
|||
|
|
@ -3212,6 +3212,48 @@ def test_space_heating_electric_room_heater_off_peak_bills_at_direct_acting_high
|
|||
assert abs(gas_rate - 0.0550) > 1e-6
|
||||
|
||||
|
||||
def test_hot_water_from_pcdb_heat_pump_bills_at_app_n_wh_high_rate() -> None:
|
||||
# Arrange — when DHW is heated by the main heat pump (WHC 901/902/914
|
||||
# "from main system") and that main carries a PCDB Table 362 record,
|
||||
# SAP 10.2 Table 12a Grid 1 WH column (PDF p.191) bills it on the
|
||||
# ASHP/GSHP-from-database row: 0.70 high-rate fraction at 7-hour and
|
||||
# 10-hour. `_hot_water_fuel_cost_gbp_per_kwh` previously billed any
|
||||
# electric off-peak HW at 100% low rate (its TODO), over-crediting the
|
||||
# HP-DHW cat-4 cluster. Electric IMMERSION (WHC 903) is a different
|
||||
# Table 12a row (off-peak immersion 0.17 / Table 13) and must stay on
|
||||
# the 100%-low-rate fallback here.
|
||||
from domain.sap10_calculator.tables.table_12a import Tariff
|
||||
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||
_hot_water_fuel_cost_gbp_per_kwh, # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
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,
|
||||
sap_main_heating_code=None,
|
||||
main_heating_index_number=104351, # PCDB Table 362 heat pump
|
||||
)
|
||||
|
||||
# Act — DHW from the main HP (WHC 901) vs a separate electric
|
||||
# immersion (WHC 903), both on a 10-hour off-peak tariff.
|
||||
rate_from_hp = _hot_water_fuel_cost_gbp_per_kwh(
|
||||
29, pcdb_heat_pump_main, Tariff.TEN_HOUR, SAP_10_2_SPEC_PRICES,
|
||||
water_heating_code=901,
|
||||
)
|
||||
rate_immersion = _hot_water_fuel_cost_gbp_per_kwh(
|
||||
29, pcdb_heat_pump_main, Tariff.TEN_HOUR, SAP_10_2_SPEC_PRICES,
|
||||
water_heating_code=903,
|
||||
)
|
||||
|
||||
# Assert — HP-DHW: 0.70 × 14.68 p + 0.30 × 7.50 p = 12.526 p; immersion
|
||||
# stays at the 10-hour low rate 7.50 p (£0.0750).
|
||||
assert abs(rate_from_hp - 0.12526) <= 1e-6
|
||||
assert abs(rate_immersion - 0.0750) <= 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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue