# Handover — closing golden cert 0240-0200-5706-2365-8010 to 1e-4 Point-in-time note. Start from [`AGENT_GUIDE.md`](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 with `git 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=73` in 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.52` vs 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: 1. **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. 2. **"Case 7" — case 6 with 0240's combi swapped in.** If generating an exact 0240 replica is hard, generate a `001431` variant 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) 1. **Condensing oil combi, SAP code 130** (case 6 = regular oil boiler 127). 2. **Combi, NO cylinder** → Table 3a keep-hot 600 kWh (case 6 = boiler + 110 L cylinder → primary/storage loss). 3. **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` (cert `0240-0200-5706-2365-8010`, line ~83). - Case-6 fixture to mirror: `tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case6.py` + its e2e pins in `test_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 real `cert_to_inputs` cascade. - Process: one slice = one commit, spec citation (page+line), `Co-Authored-By: Claude Opus 4.8 `. SAP 10.2 only. No tolerance widening. mapper.py + cert_to_inputs.py each carry 32 pre-existing pyright errors (baseline-compare with `git stash`).