From d10b5210c68fd1695cd8c9b4868b257a45542ff4 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 15 Jul 2026 10:27:54 +0000 Subject: [PATCH] =?UTF-8?q?Accrue=20Table=203=20primary-circuit=20loss=20f?= =?UTF-8?q?or=20water-only=20heat-network=20cylinders=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) --- .../rdsap/test_cert_to_inputs.py | 50 +++++++++++++++++++ 1 file changed, 50 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 f454eddd0..551e09d7d 100644 --- a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py +++ b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py @@ -1561,6 +1561,56 @@ def test_water_only_heat_network_dhw_fuel_independent_of_plant_type() -> None: assert heat_pump == pytest.approx(boilers, abs=1e-6) +def test_water_only_heat_network_cylinder_accrues_table_3_primary_loss() -> None: + # Arrange — SAP 10.2 §4.3 (PDF p.24) scopes itself "Where hot water is + # provided by a heat network", then requires: "Primary circuit loss for + # insulated pipework and cylinderstat should be included (see Table 3)." + # A water-ONLY network (WHC 950) feeds the dwelling-side cylinder via + # primary pipework from the network connection, so the loss applies on + # the DHW source alone — whatever the SPACE main is. + # + # The space main here is warm air (Table 4a code 502, category 9), + # mirroring cert 9093 (whc 950 + warm-air main). That main is not a + # boiler, so no boiler-category branch can accrue the loss on its + # behalf: without a WHC-keyed gate the cylinder silently gets (59)m = 0. + part = make_building_part(construction_age_band="E") + warm_air_main = MainHeatingDetail( + has_fghrs=False, + main_fuel_type=26, + heat_emitter_type=1, + emitter_temperature=1, + main_heating_control=2106, + main_heating_category=9, + sap_main_heating_code=502, + ) + epc = make_minimal_sap10_epc( + total_floor_area_m2=_TYPICAL_TFA_M2, + habitable_rooms_count=4, + country_code="ENG", + has_hot_water_cylinder=True, + sap_building_parts=[part], + sap_heating=make_sap_heating( + main_heating_details=[warm_air_main], + water_heating_code=950, # hot-water-only heat network + cylinder_size=2, # Normal / 110 L + cylinder_thermostat="Y", + ), + ) + + # Act + wh_result, _ = _water_heating_worksheet_and_gains( + epc=epc, + water_efficiency_pct=100.0, + is_instantaneous=False, + primary_age="E", + pcdb_record=None, + ) + + # Assert — the primary circuit runs, so (59)m is billed every month. + assert wh_result is not None + assert all(m > 0.0 for m in wh_result.primary_loss_monthly_kwh) + + 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