From 158c08f10f27a5688654faaa837397138f2e9a52 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 26 May 2026 21:09:32 +0000 Subject: [PATCH] docs: handover for cert 9501 (flat exposure) + HP workstream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captures session state after cert 0330 closed both Summary and API Layer 4 1e-4 gates (Slices 96-98). Cert 9501 fixtures are staged (commit 5d1778ac) but the Summary path is RED at Δ -5.25 SAP because the cert is a flat with RR + party-floor / party-ceiling — a fundamentally different cascade shape from the boiler houses we've validated. Handover quantifies the cascade-component gaps (-69.92 W/K on walls because RR gables aren't surfaced, +9.25 W/K on floor because the party-floor exposure isn't recognised, +7.36 W/K on party walls because U_party=0 isn't being applied), lists the 4 fixes likely needed in slice order, and leaves the heat-pump workstream sketch intact for when the user gives the go-ahead. Co-Authored-By: Claude Opus 4.7 --- .../docs/HANDOVER_CERT_9501_AND_HEATPUMPS.md | 222 ++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 domain/sap10_calculator/docs/HANDOVER_CERT_9501_AND_HEATPUMPS.md diff --git a/domain/sap10_calculator/docs/HANDOVER_CERT_9501_AND_HEATPUMPS.md b/domain/sap10_calculator/docs/HANDOVER_CERT_9501_AND_HEATPUMPS.md new file mode 100644 index 00000000..5bb95954 --- /dev/null +++ b/domain/sap10_calculator/docs/HANDOVER_CERT_9501_AND_HEATPUMPS.md @@ -0,0 +1,222 @@ +# Handover — Cert 9501 flat-exposure + heat-pump workstream + +You're picking up branch `feature/per-cert-mapper-validation` after +the cert 0330 boiler workflow landed (Layer 4 1e-4 gate GREEN on both +Summary and API paths, mirroring cert 001479). Two boiler certs are +now validated end-to-end against worksheets at 1e-4. The third boiler +cert (9501) is staged but RED at Δ -5.25 SAP because it surfaces a +new class of mapper gap: **flat-specific exposure**. + +## State at session start + +Recent commits: + +``` +8443c770 Slice 98: API path shower-counts + window-rounding → cert 0330 1e-4 +aa6645e3 Slice 97: API glazing_type=2 → RdSAP 10 Table 24 (DG 2002-2021) +da5e7196 Slice 96: flat-roof U-value defaults — RdSAP 10 §5.11 Table 18 col (3) +5d1778ac chore: stage cert 9501 fixtures (second boiler validation cert) +17646c8a chore: stage cert 0380 fixtures (HP pilot — deferred workstream) +460f1735 chore: stage cert 0330 fixtures (boiler pilot) +``` + +Test baselines you should see (197 pass + 9 pre-existing 001479 +Layer 1 fails): + +```bash +PYTHONPATH=/workspaces/model python -m pytest \ + backend/documents_parser/tests/test_summary_pdf_mapper_chain.py \ + domain/sap10_calculator/worksheet/tests/test_e2e_elmhurst_sap_score.py \ + domain/sap10_calculator/rdsap/tests/test_golden_fixtures.py \ + domain/sap10_ml/tests/test_rdsap_uvalues.py \ + datatypes/epc/schema/tests/test_schema_loading.py \ + --no-cov -q +``` + +Layer 4 1e-4 gates passing: + +- `test_api_001479_full_chain_sap_matches_worksheet_pdf_exactly` +- `test_api_0330_full_chain_sap_matches_worksheet_pdf_exactly` ← landed this session +- `test_summary_001479_full_chain_sap_matches_worksheet_pdf_exactly` +- `test_summary_0330_full_chain_sap_matches_worksheet_pdf_exactly` ← landed this session +- 000477 / 000516 cohort chain tests + +## Cert 9501 — staged but RED (Δ -5.25 SAP) + +Fixtures committed in `5d1778ac`: +- API JSON: `domain/sap10_calculator/rdsap/tests/fixtures/golden/9501-3059-8202-7356-0204.json` +- Summary PDF: `backend/documents_parser/tests/fixtures/Summary_000784.pdf` +- Worksheet (reference): `sap worksheets/Additional data with api/9501-3059-8202-7356-0204/dr87-0001-000784.pdf` + +Cert shape (per worksheet header): +- Property type: **Flat, Mid-Terrace** (mid-floor — `not` top-floor as + the prior handover claimed) +- Storeys (building): 4 +- Age band: B +- TFA: 113.08 m² +- Heating: mains-gas boiler, PCDB idx 19007 (Vaillant) +- Worksheet target unrounded SAP: **68.5252** + +### Cascade-component diff (Summary path vs worksheet) + +``` +TFA: 113.08 = 113.08 ✓ +walls: 148.89 vs 218.81 (Δ -69.92 ← BIG — missing RR gables) +roof: 18.10 = 18.10 ✓ (Table 18 age B col-(3) + + col-(1) compound — fine) +floor: 9.25 vs 0.00 (Δ +9.25 ← FLAT GROUND-FLOOR PARTY) +windows: 25.83 = 25.83 ✓ +doors: 5.55 = 5.55 ✓ +party: 7.36 vs 0.00 (Δ +7.36 ← worksheet U_party=0 for flat) +bridges: 25.00 vs 28.39 (Δ -3.39 ← downstream of (31) shrink) +(37) tot: 239.98 vs 296.68 (Δ -56.70 ← composite) + +ECF: 2.6326 vs 2.2563 (too high; SAP too low by 5.25) +``` + +### Worksheet element decomposition (line 187-205) + +``` +Element Net Area U A x U +(26) Doors uninsulated 1 1.85 3.00 5.55 +(27) Windows 1 10.60 2.44 25.83 +(28a) Ground floor Main 67.58 0.00 0.00 ← PARTY +(29a) External walls Main 99.26 1.70 168.74 +(29a) Roof room Main Gable Wall 1 13.50 1.70 22.95 ← RR +(29a) Roof room Main Gable Wall 2 15.95 1.70 27.12 ← RR +(30) Roof room Main Flat Ceiling 1 5.50 0.19 1.045 ← RR +(30) External roof Main 42.63 0.40 17.05 +(31) Total net area = 189.29 m² +(33) Fabric heat loss = 268.28 +(32) Party walls Main 52.54 0.00 0.00 ← PARTY +(32d) Dwelling below Main 6.85 — — ← PARTY +(35) TMP = 250 +(36) Bridges (0.150 × 189.29) 28.39 +(37) Total fabric heat loss 296.68 +``` + +### Localised mapper gaps + +The Summary path's `EpcPropertyData` has these load-bearing wrong +or missing fields: + +| Field | Currently | Should be | +|---|---|---| +| `dwelling_type` | `"Number of Storeys: flat"` (mangled by extractor) | `"Flat"` | +| `built_form` | `"Number of Storeys:"` (mangled) | `"Mid-Terrace"` (or similar) | +| `sap_flat_details` | `None` | populated with the cert's flat position | +| `sap_building_parts[0].sap_room_in_roof` | likely None | populated with the RR's gable walls + flat ceiling areas | + +**Order of attack** (each is a slice candidate): + +1. **Fix the Elmhurst extractor's `dwelling_type` / `built_form` + parsing** for this Summary PDF format. Some other section of the + PDF is bleeding into the parsed value (the "Number of Storeys:" + prefix). The extractor's anchor for `built_form` is likely + matching too eagerly; check `ElmhurstSiteNotesExtractor`. Don't + guess — read the Summary_000784.pdf header section + compare to + what `ElmhurstSiteNotesExtractor` returns. + +2. **Populate `sap_flat_details`** in `EpcPropertyDataMapper. + from_elmhurst_site_notes`. The cascade's `_dwelling_exposure` + reads from this field (see + `domain/sap10_calculator/rdsap/cert_to_inputs.py`) to gate + floor/roof contributions per RdSAP 10 §5. For cert 9501 (mid- + floor flat), both floor (party with dwelling below) and roof + (party with dwelling above) should be excluded — but the cert + does have an RR with gable walls and flat ceiling exposed + externally, so the dwelling has SOME exposed roof. + +3. **Populate `sap_room_in_roof`** with the RR-specific geometry: + gable walls 13.50 + 15.95 m², flat ceiling 5.50 m². Worksheet + lodges these as part of the Main bp's (29a) walls + (30) roof. + Cascade reads from `sap_room_in_roof.detailed_surfaces` — + check `worksheet/heat_transmission.py` for the surfacing + convention. + +4. **Re-pin or remove cert 9501 from Layer 4 tracking** once + Summary path lands at 1e-4. The RED test was NOT committed this + session (working-tree-only) — add the equivalent of + `test_summary_0330_full_chain_sap_matches_worksheet_pdf_exactly` + for cert 9501 once the gap closes. + +### API path expected gaps (after Summary lands) + +The API JSON for cert 9501 lodges `property_type=2` (Flat) and +`built_form=NR`. The API mapper needs to populate `sap_flat_details` +from `floors[]` + `roofs[]` + the GOV.UK schema's flat-specific +fields. Probable additional gaps (same pattern as Summary): +- `sap_flat_details` mid-floor exposure routing +- RR detection from cert's `roofs[].description` if the cert lodges + an attic-style roof + +## Heat-pump workstream (cert 0380 + 6 sibling ASHPs) — DEFERRED + +Per the user's direction, the 7 ASHP certs are deferred until the +boiler workflow is proven. Status: + +- Cert 0380 fixtures staged in commit `17646c8a` (worksheet target + SAP 88.5104). Original probe showed catastrophic Δ -70 SAP on + Summary path and Δ -18 SAP on API path — the Summary mapper + identified the HP as an 80%-efficient boiler. +- 6 other ASHPs share PCDB index 104568 (one uses 102421) — work + is likely shared across them. + +Work sketch (from the prior handover): + +1. **API mapper**: surface `main_heating_index_number`, set + `main_heating_category` for HPs, `main_fuel_type=29` (electric + heat pump). +2. **Cascade**: ensure `cert_to_inputs._main_heating_efficiency` + reads PCDB HP COP correctly. Investigate Table 4a/4b vs PCDB + precedence for HPs. +3. **Fuel cost**: HW + space heating on electricity tariffs + (Table 12) — check if the cascade has electric-tariff fuel-cost + plumbing wired up. +4. **Appendix N**: HP-specific efficiency adjustments (climate + + flow temperature). Likely the biggest cascade-side gap. +5. **Summary mapper**: separate slice — needs to identify HPs from + the Summary PDF's heating section. + +Do NOT start HP slices without an explicit go-ahead from the user. + +## Conventions (preserved) + +- **One slice = one commit** — stage by name. +- **AAA test convention** — literal `# Arrange / # Act / # Assert`. +- **`abs(diff) <= tol`** not `pytest.approx` (strict-pyright clean). +- **1e-4 worksheet tolerance** when worksheet is available. +- **Spec citation** in commit messages when a slice implements a + spec rule (quote RdSAP 10 / SAP 10.2/10.3 page reference). +- **Pyright net-zero per file**. Updated baselines: + - `datatypes/epc/domain/mapper.py`: 33 + - `domain/sap10_calculator/worksheet/heat_transmission.py`: 13 + - `domain/sap10_calculator/rdsap/cert_to_inputs.py`: 35 + - `datatypes/epc/domain/epc_property_data.py`: 1 (pre-existing) + - `domain/sap10_ml/rdsap_uvalues.py`: 1 (pre-existing) + +## Tooling shortcuts (unchanged) + +- EPC fetch: `OPEN_EPC_API_TOKEN` (NOT `EPC_AUTH_TOKEN`) in + `backend/.env`. +- Worksheet SAP: `pdftotext -layout -` then grep. +- Cascade-component probe: reuse the inline pattern from this + handover's "Cascade-component diff" section above. + +## Open items / known gaps from prior session + +- Pre-existing `test_roof_insulated_assumed_with_ni_thickness_uses_ + 50mm_per_section_5_11_4` in `test_heat_transmission.py` fails + with `229.99 vs 68.0 ± 2` — verified pre-existing (stash test + showed same failure without my changes). Not addressed this + session; address separately when the §5.11.4 50mm-rule cascade + path is touched. +- 8 cohort golden certs (0240, 0300, 0390-2954, 6035, 7536, 8135, + 2130, 0390-2254) are API-only with integer SAP residual pins — + if worksheets become available for any of them, migrate to + Layer 4 1e-4 chain pins (cleanest forcing function). + +Good luck. The diagnostic methodology (Summary path → worksheet 1e-4 +first, then API path catches up) is now proven on 2 boiler certs; +cert 9501 should land in ~3-5 slices once the flat-exposure plumbing +is in place.