Slice S0380.80: SAP 10.2 Table 4c −5% DHW for missing boiler interlock closes cert 000565 HW pin EXACT

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 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-29 22:10:06 +00:00
parent f9551355bb
commit 760a893ce8
2 changed files with 77 additions and 0 deletions

View file

@ -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.

View file

@ -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):