S0380.201: SAP 10.2 Table 4f note c) second main-system circulation pump

Simulated case 6 (P960-0001-001431, dual oil boiler 51% rads + 49%
underfloor) worksheet (231) = 356 = (230c) central-heating pump 156 +
(230d) oil boiler pump 200. (230c) decomposes per SAP 10.2 Table 4f
note c) (PDF p.175): "Where there are two main heating systems include
two figures from this table" — Main 1 41 kWh (pump age "2013 or later")
+ Main 2 115 kWh (pump age unknown). The cascade summed only Main 1's
circulation pump, giving (231) = 241.

cert_to_inputs now adds the second main's circulation pump, gated on a
lodged main_heating_fraction > 0 (a genuine second SPACE-heating main —
the same test §9a uses to split space-heating demand). This excludes
DHW-only second mains (cert 000565 Main 2 = gas combi via WHC 914,
fraction 0); without the gate 000565's worksheet pins regressed +115 kWh.

Re-pin: golden 0240 (dual-main oil combi, API-only, no worksheet) gains
its Main 2 pump too (pumps_fans 315 → 430). Spec-correct per
note c and validated by the case-6 worksheet; SAP cont 72.55 → 72.18
(integer 73 → 72, resid +0 → -1), PE +1.9459 → +2.8092, CO2 +0.1226 →
+0.1385. The lodged 73 carries Elmhurst's own residual; the worksheet-
backed case 6 is the spec authority for the archetype.

Note: the boiler-interlock −5pp per-main determination the prior
handover flagged as the priority is already implemented (S0380.141
cylinder-thermostat path + S0380.177 room-thermostat path) — case 6
already produces (206)=79 / (207)=84 exactly, and 0240 is a combi with
no cylinder so correctly unpenalised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-03 13:51:13 +00:00
parent 558aaf6d09
commit 963db2ae23
4 changed files with 76 additions and 4 deletions

View file

@ -5924,6 +5924,27 @@ def cert_to_inputs(
has_balanced_mv=_has_balanced_mechanical_ventilation(epc),
)
)
# SAP 10.2 Table 4f note c) (PDF p.175): "Where there are two main
# heating systems include two figures from this table." A genuine
# second SPACE-heating main therefore contributes its own circulation
# pump alongside Main 1's. The "second main heating system" test is the
# same one §9a uses to split space-heating demand: a lodged
# `main_heating_fraction > 0`. This excludes DHW-only second mains
# (e.g. cert 000565 Main 2 = gas combi via WHC 914, fraction 0 — water
# heating only, no space-heating circulation pump). Simulated case 6
# (dual oil boiler, 51% rads + 49% underfloor) lodges Main 1 "2013 or
# later" (41 kWh) + Main 2 unknown-date (115 kWh) → worksheet (230c)
# central-heating pump = 41 + 115 = 156. The Main 2 oil-boiler aux
# (230d) is already summed in `_table_4f_additive_components`; this
# adds only the circulation pump.
_pumps_main_details = (
epc.sap_heating.main_heating_details if epc.sap_heating else []
)
if len(_pumps_main_details) >= 2:
_pumps_main_2 = _pumps_main_details[1]
_pumps_main_2_fraction = _pumps_main_2.main_heating_fraction
if _pumps_main_2_fraction is not None and _pumps_main_2_fraction > 0:
pumps_fans_kwh += _table_4f_circulation_pump_kwh(_pumps_main_2)
pumps_fans_kwh += _table_4f_additive_components(epc)
# Track the MEV/MVHR-fan portion separately so the cost cascade can
# apply Table 12a Grid 2 `FANS_FOR_MECH_VENT` (0.58 high-frac on

View file

@ -82,9 +82,9 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
_GoldenExpectation(
cert_number="0240-0200-5706-2365-8010",
actual_sap=73,
expected_sap_resid=+0,
expected_pe_resid_kwh_per_m2=+1.9459,
expected_co2_resid_tonnes_per_yr=+0.1226,
expected_sap_resid=-1,
expected_pe_resid_kwh_per_m2=+2.8092,
expected_co2_resid_tonnes_per_yr=+0.1385,
notes=(
"Detached house, TFA 118, age J, oil boiler PCDB-listed + PV + "
"RR on BP[0]. Mapper DOES extract sap_room_in_roof.room_in_roof_"
@ -136,7 +136,25 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
"45°-inclined solar gains. Validated against the simulated-"
"case-6 worksheet ((27a) U_eff 2.1062). The inclined solar "
"gain dominates → SAP cont 72.14 → 72.55 (resid -1 → +0 "
"EXACT), PE +3.9138 → +1.9459, CO2 +0.2213 → +0.1226."
"EXACT), PE +3.9138 → +1.9459, CO2 +0.2213 → +0.1226. "
"Slice S0380.201 added the SECOND main heating system's "
"circulation pump per SAP 10.2 Table 4f note c) (PDF p.175) "
"\"Where there are two main heating systems include two "
"figures from this table\" — gated on a lodged "
"main_heating_fraction > 0 (a genuine second SPACE-heating "
"main, excluding DHW-only second mains). This cert is dual-"
"main oil combi 51%/49%; Main 2 pump_age unknown (115 kWh) "
"joins Main 1's 115 → cascade pumps_fans 315 → 430 (+115 "
"kWh/yr). The fix was validated against the simulated-case-6 "
"worksheet, whose (231) = 356 decomposes as (230c) central-"
"heating pump 156 (= Main 1 41 + Main 2 115) + (230d) oil "
"boiler pump 200 — proving the two-pump treatment is spec-"
"correct. Cascade SAP cont 72.55 → 72.18 (integer 73 → 72, "
"resid +0 → -1), PE +1.9459 → +2.8092, CO2 +0.1226 → "
"+0.1385. The lodged 73 carries Elmhurst's own rounding/"
"residual (this cert is API-only with no worksheet); the "
"worksheet-backed case 6 is the spec authority for the "
"archetype per [[feedback-worksheet-not-api-reference]]."
),
),
_GoldenExpectation(

View file

@ -69,6 +69,14 @@ LINE_27_WINDOWS_W_PER_K: Final[float] = 22.7408
LINE_27A_ROOF_WINDOWS_W_PER_K: Final[float] = 13.0375
LINE_31_TOTAL_EXTERNAL_AREA_M2: Final[float] = 336.13
# Worksheet (231) "Total electricity for the above, kWh/year" (Block 1).
# Decomposes as (230c) central heating pump 156 + (230d) oil boiler pump
# 200. (230c) = 41 (Main 1 circ pump, "2013 or later") + 115 (Main 2 circ
# pump, unknown date) — the two-main-system circulation-pump pair per
# SAP 10.2 Table 4f note c. (230d) = 2 × 100 oil-boiler aux (already
# wired in `_table_4f_additive_components`).
LINE_231_PUMPS_FANS_KWH: Final[float] = 356.0
def _summary_pdf_to_textract_style_pages(pdf_path: Path) -> list[str]:
"""Convert a Summary PDF into the per-page text format the

View file

@ -278,6 +278,31 @@ def test_section_3_roof_windows_case6_match_pdf() -> None:
)
def test_section_4f_pumps_fans_case6_match_pdf() -> None:
"""(231) pumps/fans pin for simulated case 6 — a DUAL-oil-boiler
detached dwelling. Worksheet (231) = 356 = (230c) central heating
pump 156 + (230d) oil boiler pump 200. (230c) is itself the two-
main-system circulation-pump pair per SAP 10.2 Table 4f note c
("Where there are two main heating systems include two figures from
this table"): Main 1 41 kWh (pump age "2013 or later") + Main 2 115
kWh (pump age unknown). The pre-S0380.201 cascade summed only Main 1's
circulation pump (41) and gave (231) = 241."""
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
# Arrange
epc = _w001431_case6.build_epc()
# Act
result = calculate_sap_from_inputs(cert_to_inputs(epc))
# Assert
_pin(
result.pumps_fans_kwh_per_yr,
_w001431_case6.LINE_231_PUMPS_FANS_KWH,
"§4f (231) case6",
)
# ============================================================================
# §4 Water heating — LINE_42..LINE_65 scalar + monthly tuples
# ============================================================================