Model/domain/sap10_calculator/docs/HANDOVER_POST_S0380_80.md
Khalim Conn-Kowlessar 69710f5882 docs: handover + next-agent prompt post S0380.77..80 (cert 000565 §4 HW EXACT)
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>
2026-05-29 22:18:41 +00:00

19 KiB
Raw Blame History

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 × (VVs)/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.19 when shower also present; 0.35 N + 0.50 when 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_showers routes 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.py fallback total_cost adds inputs.electric_shower_kwh_per_yr × inputs.other_fuel_cost_gbp_per_kwh.

Tests:

  • test_summary_000565_extractor_finds_electric_shower_in_section_1x_0
  • test_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: when epc.solar_water_heating, return (56)m × (VVs)/V. Vs = round(volume_l × ⅓) per S0380.76's combined-cylinder convention.
  • _separately_timed_dhw(epc, main): signature gains epc; 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:

  1. Read ADR-0010 to understand the current price-table decision and what's blocking the switch to Table 32.
  2. Identify which Elmhurst worksheets in the cohort actually use Table 32 (the U985 ones definitely do).
  3. Stand up a parallel RDSAP10_TABLE_32_PRICES constant alongside SAP_10_2_SPEC_PRICES.
  4. Re-pin all golden + Elmhurst e2e expectations under Table 32.
  5. 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 tariff
  • MEMORY.md — index entry refreshed