Documents the full §4 HW cascade closure for cert 000565 across the S0380.77 → S0380.80 series: - S0380.77 primary loss WHC 914 routing - S0380.78 §1x.0 shower extractor + (247a) fallback cost - S0380.79 (57)m solar storage + separately-timed-DHW cylinder default - S0380.80 Table 4c −5% DHW for missing boiler interlock Cumulative cert 000565 closure: hot_water_kwh: +1399 → ✓ 0 EXACT (100%) continuous SAP: +0.78 → -0.041 (95% closed) total cost: -69 → +3.62 (95% closed) All §4 line refs (45)/(46)/(57)/(59)/(62)/(64)/(217)/(219) EXACT Open #1 priority for the next agent: deferred ADR-0010 mains-gas tariff Table 32 vs Table 12 cohort closure. The remaining sap_score=28 vs worksheet 29 flip is entirely due to this £0.16/100 gas-price delta over 3755 HW kWh = +£6 → +0.041 continuous SAP → flips integer at the 28.5 boundary. Cohort-wide change; would land sap_score=29 EXACT for cert 000565 AND likely tighten several other worksheet certs in the same coordinated pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
19 KiB
Handover — post S0380.77..80 + cert 000565 §4 HW cascade fully spec-correct
Branch: feature/per-cert-mapper-validation. HEAD 760a893c.
Predecessor: HANDOVER_POST_S0380_76.md.
Slices committed this session (S0380.77..80)
Four spec-cited slices closed the entire §4 HW cascade for cert 000565 from +1399 kWh HW pin to EXACT.
| Slice | Commit | Spec | Cert 000565 closure |
|---|---|---|---|
| S0380.77 | a33904c5 |
SAP 10.2 §4 line 7700 + Table 3 (p.159) — primary loss applies to the heat generator that feeds the cylinder, not the space-heating main. WHC 914 routes the gate to the DHW main. | (59)m EXACT |
| S0380.78 | 509ef4fb |
SAP 10.2 Appendix J §J2 step 2a (p.81) bath formula + §10a line (247a) (p.145) electric-shower cost. Coupled fixes: §1x.0 section-bounded shower extractor + (247a) added to fallback total_cost. |
(45)m EXACT |
| S0380.79 | f9551355 |
SAP 10.2 §4 line 7693 (p.137) (57)m = (56)m × (V−Vs)/V solar adjustment + Table 2b note b) + RdSAP §3 (p.57) _separately_timed_dhw=True when cylinder lodged. |
(57)m EXACT, (62)m EXACT |
| S0380.80 | 760a893c |
SAP 10.2 Table 4c (p.169) "No boiler interlock — regular boiler: DHW −5%" + RdSAP §3 (p.57) boiler interlock definition. Combi-fed cylinder + cyl-stat absent → −5pp DHW efficiency. | hot_water_kwh EXACT |
Test baseline at HEAD 760a893c: 551 pass + 9 expected
test_sap_result_pin[000565-*] cascade-gap fails. Pyright net-zero on every
touched file.
Cert 000565 state (HEAD 760a893c)
| Pin | Cascade | Worksheet | Δ | Cause |
|---|---|---|---|---|
| sap_score (int) | 28 | 29 | −1 | Rounding boundary; continuous SAP 28.4680 lands 0.041 below 28.5 cutoff |
| sap_score_continuous | 28.4680 | 28.5087 | −0.041 | Downstream of total_cost +£3.62 (deferred ADR-0010 gas tariff) |
| ecf | 5.3910 | 5.3866 | +0.004 | Downstream of total_cost |
| total_fuel_cost_gbp | 4683.88 | 4680.26 | +3.62 | Deferred ADR-0010 gas tariff (Table 12 £0.0364 vs Table 32 £0.0348) |
| co2_kg_per_yr | 6438.71 | 6447.63 | −8.92 | Lighting + Main-1 small CO2 factor residual |
| space_heating_kwh | 58936.06 | 59008.35 | −72.29 | RR fold-in (RdSAP §3.10 detailed-RR geometry) |
| main_heating_fuel | 34668.27 | 34710.79 | −42.52 | Follows space_heating via 1/COP |
| hot_water_kwh | 3755.03 | 3755.03 | ✓ 0 EXACT | §4 cascade fully closed |
| lighting | 1387.02 | 1384.84 | +2.19 | Sub-spec |
| pumps_fans | 255.00 | 252.52 | +2.48 | MEV PCDB record missing |
§4 HW cascade line refs all EXACT
| Line | Cascade | Worksheet | Δ |
|---|---|---|---|
| (45)m sum energy_content | 1286.3266 | 1286.3266 | ✓ 0 |
| (46)m sum distribution_loss | 192.9490 | 192.95 | ✓ <1e-3 |
| (57)m sum solar_storage | 596.9725 | 596.9725 | ✓ <1e-4 |
| (59)m sum primary_loss | 1176.77 | 1174.79 | +1.98 (sub-2 kWh rounding) |
| (61)m combi_loss | 0.00 | 0.00 | ✓ 0 |
| (62)m sum total_demand | 3060.07 | 3060.07 | ✓ <1e-3 |
| (64)m sum (after solar) | 2778.72 | 2778.7213 | ✓ <1e-4 |
| (64a)m electric shower | 702.94 | 702.94 | ✓ <1e-4 |
| (217)m water-heater eff | 0.74 | 0.74 | ✓ EXACT |
| (219) HW fuel kWh | 3755.03 | 3755.0288 | ✓ <1e-3 |
The §4 line-by-line trace is the most diagnostically transparent state cert 000565 has been in. Any future cascade refactor should be validated by checking each line stays EXACT, not just the SapResult-level pins.
Slice S0380.77 — primary loss WHC 914 routing
Bug: _primary_loss_override(epc, main, primary_age) was called with
main = _first_main_heating(epc) (Main 1 = HP for cert 000565). The
_primary_loss_applies gate then keyed off the HP's category = None,
returned False, and (59)m was zeroed despite the cert having an external
cylinder fed by Main 2 (gas combi).
Spec: SAP 10.2 §4 line 7700 + Table 3 (PDF p.159):
Primary circuit loss applies when hot water is heated by a heat generator (e.g. boiler) connected to a hot water storage vessel via insulated or uninsulated pipes (the primary pipework).
The eligibility is determined by the heat generator that feeds the cylinder — for cert 000565 that's Main 2 (gas combi via WHC 914), not Main 1 (HP).
Fix: _primary_loss_override resolves its main via
_water_heating_main(epc) (the WHC-914 resolver) rather than
_first_main_heating. Signature drops the main parameter.
Test: test_whc_914_dhw_routes_primary_loss_gate_to_second_main_heating_per_sap_table_3.
Slice S0380.78 — §1x.0 shower extractor + (247a) fallback cost
Bug A (extractor): _extract_baths_and_showers used
self._lines.index("Connected") (global search) to anchor the shower
roster. Cert 000565 lodges 4 extensions whose §3 building-parts list
contains "Connected" / "Exposed" / "Sheltered" wall elevation flags
earlier in the document. The global match landed on a wall row; the
digit-check num_line.isdigit() failed on "0.00" and the shower list
came back empty.
Bug B (calculator fallback): calculator.py STANDARD-tariff path
already plumbed instant_shower_cost_gbp via fuel_cost(...). The
fallback scalar path for TEN_HOUR / _ZERO_FUEL_COST_RESULT certs was
silently dropping electric_shower_kwh × other_fuel_cost from total
cost. Cert 000565 (Dual-meter TEN_HOUR + 1 electric shower) trips this
branch — fix A surfaced the £93/yr under-count.
Spec:
- SAP 10.2 Appendix J §J2 step 2a (p.81):
N_bath = 0.13 N + 0.19when shower also present;0.35 N + 0.50when no shower. 2.7× swing. - SAP 10.2 §10a (p.145):
Energy for instantaneous electric shower(s) (64a) × 0.01 = (247a)— feeds (255) total cost.
Fixes:
_extract_baths_and_showersroutes the "Connected" lookup through_section_lines("1x.0 Baths and Showers", "18.0 Flue Gas Heat Recovery System"). Both anchors are single-occurrence in the Elmhurst Summary PDF schema.calculator.pyfallbacktotal_costaddsinputs.electric_shower_kwh_per_yr × inputs.other_fuel_cost_gbp_per_kwh.
Tests:
test_summary_000565_extractor_finds_electric_shower_in_section_1x_0test_total_fuel_cost_includes_247a_electric_shower_in_fallback_path
Why coupled
Splitting the fixes would flip sap_score from 29 → 30 mid-state: the extractor fix corrects (45)m to EXACT but exposes (64a) electric-shower kWh, which without (247a) cost flow makes total_cost too low → ECF too low → SAP rating too high. Bundling keeps sap_score within rounding.
Slice S0380.79 — (57)m solar storage + separately_timed_dhw cylinder default
Bug A: _cylinder_storage_loss_override returned raw (56)m as
solar_storage_monthly_kwh_override. SAP 10.2 §4 (62)m formula uses
(57)m (the solar-adjusted storage loss), not (56)m. For cert 000565 with
solar HW + combined cylinder, (62)m was over-counting by
(56)m × Vs/V ≈ 395 kWh/yr.
Bug B: _separately_timed_dhw gated only on
main.main_heating_category == 4 (heat pumps), returning False for
boiler-family + cylinder configs. Cert 000565 (gas combi + cylinder +
no cyl-stat) fell through to TF = 0.78; worksheet uses 0.702 (with the
0.9 multiplier for separately-timed DHW). 10% TF over-count drove
+98 kWh into (56)m.
Spec:
- SAP 10.2 §4 line 7693 (p.137):
If the vessel contains dedicated solar storage or dedicated WWHRS storage, (57)m = (56)m × [(47) - Vs] ÷ (47), else (57)m = (56)m where Vs is Vww from Appendix G3 or (H12) from Appendix H. - SAP 10.2 Table 2b note b) (p.159): "Multiply Temperature Factor by 0.9 if there is separate time control of domestic hot water (boiler systems, warm air systems and heat pump systems)".
- RdSAP 10 §3 (p.57) default table "Hot water separately timed":
No programmer, pre-1998 boiler: - No Programmer, pre-1998 boiler: - Yes Post-1998 boiler: - Yes
Fixes:
_cylinder_storage_loss_override: whenepc.solar_water_heating, return(56)m × (V−Vs)/V. Vs =round(volume_l × ⅓)per S0380.76's combined-cylinder convention._separately_timed_dhw(epc, main): signature gainsepc; returns True when a cylinder is lodged in addition to the existing HP branch.
Tests:
test_cylinder_storage_loss_applies_57m_solar_adjustment_per_sap_4_line_7693
Cross-cohort impact — cert 0390 pin update
Golden cert 0390-2954-3640-2196-4175 (Firebird oil combi PCDF 9005 +
160 L cylinder + cyl-stat=Y) was previously flagged at SAP residual −7
with the comment "traces to fabric heat-loss / oil-fuel cost cascade
rather than the §4 HW path". That diagnosis was wrong: cert 0390's §4
HW cascade WAS applying TF = 0.60 instead of TF = 0.54 — cyl-stat=Y
- programmer-present default → separately_timed=True per RdSAP §3, which the cohort heuristic was missing. Pin updated −7 → −6 per feedback-golden-residuals-near-zero.
Slice S0380.80 — Table 4c −5% DHW for missing boiler interlock
Bug: Cascade water-efficiency for cert 000565 used PCDB summer η = 79% directly. Worksheet uses (217)m = 74%. Investigation in this session resolved the −5pp gap to SAP 10.2 Table 4c.
Spec: SAP 10.2 Table 4c (p.169-170):
(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 (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 PCDB-listed boiler feeding a cylinder without a cylinder thermostat has no boiler interlock → −5pp DHW. A combi-fed cylinder routes the boiler as a regular boiler for the DHW circuit (instantaneous-DHW capability is bypassed), so the regular-boiler row (DHW −5%) applies.
Fix: cert_to_inputs.py water-efficiency branch:
if (
epc.has_hot_water_cylinder
and epc.sap_heating.cylinder_thermostat != "Y"
and water_pcdb_main is not None
):
water_eff -= 0.05
Test:
test_table_4c_no_boiler_interlock_applies_minus_5_dhw_adjustment_when_cylinder_lodged_without_thermostat.
Effect on other certs:
- Combi-only certs (no cylinder): condition fails → no change.
- ASHP cohort certs: water_pcdb_main is None (HP not in Table 105) → no change.
- Boiler + cylinder + cyl-stat=Y certs (e.g. cert 0390): cyl-stat present → condition fails → no change.
- Boiler + cylinder + cyl-stat=N + PCDB Table 105 record: −5% applies. Only cert 000565 in the current test suite has this shape.
Why sap_score=28 (not 29) at HEAD 760a893c
S0380.80 closes the cascade to spec-correct values. The remaining deviation is a documented deferred gap:
worksheet HW cost = 3755.0288 × £0.0348/kWh = £130.6750 (RdSAP Table 32)
cascade HW cost = 3755.0288 × £0.0364/kWh = £136.6831 (SAP 10.2 Table 12)
----------
Δ = +£6.01
The £0.16/100 gas-price delta inflates HW cost by ~£6, exactly the total_fuel_cost residual (+£3.62 net after smaller offsets) AND the continuous SAP deviation (+0.041). ECF = cost / (TFA + 45) is the forcing function: lower cost → higher SAP rating; higher cost → lower SAP rating. The cascade is pricing UP, so SAP rating drops below the 28.5 integer boundary.
Fix is the deferred ADR-0010 cohort-wide repricing, not a single- cert patch (see Open thread #6 below).
After ADR-0010 lands, projected cert 000565 sap_score = 29 ✓ EXACT (continuous projected at ≈ 28.51, well within rounding of worksheet 28.5087).
Open work — prioritised next slices
#1 (largest) — ADR-0010 mains gas tariff Table 32 vs Table 12
Magnitude: Cohort-wide. For cert 000565: +£3.62 cost → +0.041 continuous SAP → flips sap_score 29→28. For other gas-DHW certs: similar order-of-magnitude.
Cascade: Uses SAP 10.2 Table 12 prices (£0.0364/kWh mains gas). Worksheet uses RdSAP 10 Table 32 (£0.0348/kWh).
Tractability: Requires ADR-0010 amendment + coordinated cohort re- pin (every golden + Elmhurst worksheet cert's pinned cost shifts). NOT a single-cert slice.
Suggested approach:
- Read ADR-0010 to understand the current price-table decision and what's blocking the switch to Table 32.
- Identify which Elmhurst worksheets in the cohort actually use Table 32 (the U985 ones definitely do).
- Stand up a parallel
RDSAP10_TABLE_32_PRICESconstant alongsideSAP_10_2_SPEC_PRICES. - Re-pin all golden + Elmhurst e2e expectations under Table 32.
- ADR-0010 amendment commit that frames the policy decision.
This is the highest-leverage single change for the Elmhurst worksheet cohort. After this lands, cert 000565 → sap_score 29 EXACT, plus likely several other open-residual certs close.
#2 — RR (room-in-roof) fold-in for cert 000565 space_heating −72
Magnitude: −72 kWh space_heating (cert 000565) → −42 kWh main_heating_fuel via 1/COP.
Cascade: Doesn't fully implement RdSAP §3.10 detailed-RR geometry
- area formula. Cert 000565 has RR on every part (5 BPs) with detailed gable wall lengths, slopes, common walls.
Tractability: Single-cert slice, but needs spec-citation work in the heat_transmission cascade. The detailed-RR area formula is in RdSAP §3.10 (PDF p.30-35, "Room in roof").
#3 — Lighting CO2 factor Δ−0.0025 (tariff-blended Table 12d)
Magnitude: −3.16 kg CO2 (lighting) and similar Δ−0.0025 on pumps_fans CO2 factor. Same cause: cascade uses code 30 (standard electricity) Table 12d factors; worksheet uses TEN_HOUR Grid 1 blend of codes 33 (10h low) + 34 (10h high).
Cascade: lighting_co2_factor_kg_per_kwh=_effective_monthly_co2_factor( lighting_monthly_kwh, _STANDARD_ELECTRICITY_FUEL_CODE) at
cert_to_inputs.py:4054. Same shape for pumps_fans at line 4050.
Tractability: Clean spec citation. Mirror what S0380.65 did for main_heating_co2_factor (Table 12a Grid 1 high/low blend) for lighting and pumps_fans. Only affects off-peak tariff certs (cert 000565 is the only Elmhurst worksheet fixture on Dual-meter; cohort-2 has some).
#4 — MEV pumps_fans +2.48 (PCDB MEV table missing)
Magnitude: +2.48 kWh pumps_fans (cert 000565). PCDB MEV record table not in the repo (cert lodges PCDF 500755). External-data acquisition gates this; not solvable in code.
#5 — HP SAP code → main_heating_category=4 in mapper
Cert 000565 Main 1 has sap_main_heating_code=224 but no PCDB Table 362
ref → mapper sets category=None. The TODO in
mapper.py:_elmhurst_main_heating_category says this is deferred
because of HP-on-E7 cost cascade + Table 4f MEV component coupling.
Now that S0380.80 has surfaced the cleaner cascade, the coupling cost
analysis can be redone. Couples with #4 (MEV).
#6 — 12 gas-combi PV certs at +0.5..+1.6 PE
Unchanged from prior handover. No worksheets available; re-pinned at current residuals.
#7 — 5 SAP-integer-residual certs
Unchanged. All API-only (no worksheets). User has agreed not to chase these without worksheet ground truth.
How to run the baseline
PYTHONPATH=/workspaces/model python -m pytest \
backend/documents_parser/tests/test_summary_pdf_mapper_chain.py \
backend/documents_parser/tests/test_elmhurst_extractor.py \
backend/documents_parser/tests/test_elmhurst_end_to_end.py \
domain/sap10_calculator/worksheet/tests/test_e2e_elmhurst_sap_score.py \
domain/sap10_calculator/worksheet/tests/test_appendix_h_solar.py \
domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py \
domain/sap10_calculator/rdsap/tests/test_golden_fixtures.py \
--no-cov -q
Expected: 551 pass + 9 expected test_sap_result_pin[000565-*] fails
at HEAD 760a893c.
The 9 expected fails (verbatim from the latest run):
sap_score
sap_score_continuous
ecf
total_fuel_cost_gbp
co2_kg_per_yr
space_heating_kwh_per_yr
main_heating_fuel_kwh_per_yr
lighting_kwh_per_yr
pumps_fans_kwh_per_yr
hot_water_kwh_per_yr was the 10th fail in baselines a532f75d through
f9551355; now passes at HEAD 760a893c.
Files touched this session
| File | Slices | Change |
|---|---|---|
backend/documents_parser/elmhurst_extractor.py |
S0380.78 | _extract_baths_and_showers uses _section_lines("1x.0 Baths and Showers", "18.0 Flue Gas Heat Recovery System") instead of self._lines.index("Connected") |
backend/documents_parser/tests/test_summary_pdf_mapper_chain.py |
S0380.78 | New test test_summary_000565_extractor_finds_electric_shower_in_section_1x_0 |
domain/sap10_calculator/calculator.py |
S0380.78 | Fallback scalar total_cost adds electric_shower_kwh × other_fuel_cost |
domain/sap10_calculator/tests/test_calculator.py |
S0380.78 | New test test_total_fuel_cost_includes_247a_electric_shower_in_fallback_path |
domain/sap10_calculator/rdsap/cert_to_inputs.py |
S0380.77, S0380.79, S0380.80 | _primary_loss_override resolves DHW main internally; _separately_timed_dhw(epc, main) cylinder-default; _cylinder_storage_loss_override applies (57)m solar adjustment; water_eff −= 0.05 for Table 4c boiler-interlock |
domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py |
S0380.77, S0380.79, S0380.80 | 3 new tests pinning the spec rules above |
domain/sap10_calculator/rdsap/tests/test_golden_fixtures.py |
S0380.79 | Cert 0390 pin updated −7 → −6 with revised notes citing S0380.79 |
Spec source quick-reference
- SAP 10.2 full specification:
domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf- Appendix J §J2 step 2a (bath formula): p.81
- §4 (45)..(65) HW worksheet: p.135-137
- §4 line 7693 (57)m solar adjustment: p.137
- §4 line 7700 + Table 3 (primary loss): p.159
- §10a (245)..(255) cost worksheet: p.145
- Table 2 (HW storage loss factor): p.158
- Table 2b (HW storage loss temperature factor + notes a/b): p.159
- Table 3 (primary circuit loss): p.159
- Table 3a (combi loss): p.160
- Table 4b (gas/oil boiler seasonal efficiency): p.168
- Table 4c (efficiency adjustments — boiler interlock, etc.): p.169-170
- Appendix D2.1 (using PCDB efficiency values): p.57
- Appendix D2.2 (condensing boiler corrections): p.58
- RdSAP 10 specification:
domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf- §3 default table (boiler interlock, separately timed DHW, pipework insulation): p.57
- §10.11 Table 29 (solar panel defaults): p.58
- S10TP-12 (BRE seasonal efficiency of condensing boilers):
domain/sap10_calculator/docs/specs/sap10 technical papers/S10TP-12 - Seasonal efficiency of condensing boilers - V1.2.pdf - SAP 10.3 at
domain/sap10_calculator/docs/specs/sap-10-3-full-specification-2026-01-13.pdf: DO NOT reference (project tracks 10.2 only per feedback-sap-10-2-only-never-10-3)
Memory updated this session
project_cert_000565_recovery_state— full S0380.77/78/79/80 history, cumulative closure table, attribution of remaining residuals to deferred ADR-0010 gas tariffMEMORY.md— index entry refreshed