From 8a96003d45f041266b3478763df11f4100f5c039 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 15 Jul 2026 13:31:33 +0000 Subject: [PATCH] =?UTF-8?q?Include=20the=20C6=20half=20heat-network=20stan?= =?UTF-8?q?ding=20charge=20on=20top=20of=20a=20water-only=20dwelling's=20f?= =?UTF-8?q?uel=20standing=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../sap10_calculator/rdsap/cert_to_inputs.py | 11 ++++ .../rdsap/test_cert_to_inputs.py | 56 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index a1a8af635..d203776e9 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -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. diff --git a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py index fb76456dc..58c1150bc 100644 --- a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py +++ b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py @@ -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