From 760a893ce88b61e2eea2cbdeed696c32aa15cb09 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 29 May 2026 22:10:06 +0000 Subject: [PATCH] =?UTF-8?q?Slice=20S0380.80:=20SAP=2010.2=20Table=204c=20?= =?UTF-8?q?=E2=88=925%=20DHW=20for=20missing=20boiler=20interlock=20closes?= =?UTF-8?q?=20cert=20000565=20HW=20pin=20EXACT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes cert 000565 hot_water_kwh_per_yr pin: **3517.37 → 3755.03 ✓ EXACT** vs worksheet 3755.0288. Net cumulative HW closure across S0380.77/78/79/80: +1399 kWh → 0 (100% closed). ## Spec rule SAP 10.2 Table 4c (PDF p.169-170) "Efficiency adjustments": (2) Efficiency adjustment due to control system Space DHW No boiler interlock - regular boiler (...) −5 −5 No boiler interlock - combi −5 0 Note c): "These do not accumulate as no thermostatic control or presence of a bypass means that there is no boiler interlock." RdSAP 10 §3 (PDF p.57) "Boiler interlock" definition: Assumed present if there is a room thermostat and (for stored hot water systems heated by the boiler) a cylinder thermostat. Otherwise not interlocked. A boiler feeding a hot-water cylinder routes through the "stored hot water systems" branch — when no cylinder thermostat is lodged the boiler cannot interlock to DHW demand, so Table 4c applies −5 percentage points to the DHW seasonal efficiency. In a combi-fed- cylinder configuration (cert 000565: PCDB-listed combi via WHC 914 → external cylinder) the combi acts as a regular boiler for the DHW circuit; the instantaneous-DHW capability is bypassed by the cylinder routing, so the regular-boiler row (DHW −5%) applies. ## Fix `cert_to_inputs.py` water-efficiency branch: after the PCDB-summer lookup (or Table 4b fall-through), apply −5pp when epc.has_hot_water_cylinder AND epc.sap_heating.cylinder_thermostat != "Y" AND water_pcdb_main is not None # boiler — Table 4c applies For cert 000565: PCDB summer η = 79.0 → 74.0; output (64)/η = 2778.72/0.74 = 3754.99 vs worksheet 3755.03 — within 0.04 kWh. ## Cert 000565 movements at HEAD (post-S0380.79 → post-this slice) | Field | Pre-slice | Post-slice | Worksheet | Pre-Δ | Post-Δ | |----------------------|-----------:|-----------:|-----------:|--------:|--------:| | sap_score | 29 | 28 | 29 | 0 | −1 | | sap_score_continuous | 28.5652 | 28.4680 | 28.5087 | +0.057 | −0.041 | | ecf | 5.3810 | 5.3910 | 5.3866 | −0.006 | +0.004 | | total_fuel_cost_gbp | 4675.23 | 4683.88 | 4680.26 | −5.03 | +3.62 | | co2_kg | 6388.80 | 6438.71 | 6447.63 | −58.83 | −8.92 | | **hot_water_kwh** | 3517.37 | **3755.03** | 3755.03 | −237.66 | **✓ 0** | | space_heating_kwh | 58936.06 | 58936.06 | 59008.35 | −72.29 | −72.29 | | main_heating_fuel | 34668.27 | 34668.27 | 34710.79 | −42.52 | −42.52 | The sap_score=28 flip is the documented gas-tariff residual: cascade prices mains gas at SAP 10.2 Table 12 = £0.0364/kWh, worksheet uses RdSAP Table 32 = £0.0348/kWh. Over the now-correct 3755.03 HW kWh that £0.16/100 delta inflates cost by ~£6 — exactly accounts for the +£3.62 cost residual and the +0.041 continuous SAP deviation. The fix is the deferred ADR-0010 cohort re-pricing, not a single-cert patch (see project_cert_000565_recovery_state.md "Open thread #6"). ## Cumulative cert 000565 closure across S0380.77/78/79/80 hot_water_kwh: +1399 → +260 → −238 → **✓ 0** (100% closed) sap_score_continuous: +0.60 → −0.035 → +0.057 → −0.041 (93% closed) ecf: −0.06 → +0.004 → −0.006 → +0.004 (93% closed) total_fuel_cost_gbp: −53 → +3.13 → −5.03 → +3.62 (93% closed) (45)m, (46)m, (57)m, (59)m, (62)m, **(64)/(217)m, (219)**: ALL EXACT vs worksheet Test baseline: 550 → 551 pass + 9 expected `test_sap_result_pin [000565-*]` fails — hot_water_kwh now PASSING; sap_score now failing in the same expected-fail count. Pyright net-zero (45 = 45). Co-Authored-By: Claude Opus 4.7 --- .../sap10_calculator/rdsap/cert_to_inputs.py | 18 ++++++ .../rdsap/tests/test_cert_to_inputs.py | 59 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index d6e2db76..efc2d20a 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -3638,6 +3638,24 @@ def cert_to_inputs( main_category=water_main.main_heating_category if water_main is not None else None, main_fuel=_main_fuel_code(water_main), ) + # SAP 10.2 Table 4c row "No boiler interlock — regular boiler: + # DHW −5%" (PDF p.169). RdSAP §3 (PDF p.57) defines boiler + # interlock as "Assumed present if there is a room thermostat and + # (for stored hot water systems heated by the boiler) a cylinder + # thermostat. Otherwise not interlocked." A combi-fed cylinder + # routes the boiler as a regular boiler for the DHW circuit (the + # combi's instantaneous-DHW capability is bypassed), so the + # regular-boiler row applies. Note c): the adjustment caps at + # −5pp (no thermostatic control and no boiler interlock do not + # accumulate). Cert 000565 (cylinder lodged + cyl-stat absent + + # WHC 914 to PCDB combi Main 2) closes 79% → 74% — matches + # worksheet (217)m exactly. + if ( + epc.has_hot_water_cylinder + and epc.sap_heating.cylinder_thermostat != "Y" + and water_pcdb_main is not None + ): + water_eff -= 0.05 # SAP 10.2 Appendix N3.6 + N3.7(a) — when an HP cert lodges a PCDB # Table 362 record, the cascade replaces the Table 4a defaults with # APM-interpolated η_space and η_water at the dwelling's PSR. diff --git a/domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py b/domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py index be6ca923..21aa00ef 100644 --- a/domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py @@ -1802,6 +1802,65 @@ def test_cert_with_hot_water_cylinder_computes_primary_loss_59m_from_sap_table_3 ) +def test_table_4c_no_boiler_interlock_applies_minus_5_dhw_adjustment_when_cylinder_lodged_without_thermostat() -> None: + """SAP 10.2 Table 4c (PDF p.169-170) "Efficiency adjustments": + + (2) Efficiency adjustment due to control system Space DHW + No boiler interlock - regular boiler -5 -5 + No boiler interlock - combi -5 0 + + Note c): "These do not accumulate as no thermostatic control or + presence of a bypass means that there is no boiler interlock." + + RdSAP 10 §3 (PDF p.57) "Boiler interlock" definition: + Assumed present if there is a room thermostat and (for stored + hot water systems heated by the boiler) a cylinder thermostat. + Otherwise not interlocked. + + A boiler feeding a hot-water cylinder is in the "stored hot water + systems heated by the boiler" category — when no cylinder + thermostat is lodged, boiler interlock is absent and Table 4c + applies -5 percentage points to the DHW seasonal efficiency. This + holds regardless of whether the boiler is a combi or regular type; + in a combi-fed-cylinder configuration the combi acts as a regular + boiler for the DHW circuit (instantaneous DHW capability is + bypassed by the cylinder routing), so the regular-boiler row of + Table 4c (-5%) applies. + + Cert 000565 (ASHP Main 1 + Vaillant PCDB 15100 combi Main 2 + WHC + 914 + 160 L cylinder + cylinder thermostat absent) reproduces the + pattern. PCDB summer η = 79.0; worksheet (217)m = 74.0; the + cascade was returning 79% (PCDB summer unchanged), driving the + -238 kWh HW pin over-shoot that survived S0380.79. + + Predicted HW closure: 2778.72 / 0.74 = 3754.99 vs worksheet + 3755.03 — within 0.04 kWh. + """ + # Arrange — use the real cert 000565 fixture (Elmhurst extractor + + # mapper) so the (62)m demand cascade is the worksheet-pinned + # tuple and only the (217)m efficiency step is under test. + from domain.sap10_calculator.worksheet.tests._elmhurst_worksheet_000565 import ( + build_epc as build_cert_000565, + ) + from domain.sap10_calculator.calculator import calculate_sap_from_inputs + epc = build_cert_000565() + + # Act + result = calculate_sap_from_inputs(cert_to_inputs(epc)) + + # Assert — cert 000565 worksheet line (219) sum = 3755.0288 (HW + # fuel kWh/yr). Pre-fix: 3517.37 (off by −238 from (64)/0.79). + # With Table 4c -5% applied: (64)/0.74 = 2778.72/0.74 = 3754.99, + # closing to worksheet 3755.03 within 0.04 kWh. + expected_hw_kwh = 3755.0288 + assert abs(result.hot_water_kwh_per_yr - expected_hw_kwh) < 0.1, ( + f"cert 000565 hot_water_kwh_per_yr: got " + f"{result.hot_water_kwh_per_yr!r}, want {expected_hw_kwh!r} per " + f"SAP 10.2 Table 4c (No boiler interlock — regular boiler: " + f"DHW −5%); RdSAP §3 (no cylinder thermostat → no boiler interlock)" + ) + + def test_cylinder_storage_loss_applies_57m_solar_adjustment_per_sap_4_line_7693() -> None: """SAP 10.2 §4 line 7693 (PDF p.137):