From 5f3e9f893fc496c8a57575568642db44a697382f Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 15 Jul 2026 10:25:59 +0000 Subject: [PATCH] =?UTF-8?q?Bill=20water-only=20heat-network=20DHW=20identi?= =?UTF-8?q?cally=20whatever=20plant=20is=20behind=20it=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 950 boilers / 951 CHP / 952 community heat pump differ only in plant efficiency, which SAP 10.2 §4.3 puts in the Table 12 heat price — so the dwelling's delivered-heat demand must not vary with it. Passes on the previous commit's expression; verified RED against the prior impl, where 951 leaked 0.80/0.75 and 952 under-billed its DHW fuel by 3.15×. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../rdsap/test_cert_to_inputs.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) 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 85494bbfd..f454eddd0 100644 --- a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py +++ b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py @@ -1510,6 +1510,57 @@ def test_water_only_heat_network_dhw_prices_heat_without_plant_efficiency() -> N assert abs(hw_fuel / wh_result.output_kwh_per_yr - 1.41 * 1.05) <= 1e-6 +def test_water_only_heat_network_dhw_fuel_independent_of_plant_type() -> None: + # Arrange — the three water-only heat-network codes differ ONLY in the + # plant behind the network: 950 boilers (Table 4a eff 0.80), 951 CHP + # (0.75), 952 community heat pump (3.00). Because SAP 10.2 §4.3 (p.24) + # puts that efficiency in the Table 12 price of delivered heat, the + # DWELLING's delivered-heat demand cannot depend on it: worksheet (310) + # is "(64) × (305a) × (306)" whatever the plant is. C6 (p.54) likewise + # gives one rule for a DHW-only network, and the Table 4c(3) (p.169) + # "DHW-only system" rows make no 950/951/952 distinction. + # + # So all three must bill identical HW fuel. The plant efficiency still + # matters — but at (342)/(373)/(473), the network's CO2/PE factors (C4, + # p.53) — never as a dwelling-side divisor. + # + # This is the sharpest statement of the double-count fix: under the old + # `water_eff = plant_eff / DLF` the three diverged by their plant + # efficiencies, and 952 under-billed its DHW fuel by ~3× (0.47 × q_useful + # against the spec's 1.4805 × q_useful). + part = make_building_part(construction_age_band="E") + gas_main = MainHeatingDetail( + has_fghrs=False, + main_fuel_type=26, + heat_emitter_type=1, + emitter_temperature=1, + main_heating_control=2106, + main_heating_category=2, + ) + + def hw_fuel_for(water_heating_code: int) -> float: + epc = make_minimal_sap10_epc( + total_floor_area_m2=_TYPICAL_TFA_M2, + habitable_rooms_count=4, + country_code="ENG", + sap_building_parts=[part], + sap_heating=make_sap_heating( + main_heating_details=[gas_main], + water_heating_code=water_heating_code, + ), + ) + return cert_to_inputs(epc).hot_water_kwh_per_yr + + # Act — boilers / CHP / community heat pump behind the same network. + boilers = hw_fuel_for(950) + chp = hw_fuel_for(951) + heat_pump = hw_fuel_for(952) + + # Assert — the plant behind the network never reaches the dwelling. + assert chp == pytest.approx(boilers, abs=1e-6) + assert heat_pump == pytest.approx(boilers, abs=1e-6) + + def test_gas_boiler_main_efficiency_unchanged_by_dlf_override() -> None: # Arrange — regression check: the DLF override only fires for heat- # network main heating. A standard gas boiler (cat=2, code=102) must