From 5d4b55d7f99304f0b533a1a07b8a44f29560c3af Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 4 Jun 2026 17:44:11 +0000 Subject: [PATCH] S0380.227: dedicated DHW-only system is not separately timed (Table 2b note b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SAP 10.2 Table 2b note b (PDF p.159) applies the ×0.9 temperature-factor reduction only when DHW is "separately timed" relative to space heating on a SHARED heat generator ("boiler systems, warm air systems and heat pump systems"). Per RdSAP 10 §10.5.1 (PDF p.55) a separate boiler/ circulator providing DHW only (water-heating code 911 = "Gas boiler/ circulator for water heating only") is NOT the main space-heating system — so there is no shared timer to apply the ×0.9 against. `_separately_ timed_dhw` now returns False when water_heating_code is not "from main / 2nd-main system" ({901,902,914}), mirroring the existing WHC 903 electric- immersion carve-out. Simulated case 19 (electric storage main SAP 402 + WHS 911 + 210 L loose-jacket cylinder) is the worksheet case. The single flag drives both: - (53) Temperature factor: 0.54 → 0.6000 (worksheet base, no ×0.9) - (55) storage loss/day: → 3.4531; (56)/(57)m Jan → 107.0456 (1e-4) - (59)m primary loss: h=3 (43.31) → h=5 (Jan 64.5792), worksheet-exact This also worksheet-pins S0380.224's loose-jacket storage loss magnitude at 1e-4, previously only direction-validated. Co-Authored-By: Claude Opus 4.8 --- .../sap10_calculator/rdsap/cert_to_inputs.py | 14 ++++++ .../rdsap/test_cert_to_inputs.py | 48 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index 21619ed4..417a9e28 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -4764,6 +4764,20 @@ def _separately_timed_dhw( return False if main.sap_main_heating_code in _TABLE_4A_SOLID_FUEL_BOILER_CODES: return False + # SAP 10.2 Table 2b note b + RdSAP 10 §10.5.1 (PDF p.55): the ×0.9 + # reduction reflects DHW timed separately from space heating on a + # SHARED heat generator. When DHW is from a separate dedicated + # water-heating-only system (water-heating code not "from main / + # 2nd-main system" — e.g. 911 "Gas boiler/circulator for water + # heating only") there is no shared timer to apply the ×0.9 against, + # so the multiplier must not fire — the same principle as the WHC + # 903 electric-immersion carve-out above. Simulated case 19 (electric + # storage main + WHS 911 + 210 L loose-jacket cylinder) is the + # worksheet case: (53) Temperature factor 0.6000 (not 0.54) and + # (59)m primary loss h=5 (Jan 64.5792, not 43.31) both confirm the + # DHW is not separately timed. + if epc.sap_heating.water_heating_code not in _WATER_INHERIT_FROM_MAIN_CODES: + return False return bool(epc.has_hot_water_cylinder) 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 3be0ea4f..c0e8df0a 100644 --- a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py +++ b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py @@ -1864,6 +1864,54 @@ def test_separately_timed_dhw_excludes_electric_immersion_per_table_2b_note_b() assert sep_immersion is False +def test_separately_timed_dhw_excludes_dedicated_water_heater_per_table_2b_note_b() -> None: + # Arrange — SAP 10.2 Table 2b note b) (PDF p.159) applies the ×0.9 + # temperature-factor reduction only when DHW "is separately timed" + # relative to space heating on a SHARED heat generator ("boiler + # systems, warm air systems and heat pump systems"). Per RdSAP 10 + # §10.5.1 (PDF p.55) a separate boiler/circulator providing DHW only + # (water-heating code 911 = "Gas boiler/circulator for water heating + # only") is NOT the main space-heating system — here space is by + # electric storage heaters (SAP code 402). With no shared generator + # there is no separate DHW timer to apply the ×0.9 against, so the + # multiplier must not fire — the same principle as the WHC 903 + # electric-immersion carve-out above. Simulated case 19's worksheet + # confirms it: cylinder thermostat present + "Separate Time Control: + # No" → (53) Temperature factor 0.6000 (base, not 0.54 = 0.6 × 0.9) + # AND (59)m primary loss h=5 (winter Jan 64.5792), not h=3 (43.31). + storage_heater_main = MainHeatingDetail( + has_fghrs=False, + main_fuel_type=30, # electricity + heat_emitter_type="", + emitter_temperature=1, + main_heating_control=2402, # storage-heater auto-charge control + main_heating_category=None, + sap_main_heating_code=402, # electric storage heaters + ) + dedicated_gas_water_heater_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_heating=make_sap_heating( + main_heating_details=[storage_heater_main], + water_heating_fuel=26, # mains gas (dedicated WHS boiler) + water_heating_code=911, # gas boiler/circulator, water only + cylinder_size=4, + cylinder_insulation_type=2, # loose jacket + cylinder_insulation_thickness_mm=50, + ), + ) + + # Act + separately_timed = _separately_timed_dhw( + dedicated_gas_water_heater_epc, storage_heater_main, + ) + + # Assert — dedicated water-heating-only system → not separately timed. + assert separately_timed is False + + def test_water_efficiency_uses_table_4a_water_column_for_heat_pumps_per_sap_10_2() -> None: # Arrange — SAP 10.2 Table 4a (PDF p.163-164) gives heat pumps two # efficiency columns: "space" and "water". For low-temperature