Adds a "build THIS in Elmhurst" specification — dwelling, dual condensing oil-combi (code 130) heating, combi/no-cylinder DHW (Table 3a keep-hot 600), per-element fabric W/K targets, room-in-roof gables, the 5 vertical + 6 roof-of-room windows, lighting (8 LED), no PV — so a generated worksheet reproduces cert 0240 as closely as possible. Flags the three load-bearing differences vs case 6 (combi code 130, no cylinder, boiler interlock PRESENT → no -5pp) that the new worksheet must capture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
Handover — closing golden cert 0240-0200-5706-2365-8010 to 1e-4
Point-in-time note. Start from AGENT_GUIDE.md for methodology,
accuracy bar, and pipeline. This records the state of cert 0240 and the
concrete path to driving its residual to zero.
- Branch:
feature/per-cert-mapper-validation - HEAD:
7344f600(S0380.207). Confirm withgit rev-parse HEAD. - Baseline:
2372 passed, 1 skipped, 0 failed(AGENT_GUIDE §4 suite command).
What the last session shipped (S0380.201–207)
Closed simulated case 6 (001431_case6) to 1e-4 on the full SapResult and
promoted it to an e2e fixture. It is the worksheet-backed proxy for 0240's
dual-oil, different-parts archetype (Main 1 rads/2106 + Main 2 UFH/2110,
51/49, 6 "Roof of Room" rooflights, no boiler interlock). Slices:
| slice | spec | what |
|---|---|---|
| S0380.201 | Table 4f note c) | 2nd-main circulation pump → (231) |
| S0380.202 | Table 5a note a) | 2nd-main pump gain → (70) |
| S0380.203 | RdSAP §3.7 | "Roof of Room" rooflights deduct from the §3.10.1 RR residual → (30) |
| S0380.204 | extractor/mapper | capture Main 2's §14.1 emitter + control |
| S0380.205 | SAP 10.2 p.186 | two-systems-different-parts MIT: weighted R + elsewhere two-control blend → (87)/(90)/(98c) |
| S0380.206 | Appendix D Eq D1 | Q_space = DHW boiler's own (204) share, not (202) → (219) |
| S0380.207 | test | promote case 6 to a full e2e fixture |
0240 was re-pinned at each step (it shares the archetype) and its residual improved on PE/CO2 but its SAP integer dropped 73→72. The boiler-interlock −5pp the previous handover called the priority was already implemented — see project_case6_interlock_already_done.
The 0240 problem — and why case 6 did NOT close it
⚠️ Critical: 0240 is API-only and its register target is INTEGER-rounded
0240 has no worksheet. The golden test pins the cascade against the lodged EPC register:
energy_consumption_current = 122— an integer (1 kWh/m² resolution).co2_emissions_current = 6.0— 1 d.p. (tonnes).current_energy_efficiency = None— the SAP isn't even in the JSON (actual_sap=73in the test was carried from the original lodgement).
You cannot drive 0240 to "0 residual" at 1e-4 against these. The register
rounds PE to the nearest whole kWh/m², so any cascade value in [121.5, 122.5)
is 122, and the true (unrounded) Elmhurst value could sit anywhere in that band
— or itself carry residual vs the rounded lodgement. Matching a rounded integer
to 1e-4 is not a well-posed target. The only 1e-4 ground truth is a worksheet
(the per-line (1)..(286) Elmhurst output), which is exactly why case 5/6 were
generated.
Current 0240 cascade vs lodged: PE 123.8687 vs 122 (resid +1.8687), CO2 6.0907 vs 6.0 (+0.0907), SAP cont 72.39 (integer 72 vs lodged 73, resid −1).
Why case 6 didn't close 0240
Case 6 validated the dual-main structure (MIT p.186, pumps, rooflights, Eq-D1 fraction). But 0240 differs in cert-specific features case 6 does not exercise:
| feature | case 6 (worksheet-validated) | 0240 (unvalidated) |
|---|---|---|
| SAP code | 127 regular oil boiler | 130 condensing oil combi (Table 4b 82/73) |
| DHW path | regular boiler + 110 L cylinder → primary/storage loss | combi, NO cylinder → Table 3a keep-hot 600 kWh (combi_loss), primary_loss 0 |
| TFA | (case-6 dwelling) | 201.53 m² (different fabric/dimensions) |
| PV | none | none (the golden note's "+ PV" is STALE — solar_water_heating=N, no PV field) |
So 0240's remaining residual lives in the parts case 6 never touched — the condensing-combi (130) + no-cylinder HW path and the cert's own fabric. The combi Eq-D1 / Table 3a keep-hot path has never been pinned against a worksheet in the dual-main context.
Partial ground truth already in the 0240 JSON
The lodged renewable_heat_incentive block gives two deemed-demand figures:
water_heating = 2842.82— exactly equals the cascade's §4 HW output(64)(2842.82). So the HW demand is right; any HW residual is in the efficiency (Eq D1 combi blend), not the demand.space_heating_existing_dwelling = 13254.52vs cascade(98c)12760.9 — differ ~494 kWh (~3.7%). RHI uses its own deemed methodology so this is not a clean 1e-4 check, but it's a hint the space-heat demand or the combi figures are worth scrutinising.
What to do next — generate the right example
To close 0240 properly you need a worksheet that exercises its combi-HW path. Two options, best first:
-
Exact 0240 replica worksheet (gold standard). Re-enter 0240's lodged data into Elmhurst and export the worksheet PDF. Then build a mapper-driven fixture (mirror
_elmhurst_worksheet_001431_case6.py) and pin every line(1)..(286)at 1e-4. The first diverging line localises the residual exactly. This is the only way to get a true 1e-4 target for 0240. -
"Case 7" — case 6 with 0240's combi swapped in. If generating an exact 0240 replica is hard, generate a
001431variant that changes case 6's heating to 0240's:- Condensing oil combi, SAP code 130 (not 127 regular boiler).
- NO hot water cylinder — combi instantaneous DHW → WHC 901, Table 3a/3b combi keep-hot loss, no primary/storage loss.
- Keep the validated dual-main rads(2106)+UFH(2110) 51/49 + RR rooflights. This pins the combi Eq-D1 + Table 3 keep-hot path (the biggest unvalidated piece) against a worksheet. Whatever it reveals applies directly to 0240.
The single most important differentiator to change vs case 6: regular boiler + cylinder → condensing combi (130) with no cylinder. That is the one HW path 0240 uses that has never seen a worksheet in this archetype.
Reframing the goal
If a worksheet is genuinely unavailable, "0 residual vs the lodged register" is not achievable at 1e-4 (integer rounding). The realistic target then is the ±0.5 SAP fallback (AGENT_GUIDE §1) — and 0240's continuous SAP 72.39 vs lodged 73 is ~0.6 off, just outside it. Closing that last 0.6 still requires knowing the true (worksheet) value, so the worksheet is the unblocker either way.
0240 worksheet input specification (build THIS in Elmhurst)
Reproduce cert 0240-0200-5706-2365-8010 as closely as possible so the
worksheet is a valid 1e-4 ground truth for the cascade. All values are from the
fixture JSON (tests/.../fixtures/golden/0240-0200-5706-2365-8010.json). Match
the U-values / W-per-K targets below — those are what the cascade actually
consumes, so hitting them matters more than the exact construction wording.
Dwelling
- Detached house, construction age band J, England. Postcode LE15 9LB (drives the demand/EPC climate). 1 extension. 7 habitable rooms.
- Storey height 2.28 m. Total floor area ≈202 m² = Main ground floor 97.72 + Extension 1 ground floor 20.61 + the room-in-roof floor 83.2.
Heating — the load-bearing difference vs case 6. TWO main systems, both a condensing oil combi, SAP main heating code 130 (Table 4b winter 82 / summer 73), oil fuel, balanced flue (not fan-assisted), efficiency from the SAP table (no PCDB boiler index), central-heating pump age unknown. They heat different parts (so the p.186 MIT applies, already implemented):
- Main 1 — radiators, control 2106 (programmer + room thermostat + TRVs), 51%, serves the living area.
- Main 2 — underfloor heating in screed (R=0.75), control 2110 (time + temperature zone control), 49%, serves elsewhere.
Domestic hot water — the other key difference vs case 6. Heated from the
main system (WHC 901), oil. It is a COMBI with NO hot-water cylinder —
instantaneous DHW → SAP 10.2 Table 3a keep-hot loss 600 kWh/yr (combi_loss
600, primary_loss 0). 3 mixer showers + 1 bath; no effective WWHRS.
NB the lodged RHI water_heating = 2842.82 already equals the cascade HW output
exactly, so get the DHW demand inputs right and any residual is in the combi
efficiency (Eq D1 winter/summer blend).
- Boiler interlock: YES for 0240 (combi + room thermostat 2106, no cylinder) → no −5pp penalty, both systems run at base eff 82/73. (This is the OPPOSITE of case 6, which had a regular boiler + cylinder with no cylinder stat → −5pp. Get this right or the efficiencies — hence everything — will be off.)
Fabric — target W/K (cascade values to reproduce; total external area 328.97 m²):
| element | W/K | notes |
|---|---|---|
| Walls (29a) | 24.45 | age J, uninsulated (NI), not dry-lined, not measured |
| Roof (30) | 32.331 | Main = pitched, access to loft, insulation at ceiling, 400 mm+ ; Ext1 = pitched vaulted ceiling, no insulation (NI) |
| Floor (28) | 29.4297 | solid; Main heat-loss perimeter 36.45, Ext1 13.45 |
| Party/gable (32) | 7.84 | RR gables billed as party at U=0.25 |
| Windows (27) | 22.7407 | see below |
| Roof windows (27a) | 12.6374 | see below |
| Doors (26) | 11.1 | 2 doors, uninsulated |
| Thermal bridging (36) | 36.1867 | = 0.11 × 328.97 |
| (33) fabric total | 140.5288 | |
| (37)+vent feeds (39) | total transmission 176.7155 |
Room-in-roof (Main only): floor area 83.2 m², two gables L = 6.40 m — one Exposed, one Party (per the case-5/6 sandstone replica convention), age J. This is the same Simplified/detailed-gable RR structure case 6 validated.
Windows (all double glazed, PVC frame, glazing "DG 2002+", U≈2.0, g=0.72):
- 5 vertical wall windows: 1.4×1.3, 1.2×1.3 (orient N), 1.6×1.3, 2.5×2.0 (orient E), 1.4×1.3 (orient S, on Extension 1).
- 6 "Roof of Room" rooflights (window_wall_type 4): all 1.0×1.0, at 45°, 3 orient N + 3 orient S. These bill on (27a) and deduct from the RR residual (S0380.203) — keep them as roof-of-room, not vertical glazing.
Ventilation / lighting / other
- Natural ventilation; no mechanical ventilation, no extract fans, no chimneys/flues. 85% draught-proofed.
- Lighting: 8 LED bulbs, 100% low-energy (no CFL/incandescent).
- No PV, no solar thermal, no secondary heating, no air-conditioning.
- Electricity meter type 3 (standard), smart meter present, not export-capable.
The three things that MUST differ from case 6 (or you've just rebuilt case 6)
- Condensing oil combi, SAP code 130 (case 6 = regular oil boiler 127).
- Combi, NO cylinder → Table 3a keep-hot 600 kWh (case 6 = boiler + 110 L cylinder → primary/storage loss).
- Boiler interlock PRESENT → no −5pp (case 6 = no interlock → −5pp). Driven automatically by "combi + room thermostat, no cylinder", but verify the worksheet shows base eff 82/73, not 77/68.
Everything else (dual-main different-parts MIT, two pumps, rooflight→RR, Eq-D1 (204) share) is already implemented and validated by case 6 — the new worksheet just confirms the combi-HW path on top of that closed structure.
Pointers
- Golden pin + full slice history:
tests/domain/sap10_calculator/rdsap/test_golden_fixtures.py(cert0240-0200-5706-2365-8010, line ~83). - Case-6 fixture to mirror:
tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case6.py+ its e2e pins intest_e2e_elmhurst_sap_score.py::_FIXTURE_PINS["001431_case6"]. - Memories: project_case6_mit_two_system_rootcause (the p.186 MIT, now CLOSED), project_case6_interlock_already_done, feedback-worksheet-not-api-reference (API matches the worksheet, not the lodged register), feedback-software-no-special-handling.
- Repro 0240:
EpcPropertyDataMapper.from_api_response(json.load(...0240.json))→cert_to_inputs/cert_to_demand_inputs→calculate_sap_from_inputs. The §2.4 section helpers are UNFAITHFUL (skip the interlock penalty + two-system MIT params) — diagnose against the realcert_to_inputscascade. - Process: one slice = one commit, spec citation (page+line),
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>. SAP 10.2 only. No tolerance widening. mapper.py + cert_to_inputs.py each carry 32 pre-existing pyright errors (baseline-compare withgit stash).