Include the C6 half heat-network standing charge on top of a water-only dwelling's fuel standing 🟥

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Daniel Roth 2026-07-15 13:31:33 +00:00
parent 70d65291c3
commit 8a96003d45
2 changed files with 67 additions and 0 deletions

View file

@ -2021,6 +2021,17 @@ def _heat_network_standing_charge_gbp(
return None
def _standing_charges_total_gbp(
epc: EpcPropertyData,
main: Optional[MainHeatingDetail],
*,
main_fuel_code: Optional[int],
water_heating_fuel_code: Optional[int],
tariff: Tariff,
) -> float:
raise NotImplementedError
def _main_heating_efficiency(epc: EpcPropertyData) -> float:
"""SAP 10.2 (206) main system 1 efficiency as a 0..1 fraction.

View file

@ -62,6 +62,7 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import (
_heat_network_dlf, # pyright: ignore[reportPrivateUsage]
_heat_network_factor_fuel_code, # pyright: ignore[reportPrivateUsage]
_heat_network_standing_charge_gbp, # pyright: ignore[reportPrivateUsage]
_standing_charges_total_gbp, # pyright: ignore[reportPrivateUsage]
_main_fuel_code, # pyright: ignore[reportPrivateUsage]
_mev_decentralised_kwh_per_yr_from_cert, # pyright: ignore[reportPrivateUsage]
_mev_default_data_iuf, # pyright: ignore[reportPrivateUsage]
@ -9104,6 +9105,61 @@ def test_non_heat_network_main_returns_none_so_caller_uses_fuel_standing() -> No
assert _heat_network_standing_charge_gbp(epc, main) is None
def test_water_only_heat_network_half_charge_adds_to_the_dwellings_fuel_standing() -> None:
# Arrange — SAP 10.2 §C6 (PDF p.54): "Include one-half of the normal heat
# network standing charge in the calculation of fuel costs unless the space
# heating is also a heat network." WHC 950 is a DHW-only heat network, and
# this dwelling's space heating is electric storage (SAP 401) — not a
# network — so the "unless" does not fire and the half charge applies.
#
# C6 says "INCLUDE [...] IN the calculation of fuel costs", in deliberate
# contrast to the full case's "the TOTAL standing charge IS the normal heat
# network standing charge" (next paragraph, p.55). Only the full case is
# phrased as a replacement, so the half is ADDITIVE to whatever Table 12
# note (a) already charges the dwelling for its own fuel. Note (a): "The
# standing charge for off-peak electricity is added to space and water
# heating costs where either main heating or hot water uses off-peak
# electricity" — a Dual meter + SAP 401 is §12 Rule 2 → 7-hour → Table 32
# code 32 = £24/yr.
#
# Half of the RdSAP10 Table 32 code-51 heat-network row (£120/yr) is £60.
# Total £84. Cert 22032926 (22 Gwydyr Mansions) is the corpus instance.
from dataclasses import replace
from domain.sap10_calculator.tables.table_12a import Tariff
base = _typical_semi_detached_epc()
storage_main = MainHeatingDetail(
has_fghrs=False,
main_fuel_type=29, # electricity
heat_emitter_type=0,
emitter_temperature=1,
main_heating_control=2401,
main_heating_category=7, # electric storage heaters
sap_main_heating_code=401,
)
epc = replace(
base,
sap_energy_source=replace(base.sap_energy_source, meter_type="1"), # Dual
sap_heating=make_sap_heating(
main_heating_details=[storage_main],
water_heating_code=950, # DHW-only heat network (boilers)
),
)
# Act
total = _standing_charges_total_gbp(
epc,
storage_main,
main_fuel_code=29,
water_heating_fuel_code=51, # Table 32 heat-network row
tariff=Tariff.SEVEN_HOUR,
)
# Assert — £24 off-peak electric (note (a)) + £60 half network (C6).
assert abs(total - 84.0) <= 1e-9
def test_mev_default_data_iuf_is_table_329_system_type_10_value() -> None:
# Arrange / Act — SAP 10.2 Table 4g note 3 (PDF p.176) directs the
# default SFP to "the appropriate in-use factor for default data from