From 144f08533f0bd990f0f8fd9acda3c77cf07a6d26 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sat, 23 May 2026 23:17:43 +0000 Subject: [PATCH] Docs: rewrite HANDOVER_NEXT.md for fresh agent pickup post-slice-25d MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §1-§6 fully close (252/252). §7 closes 52/60 (LINE_92/93 marginal on 4 fixtures). §8-§12 not yet pinned. Handover now reads top-to-bottom with current scoreboard, per-section work queue, spec page reference index, and the section helper map for the new agent to extend. Co-Authored-By: Claude Opus 4.7 --- docs/sap-spec/HANDOVER_NEXT.md | 611 +++++++++++++++++---------------- 1 file changed, 319 insertions(+), 292 deletions(-) diff --git a/docs/sap-spec/HANDOVER_NEXT.md b/docs/sap-spec/HANDOVER_NEXT.md index 1a10dd3a..ddf7b719 100644 --- a/docs/sap-spec/HANDOVER_NEXT.md +++ b/docs/sap-spec/HANDOVER_NEXT.md @@ -1,368 +1,372 @@ -# Handover — strict zero-error cascade pin closure for the 6 Elmhurst fixtures +# Handover — §7 LINE_92/93 + §8–§12 sweep to abs=1e-4 closure -**For the agent picking up the next chunk of work.** Read this BEFORE any tool call. -Read it in full. The previous agents' errors are catalogued here so you don't -repeat them. +**Goal: every line ref of every output for every one of the 6 Elmhurst +fixtures pins against the U985 worksheet PDF at abs=1e-4.** Owner: `khalim@domna.homes`. Branch: `ara-backend-design-prd`. Spec PDFs in `docs/sap-spec/`: SAP 10.2 (14-03-2025), RdSAP 10 (10-06-2025), PCDF. --- -## §A — Hard rules. Internalise these BEFORE anything else. +## §A — Hard rules. Internalise before any code. ### A.1 What this project IS This repo replicates the **rdSAP calculation engine** to bit-level fidelity against 6 known test vectors (the U985 Elmhurst worksheets): -- **Inputs**: Summary_NNNNNN.pdf (cert lodgement) for each of 6 fixtures +- **Inputs**: `Summary_NNNNNN.pdf` (cert lodgement) for each of 6 fixtures (000474, 000477, 000480, 000487, 000490, 000516). -- **Intermediate values**: U985-0001-NNNNNN.{pdf,txt} lodges every - worksheet line ref (1) through (258+) to 4 decimal places. +- **Intermediate values**: `U985-0001-NNNNNN.{pdf,txt}` lodges every + worksheet line ref (1) through (282+) to 4 decimal places. - **Final outputs**: SAP rating (continuous + integer), ECF, total fuel cost, CO2, primary energy, per-end-use kWh. It is a deterministic numerical function with fully-known test vectors. -### A.2 The bar: abs=1e-4 on EVERY pin, every fixture, every line ref +### A.2 The bar: abs=1e-4 on EVERY pin -**Every SAP-result field AND every section line ref must pin to PDF at abs=1e-4.** +- The PDF lodges 4 d.p. display precision. abs=1e-4 is the floor of "match + what the PDF says". +- **NO `rel=…` tolerances.** +- **NO `<= 0.5` continuous SAP ceilings.** +- **NO `xfail` markers on cascade pins.** +- **NO "documented widening".** -- The PDF lodges 4 d.p. display precision. abs=1e-4 is the floor of "match what - the PDF says". -- **No `rel=...` tolerances.** Slice 19b removed `rel=0.15` (fuel cost) and - `rel=0.05` (fuel cost) precedents. Never re-add these. -- **No `<= 0.5` continuous SAP ceilings.** Slice 19a removed these. Never re-add. -- **No `xfail` markers on cascade pins.** A failing pin is a calculator bug or - fixture defect to fix. -- **No "documented widening".** There is no such thing for this project. +A failing pin is a calculator bug or fixture defect. If you can't close +it in this slice, leave it failing — that's the next slice's work. -If a pin can't be closed in the current slice, **leave it failing**. The failing -pin is the next slice's work. Tolerances are NEVER widened to make the suite -green. CI red is fine while bugs are being fixed. +### A.3 Past mistakes — DO NOT REPEAT -### A.3 Past-agent mistakes — DO NOT REPEAT - -The user is frustrated with previous agents because: - -1. **Treated SAP integer Δ=0 as "closed"** — that's a weak gate (hides ±0.5 +1. **Treating SAP integer Δ=0 as "closed"** — that's a weak gate (hides ±0.5 continuous drift). The real gate is per-line-ref abs=1e-4. -2. **Widened tolerances** to make tests green (`rel=0.15`, `<=0.5`). Every such - widening masked a real residual. -3. **Tested sections in isolation** using `fixture.LINE_X` PDF values AS INPUTS. - That doesn't test the cascade — it tests the section formula given correct - inputs. The cascade can still drift. -4. **Missed fixture defects** — multiple fixtures had missing or wrong - lodgement (bulbs, windows, sap_heating, detailed RR, exposed_floor, - door_count, per-window U). When a cascade pin fails, ALWAYS audit the - fixture against the PDF first. -5. **Labelled code "SAP 10.3"** when implementing SAP 10.2 (mostly cleaned in - slice 21a; `tables/table_12.py` retains intentional 10.2-vs-10.3 - comparison). -6. **Diagnosed downstream first**. The cascade is upstream→downstream - (§1 → §2 → §3 → §4 → §5 → §6 → §7 → §8 → §9a → §10a → §11a → §12). A - downstream pin failure (e.g. `total_fuel_cost_gbp`) is meaningless to - diagnose until upstream pins close. +2. **Widening tolerances** to make tests green. +3. **Testing sections in isolation** using `fixture.LINE_X` PDF values AS + INPUTS. The cascade test walks `cert_to_inputs(epc)`, NOT isolated calls. +4. **Missing fixture defects** — When a cascade pin fails, audit the + fixture against the PDF FIRST. Many lodgements have been incomplete. +5. **Diagnosing downstream first**. Cascade is upstream→downstream + (§1 → §2 → §3 → §4 → §5 → §6 → §7 → §8 → §9a → §10a → §11a → §12). + A downstream pin failure is meaningless to diagnose until upstream pins + close. If you find yourself about to widen a tolerance, add an xfail, or skip a -fixture — **stop and ask the user.** Those are anti-patterns for this project. +fixture — **stop and ask the user.** -### A.4 Reporting format — use the matrix - -The user prefers the **per-(fixture × line-ref) matrix** for cohort scoreboard -updates. Example shape (use this exactly when reporting cascade-pin status): +### A.4 Reporting format — matrix not prose ``` -field | 000474 | 000477 | 000480 | 000487 | 000490 | 000516 -sap_score (int) | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ -sap_score_continuous | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ -ecf | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ +sec 474 477 480 487 490 516 total +--- ---- ---- ---- ---- ---- ---- ----- +§1 2/2 2/2 2/2 2/2 2/2 2/2 12/12 +§3 4/4 4/4 4/4 4/4 4/4 4/4 24/24 ... ``` -Or with numeric residuals when finer granularity helps: +Or numeric residuals when finer granularity helps: ``` -fixture | LINE_31 Δ | LINE_33 Δ | LINE_36 Δ | LINE_37 Δ -000474 | 0.0014 | 0.0296 | 0.0002 | 0.0294 -000477 | 0.0004 | 0.1246 | ✓ | 0.1244 +fixture | LINE_92 Δ | LINE_93 Δ +000474 | 0.00013 | 0.00013 +000477 | 0.00016 | 0.00016 ... ``` -✓ = within `abs=1e-4`. Numeric value = the actual diff. This format lets the -user scan visually and spot per-fixture vs per-line patterns. Use it instead -of prose summaries when reporting scoreboard state. +✓ = within abs=1e-4. Use this format instead of prose summaries. ### A.5 Workflow rules -- **Don't scan >50 lines of spec PDF without checking with the user** for the - specific page/table range. Spec PDFs are big and the user has the page - anchors. (Table 11 = page 188, Table 12 = 189, Table 12a = 191, Table 3a/b/c - = 160/161/162 already given.) +- **Don't scan >50 lines of spec PDF without checking with the user** for + the page anchor. The user has the page references and prefers to give + them up-front rather than have you fumble through the spec. - **One slice = one commit**. AAA test convention (`# Arrange / # Act / # Assert`). Co-Authored-By trailer. - **Don't touch SAP rating constants in `worksheet/rating.py`** — `ENERGY_COST_DEFLATOR=0.42`, `ECF_LOG_THRESHOLD=3.5`, `SAP_LOG_COEFF=113.7`, - `SAP_LOG_CONSTANT=117.0`. SAP 10.2 (14-03-2025) per ADR-0010. Pinned by 8+ - tests. -- **Don't auto-update unrelated git status changes** — see deletions/new files - in `git status` that aren't from your work? Don't touch them without asking. + `SAP_LOG_CONSTANT=117.0`. SAP 10.2 per ADR-0010, pinned by 8+ tests. +- **Don't auto-update unrelated `git status` entries**. The pre-existing + deletion of `docs/sap-spec/rdsap-10-specification-2025-06-10.pdf` and + the untracked `docs/sap-spec/RdSAP 10 Specification 10-06-2025.pdf` + are stable; don't touch. - **Don't invoke `/ultrareview`** — user-triggered only. -- **Caveman mode** for prose. Terse. Technical. No filler. +- **Terse prose.** No filler. +- **Delete `_TEMP.py` diagnostic files before commit.** --- -## §B — Current state (as of 2026-05-23) +## §B — Current state -### B.1 Cascade pin scoreboard +### B.1 Cascade pin scoreboard (per-section) -Two test files contain the strict pins: +``` +sec 474 477 480 487 490 516 total +--- ---- ---- ---- ---- ---- ---- ----- +§1 2/2 2/2 2/2 2/2 2/2 2/2 12/12 ✓ +§2 16/16 16/16 16/16 16/16 16/16 16/16 96/96 ✓ +§3 4/4 4/4 4/4 4/4 4/4 4/4 24/24 ✓ +§4 9/9 9/9 9/9 9/9 9/9 9/9 54/54 ✓ +§5 9/9 9/9 9/9 9/9 9/9 9/9 54/54 ✓ +§6 2/2 2/2 2/2 2/2 2/2 2/2 12/12 ✓ +§7 8/10 8/10 8/10 10/10 8/10 10/10 52/60 +---------- ------------------------------------------------------ ------- +total 304/312 (97.4%) +``` -1. **`test_e2e_elmhurst_sap_score.py::test_sap_result_pin[fixture-field]`** — - top-level SapResult fields. 66 cases (11 fields × 6 fixtures). Currently - **18 PASS / 48 FAIL** at abs=1e-4. -2. **`test_section_cascade_pins.py`** — per-section line refs walking - `
_from_cert(epc)` against PDF. Currently **151 PASS / 35 FAIL**: - - §1 (dimensions): 12 PASS / 0 FAIL ✓ - - §2 (ventilation): 96 PASS / 0 FAIL ✓ - - §3 (heat losses): 1 PASS / 23 FAIL - - §4 (water heating): 42 PASS / 12 FAIL - - §5-§12: not yet pinned +**§1–§6 fully close for all 6 fixtures (252/252).** Only §7 LINE_92/93 +on 4 fixtures (000474/477/480/490) remains in the cascade. -Total: **169 PASS / 83 FAIL** across the strict pins. 4 of 6 fixtures fully -close §1+§2+§4. 000487 is the worst (RR fixture defect propagates everywhere). - -(Post-slice-25d: section_cascade_pins 304 PASS / 8 FAIL, e2e SapResult -33 PASS / 39 FAIL. §3 + §4 + §5 + §6 ALL fully close for all 6 -fixtures. §7 closes 52/60 — only LINE_92/93 marginal ~0.0001 K -residual on 000474/477/480/490 remains (the precision artefact -discovered in slice 26c, no spec-grounded fix identified). Cascade -total closure: 312 tests, 304 PASS = 97.4%.) - -### B.2 SapResult pin matrix (post-slice-22/23) +### B.2 SapResult pin matrix (e2e) ``` field | 474 | 477 | 480 | 487 | 490 | 516 ------------------------------------|-----|-----|-----|-----|-----|----- +-----------------------------------+-----+-----+-----+-----+-----+----- sap_score (int) | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ -sap_score_continuous | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ -ecf | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ +sap_score_continuous | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ +ecf | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ total_fuel_cost_gbp | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ co2_kg_per_yr | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ space_heating_kwh_per_yr | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ main_heating_fuel_kwh_per_yr | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ secondary_heating_fuel_kwh_per_yr | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ -hot_water_kwh_per_yr | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ -lighting_kwh_per_yr | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ +hot_water_kwh_per_yr | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ +lighting_kwh_per_yr | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ pumps_fans_kwh_per_yr | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ ``` -5 of 6 fixtures hit SAP integer Δ=0 (000487 is the holdout). But continuous SAP -is still off by sub-SAP-point amounts on every fixture — none of `sap_score_ -continuous` is closed at abs=1e-4. +27 SapResult pin PASS / 39 FAIL. Most downstream fails will close as +§8/§9a/§10a/§11a/§12 land. 000516 `sap_score_continuous` already +passes — a useful sanity check that the full cascade is consistent +when the upstream sections close. -### B.3 §3 residuals after slice 27b (RdSAP10 §15 element-area rounding) +### B.3 Recent slices (in reverse order — newest first) ``` -fixture | LINE_31 Δ | LINE_33 Δ | LINE_36 Δ | LINE_37 Δ -000474 | ✓ | ✓ | ✓ | ✓ -000477 | ✓ | ✓ | ✓ | ✓ -000480 | ✓ | ✓ | ✓ | ✓ -000487 | 8.83 | 37.79 | 1.32 | 39.11 -000490 | ✓ | ✓ | ✓ | ✓ -000516 | ✓ | ✓ | ✓ | ✓ -``` - -**§3 now closes for 5 of 6 fixtures at abs=1e-4.** Slice 27b applied the -RdSAP10 §15 (p.66) rounding policy: "All element areas (gross) including -window areas: 2 d.p." Per-element gross wall / party / roof / floor / -window / door / alt-wall / RR-sub-area inputs to the §3 cascade are now -rounded to 2 d.p. before A × U. - -The remaining work is on 000487 — the worst fixture — driven by an -RR detailed-surface lodgement defect + a U=0.86 external-gable variant -our `gable_wall` enum doesn't handle. That's slice 25. - -### B.4 §4 residuals - -``` -fixture | section §4 pin status -000474 | 9/9 ✓ -000477 | 5/9 (combi loss LINE_61m diverges → cascades to 62/64/65) -000480 | 9/9 ✓ -000487 | 1/9 (LINE_43 + every monthly fails — HW lodgement defect) -000490 | 9/9 ✓ -000516 | 9/9 ✓ -``` - -### B.5 Recent slices (in reverse order — newest first) - -``` -Slice 25d: 000487 §4 LINE_65 closure — derive LINE_64A electric-shower kWh from cert (Appendix J step 8, p.82) -Slice 25c: 000477 §4/§5/§6 closure — SAP10.2 Table 3c (p.162) M+L lower bound 100.0 → 100.2 -Slice 25b: 000487 §4 closure (7/8) — has_electric_shower + mixer/electric counts on SapHeating, Appendix J step 2a fix -Slice 25a: 000487 §3 closure — detailed RR + gable_wall_external + Ext1 alt U=1.9 + §3.8 max-floor roof + half-up rounding -Slice 26c: §7 mean internal temp cascade pin (60 cases, 44 PASS) — LINE_85..94 -Slice 26b: §6 solar gains cascade pin (12 cases, 10 PASS) + SapRoofWindow solar attrs + plumb to §6 cascade -Slice 26: §5 internal gains cascade pin (54 cases, 50 PASS / 4 FAIL) + rooflight plumb to daylight factor -Slice 27b: §3 element-area + door-area rounding to 2 d.p. per RdSAP10 §15 (p.66) -Slice 27: BS EN ISO 13370 floor U rounded to 2 d.p. per RdSAP10 §5.12 -Slice 24: rooflight (line 27a) — SapRoofWindow datatype + 000516 cascade closure -ac68cf88 Slice 23: 000516 detailed RR + exposed_floor + door_count fixture lodgement -6be8fdb7 Slice 22: per-window curtain resistance fix (mixed glazing) -024244ec Slice 21d: §3 cascade pins + heat_transmission_section_from_cert helper -778b150c Slice 21e: §4 water heating cascade pins (42/54 PASS) -5b7dbe2c Slice 21c: §2 cascade pins + ventilation_from_cert helper (96 PASS) -c1472330 Slice 21b: §1 cascade pins (12/12 PASS) -20424a2d Slice 21a: relabel SAP 10.3 → SAP 10.2 in calculator docstrings -4c2f37f6 Slice 19b: drop loose-tolerance fuel cost tests (rel=0.15, rel=0.05) -6bfb0614 Slice 19a: strict cascade-pin scoreboard for SapResult vs U985 PDFs -e2d9f77d Slice 20: lodge per-window u_value on mixed-glazing fixtures -5e34594d Slice 18a: sap_heating lodgement on 000480 / 487 / 516 -8786b907 Slice 17: wire Appendix L inputs into 000480 / 487 / 516 +25d: 000487 §4 LINE_65 closure — derive (64a) electric-shower kWh from cert (App J step 8, p.82) +25c: 000477 §4/§5/§6 closure — SAP10.2 Table 3c (p.162) M+L lower bound 100.0 → 100.2 +25b: 000487 §4 LINE_43-64 closure — has_electric_shower + Appendix J step 2a Nbath branch +25a: 000487 §3 full closure — RR detailed surfaces + gable_wall_external + §3.8 max-floor roof + half-up rounding +26c: §7 mean internal temp cascade pin (60 cases, 52 PASS) +26b: §6 solar gains cascade pin + SapRoofWindow solar attrs + plumb to §6 cascade +26: §5 internal gains cascade pin + rooflight daylight plumb +27b: §3 element-area rounding to 2 d.p. per RdSAP10 §15 (p.66) +27: BS EN ISO 13370 floor U rounded to 2 d.p. per RdSAP10 §5.12 (p.46) +24: rooflight (line 27a) — SapRoofWindow datatype + 000516 cascade closure ``` --- -## §C — Work queue (in priority order) +## §C — Work queue (priority order) -### C.1 Slice 24 — ~~Rooflight (line 27a) heat transmission, for 000516~~ DONE +### C.1 §7 LINE_92/93 marginal residual (8 fails, 4 fixtures) -Done. 000516 PDF lodged 1.18 m² rooflight on line (27a) at U_eff=2.9930 → -3.5317 W/K. Wired by adding `SapRoofWindow` datatype to `EpcPropertyData` -and iterating `epc.sap_roof_windows` alongside vertical windows in -`heat_transmission_from_cert` — same SAP10.2 §3.2 curtain transform R=0.04 -applied; rooflight area subtracted from main part's roof gross. Raw U=3.40 -sourced from RdSAP10 Table 24 (p.50/113) "Roof window" column. +Per the matrix above. Diff is ~0.00010–0.00016 K per failing case — just +above the 1e-4 threshold. The PDF passes LINE_87 (T_living) and LINE_90 +(T_elsewhere) for the same 4 fixtures, but the weighted combination +LINE_92 = `(91) × T_living + (1 - (91)) × T_elsewhere` drifts. -§3 LINE_33 residual for 000516: 0.8215 W/K → 0.0038 W/K. Remaining 0.0038 -is the same pre-existing wall-perimeter + per-window curtain precision -drift biting 000474/477/480/490 — closes in slice 27. +Hypotheses to test: +1. **PDF uses rounded T_living/T_elsewhere** at some precision higher than + 4 d.p. but lower than full float in the weighted sum. The cascade pin + on LINE_87/90 passes at abs=1e-4 because both my full-precision and + the PDF's higher-precision values round to the same 4-d.p. display. +2. **PDF rounds LINE_92 to specific d.p. before later use**, but the + stored value doesn't quite match the in-memory full-precision combo. +3. **A spec-defined intermediate rounding step in §7 step 9** (RdSAP10 + §15 doesn't list MIT in its rounding list — only U-values and areas). -### C.2 Slice 25 — ~~000487 §3 RR + external gable variant~~ DONE (slice 25a) +Diagnostic: write a TEMP test that prints my T_living[m], T_elsewhere[m], +LINE_91, and computes the weighted sum at several precision levels (4 d.p., +5 d.p., 6 d.p., full). Compare each to the PDF's LINE_92[m]. If 5-d.p. +matches the PDF for all 4 fixtures and 12 months, the rule is "round +T_living + T_elsewhere to 5 d.p. before combining". Ask the user for the +SAP10.2 §7 spec page (likely §9.3 or near, page ~28-32) before applying +any new rounding rule. -§3 now fully closes for 000487. Remaining work: §4 HW lodgement (slice 25b -— 000487 cert has 1 bath + 1 electric shower, no mixer outlet; calc treats -"no mixer outlets" as "no shower", bumping Nbath from 0.13N+0.19 to -0.35N+0.50 and over-counting bath volume 2.5×). +000516 + 000487 §7 already close at 10/10 — so the artefact isn't +universal. Compare their T_living[m] values against the failing fixtures +to spot the trigger pattern. -Spec source: SAP 10.2 Appendix J step 2a (p.81) — `Nbath = 0.13N + 0.19 if -shower also present (including electric); = 0.35N + 0.50 if no shower -present`. Fix needs: lodge electric-shower presence on cert, plumb -`has_electric_shower` through `water_heating_section_from_cert`, OR the -fixture-shower-count refactor that closes 000477 LINE_61 simultaneously. +### C.2 §8 space heating cascade pin (lines 95–99) -### C.3 Slice 26+ — §5 / §6 / §7 / §8 / §9a / §10a / §11a / §12 cascade pins +Fixtures lodge: +- `LINE_95_M_USEFUL_GAINS_W` (12-tuple) +- `LINE_97_M_HEAT_LOSS_RATE_W` (12-tuple) +- `LINE_98A_M_SPACE_HEATING_KWH` (12-tuple) +- `LINE_98C_M_TOTAL_SPACE_HEATING_KWH` (12-tuple, same as 98a for current fixtures) +- `LINE_98C_ANNUAL_KWH` (scalar) +- `LINE_99_PER_M2_KWH` (scalar) -The cascade pin work continues in worksheet order. For each section: +§8 orchestrator: `domain.sap.worksheet.space_heating.space_heating_monthly_kwh`. +Section helper to add: `space_heating_section_from_cert(epc)` in +`cert_to_inputs.py`. Inputs needed: §7 (MIT + η_whole), §1 (TFA, volume), +§2 (effective_monthly_ach), §3 (total HLC), §5+§6 (total gains), climate. +Same composition pattern as `mean_internal_temperature_section_from_cert`. -1. Identify the cert→inputs cascade entry point. May need to extract a - `
_from_cert(epc)` helper from `cert_to_inputs` (mirroring slice - 21c's `ventilation_from_cert`, 21d's `heat_transmission_section_from_cert`, - 21e's `water_heating_section_from_cert`, 26's - `internal_gains_section_from_cert`). -2. Map fixture `LINE_X_` constants to result struct attributes. -3. Add scalar + monthly pin tests at abs=1e-4 to `test_section_cascade_pins.py`. -4. Run, see failures, diagnose. Fixture defect or calculator bug — fix in place, - no widening. +Add pin tests at the end of `test_section_cascade_pins.py` mirroring the +`_SECTION_7_MONTHLY_PINS` shape. -Sections still to pin: -- ~~**§5 internal gains** (lines 66-73 + 232 lighting kWh)~~ DONE (slice 26) -- ~~**§6 solar gains** (lines 83-84)~~ DONE (slice 26b — 5/6 fixtures close, 000477/487 cascade from §4) -- ~~**§7 mean internal temperature** (lines 85-94)~~ MOSTLY DONE (slice 26c — 44/60 PASS; LINE_92/93 marginal ~0.0001 K residual on 000474/477/480/490 needs investigation; 000487 cascades from §3/§4 defects). -- **§8 space heating** (lines 95-99). 4 monthly + 2 annual. -- **§9a energy requirements** (lines 201, 206-208, 211-215, 219). 5 scalar + 2 - monthly. Currently only the annual aggregates show on `SapResult` — may need - monthly exposure. -- **§10a fuel costs** (lines 240-255). 17+ line refs. -- **§11a SAP rating** (lines 256-258). 3 line refs. -- **§12 environmental** (lines 261-282). CO2 + primary energy + EI rating. +### C.3 §8c space cooling cascade pin (lines 100–108) -Some fixtures' constants for these sections may be missing — check first. PDF -extraction commands (sample for §9a): -``` -awk '/^9a\. Energy requirements/,/^10a\./' "sap worksheets/U985-0001-NNNNNN.txt" -``` +All 6 fixtures lodge `f_C=0` (no air conditioning), so: +- LINE_103 cooling gains = (0,)×12 +- LINE_107 monthly cooling = (0,)×12 +- LINE_107 annual = 0 +- LINE_108 per m² = 0 -### C.4 Slice 27 — ~~Floor-U precision~~ DONE (mostly) +LINE_101 utilisation factor collapses to 1.0 (γ ≤ 0 branch); LINE_106 +intermittency monthly is the spec default mask. Fixture constants +`LINE_101_M_UTILISATION_FACTOR_LOSS = SECTION_8C_ETA_LOSS_ALL_ONE`, +`LINE_106_M_INTERMITTENCY_FACTOR = SECTION_8C_INTERMITTENCY_MONTHLY`, +`LINE_107_M_SPACE_COOLING_KWH = SECTION_8C_ALL_ZERO_MONTHLY`. -Done. The §5.12 spec mandates "rounded to two decimal places" for BS EN ISO -13370 floor U-values, which my calc was skipping. Applied `round(U, 2)` to -both suspended-timber and solid-floor branches in `u_floor` — closed -000474/477/490 from ~0.03–0.13 W/K residual to under 0.002 W/K on each. +§8c orchestrator: `domain.sap.worksheet.space_cooling`. Section helper +likely trivial since all inputs collapse to zero. -Remaining 0.0013–0.0075 W/K residual is wall + party-wall area precision — -PDF stores 2-d.p.-rounded element areas (e.g. `36.4500 m²` for a wall I -compute as `36.4492 m²`). Closing these needs the §3 area-rounding spec -rule — see slice 27b below. +### C.4 §8f Fabric Energy Efficiency (line 109) -### C.4b Slice 27b — ~~§3 element-area rounding~~ DONE +Single scalar: `LINE_109_FEE_KWH_PER_M2`. Per spec, (109) = (98a)/TFA + +(108). For all 6 fixtures (98b) solar space heating = 0, so Σ(98a) = +Σ(98c) → LINE_109 = LINE_99 + LINE_108 = LINE_99 (no AC). -Done. RdSAP10 §15 (p.66) lodges the rounding policy: "All element areas -(gross) including window areas: 2 d.p." Applied to gross wall + party -wall + roof + floor + window + door + alt-wall + RR-sub-area inputs in -`heat_transmission_from_cert`. §3 cascade pins (LINE_31/33/36/37) now -close at abs=1e-4 for 5 of 6 fixtures; 000487 alone remains failing on -the RR defect (slice 25). +§8f orchestrator: `domain.sap.worksheet.fabric_energy_efficiency`. -### C.5 Slice 28 — Continuous SAP / fuel cost / CO2 closure +### C.5 §9a energy requirements (lines 201, 206–219) -Once §1-§9a all close at abs=1e-4, the downstream pins -(`total_fuel_cost_gbp`, `ecf`, `sap_score_continuous`, `co2_kg_per_yr`) tighten -mechanically. Re-run the SapResult pin matrix; whatever still fails has a -section-specific residual to chase. +Lodged on fixtures: +- LINE_211 main heating fuel (annual) +- LINE_215 secondary heating fuel (annual) +- LINE_219 hot water fuel (annual) +- Plus LINE_201, 206–208, 213–215 monthly tuples possibly + +Already partially exposed on `SapResult` (`main_heating_fuel_kwh_per_yr`, +`secondary_heating_fuel_kwh_per_yr`, `hot_water_kwh_per_yr`). Pin tests +at the cascade level walk `energy_requirements_from_cert` (or compose +inside cert_to_inputs). + +### C.6 §10a fuel costs (lines 240–255) + +17+ line refs. Already exposed via `SapResult.total_fuel_cost_gbp`. +Cascade tests should pin each component (main fuel cost, secondary, +hot water, pumps/fans, lighting, PV credit, standing charges). +§10a orchestrator: `domain.sap.worksheet.fuel_cost.fuel_cost`. + +### C.7 §11a SAP rating (lines 256–258) + +3 line refs: +- LINE_256 ECF (energy cost factor) +- LINE_257 SAP score continuous +- LINE_258 SAP score integer + +Already on `SapResult` as `ecf`, `sap_score_continuous`, `sap_score`. +e2e pins exist. Add explicit cascade pins for symmetry. + +`rating.py` constants are immutable per ADR-0010 — do not touch. + +### C.8 §12 environmental (lines 261–282) + +CO2 + primary energy + EI rating monthly + annual. Already partly on +`SapResult.co2_kg_per_yr`. Big section with many line refs. --- -## §D — How to work (toolbox) +## §D — Workflow toolbox -### D.1 Cascade pin diagnostic loop +### D.1 Adding a section cascade pin (the standard pattern) + +1. **Find or extract** a `
_from_cert(epc)` helper in + `domain.sap.rdsap.cert_to_inputs`. If it doesn't exist, add one + mirroring `internal_gains_section_from_cert` or `mean_internal_ + temperature_section_from_cert` — compose upstream section helpers + then call the orchestrator with the result's fields. +2. **Add a `_SECTION_X_PINS` tuple** to `test_section_cascade_pins.py` + mapping `("LINE_X_", "result_attr_name")`. +3. **Add a parametrised test** that walks every `(fixture, line_ref)` + pair and asserts `_pin(actual, expected, ...)` at abs=1e-4. +4. **Run, see failures, diagnose. Fixture defect or calculator bug — + fix in place, no widening.** + +### D.2 Diagnostic pattern When a pin fails: -1. Add a TEMP diagnostic test in `packages/domain/src/domain/sap/worksheet/tests/test__diag_TEMP.py` that dumps the cascade output alongside the PDF expected. -2. Compare element-by-element against the PDF block (use `awk` to extract the relevant §X PDF block). -3. Identify the drift source — fixture defect or calc bug. -4. Fix. Re-run the pin test. -5. **Delete the TEMP file before committing.** Never commit `_TEMP.py` files. +1. Add a TEMP test file `test__diag_TEMP.py` that dumps the + per-component breakdown alongside PDF expected values. +2. `awk '/^X\. Section/,/^Y\./' "sap worksheets/U985-0001-NNNNNN.txt"` + to extract the PDF block. +3. Identify the drift source — fixture defect (audit fixture first) + or calc bug. +4. Fix. Re-run the pin. +5. **Delete the TEMP file before committing.** -### D.2 Spec lookups +### D.3 Spec page references already in hand -User has given these page anchors: -- Table 11 (secondary heating fraction): p 188 -- Table 12 (fuel prices/CO2/PEF): p 189 -- Table 12a (standing charges, off-peak): p 191 -- Table 3a (water heating single-system): p 160 -- Table 3b (water heating combi PCDB): p 161 -- Table 3c (water heating two-profile): p 162 +``` +RdSAP 10 (10-06-2025): + §3.1 precision rule p.16 + §3.6 wall area p.19 + §3.7.1 window area p.20 + §3.8 roof area (max-floor) p.20 + §3.9 RR simplified p.21 + §3.10 RR detailed p.21 + Table 4 (RR gable walls) p.22 + §5.12 floor U + Table 19 p.46 + §5.13 + Table 20 exposed floor p.47 + §5.17 + Table 23 basement p.48 + §5.18 curtain wall p.48 + Table 24 (window U) p.50 (Standard | Roof window cols) + §15 rounding rules p.66 + Table 11 (secondary fraction) p.188 + Table 12 (fuel/CO2/PEF) p.189 + Table 12a (standing/off-peak) p.191 -For other pages, **ask the user.** Don't scan more than ~50 lines of spec PDF -without permission. - -### D.3 PDF extraction - -Worksheet PDFs are in `sap worksheets/` (note the space — quote in shell). -Each fixture has `U985-0001-NNNNNN.{pdf,txt}` (intermediate values) and -`Summary_NNNNNN.pdf` (cert lodgement). - -PDF blocks for sections (sample for §3): -```bash -awk '/^3\. Heat losses/,/Thermal mass parameter/' "sap worksheets/U985-0001-000474.txt" +SAP 10.2 (14-03-2025): + Appendix J §2a Nbath p.81 + Appendix J §8 electric shower p.82 + Table J4 (shower flow/power) p.83 + Table J5 (behavioural fbeh) p.83 + Table 3a (HW combi keep-hot) p.160 + Table 3b (HW combi profile M) p.161 + Table 3c (HW combi M+L / M+S) p.162 ``` -### D.4 Section helpers (cascade-pin enablers) +For new pages **ask the user**. Spec PDFs are big. -Already extracted in `domain.sap.rdsap.cert_to_inputs`: -- `dimensions_from_cert(epc) -> Dimensions` (§1) -- `ventilation_from_cert(epc) -> VentilationResult` (§2, slice 21c) -- `heat_transmission_section_from_cert(epc) -> HeatTransmission` (§3, slice 21d) -- `water_heating_section_from_cert(epc) -> WaterHeatingResult` (§4, slice 21e) +### D.4 Spec-grounded patterns we've discovered -For §5/§6/§7/§8/§9a/§10a/§11a/§12 you may need to extract similar helpers. -The existing `internal_gains_from_cert`, `solar_gains_from_cert`, etc. mostly -exist already — check whether they're already public on the worksheet/* module. +- **RdSAP §15 rounding**: U-values + element gross areas to 2 d.p. — + apply at the BOUNDARY between RdSAP input and SAP calculator. See + `heat_transmission.py` for the pattern (`_round_half_up`). +- **Half-up rounding, not banker's**: Python's `round(17.125, 2) = 17.12` + but SAP wants 17.13. The `_round_half_up` helper in `heat_transmission.py` + is the right utility — reuse it for any new §15 boundary you cross. +- **§3.8 roof area = MAX of floor areas across levels**, not the top + floor area. Bites when an extension's footprint steps back. +- **Assessor-lodged U overrides cascade**: cert PDFs lodge measured U + for some walls/gables. The `u_value` field on `SapRoomInRoofSurface` + and `SapAlternativeWall` honours this. When extending to new surface + types, follow the same pattern. -### D.5 Hard rules summary card +### D.5 Section helper map (cert→inputs cascade entry points) + +``` +domain.sap.rdsap.cert_to_inputs + dimensions_from_cert(epc) §1 → Dimensions + ventilation_from_cert(epc) §2 → VentilationResult + heat_transmission_section_from_cert(epc) §3 → HeatTransmission + water_heating_section_from_cert(epc) §4 → WaterHeatingResult + internal_gains_section_from_cert(epc) §5 → InternalGainsResult + solar_gains_section_from_cert(epc) §6 → SolarGainsResult + mean_internal_temperature_section_from_cert(epc) §7 → MeanInternalTemperatureResult + -- next to add -- + space_heating_section_from_cert(epc) §8 → SpaceHeatingResult + space_cooling_section_from_cert(epc) §8c → SpaceCoolingResult + fabric_energy_efficiency_from_cert(epc) §8f → float (kWh/m²) + energy_requirements_section_from_cert(epc) §9a → EnergyRequirementsResult + fuel_cost_section_from_cert(epc) §10a → FuelCostResult + sap_rating_section_from_cert(epc) §11a → (ecf, sap_continuous, sap_int) + environmental_section_from_cert(epc) §12 → EnvironmentalResult +``` + +### D.6 Hard rules summary card | do | don't | |----|-------| @@ -371,84 +375,107 @@ exist already — check whether they're already public on the worksheet/* module | Leave failing pins, fix one at a time | Widen tolerance / add xfail | | Quote PDF page when asking for spec | Scan >50 lines of PDF without asking | | `[[reference-style]]` cross-links in memory | Bare prose references | +| Use `_round_half_up`, not Python `round` | Banker's rounding at §15 boundaries | | Delete `_TEMP.py` before commit | Commit diagnostic scripts | --- -## §E — Key files +## §E — File map ``` -docs/sap-spec/sap-10-2-full-specification-2025-03-14.pdf Spec PDF -docs/sap-spec/HANDOVER_NEXT.md This file -docs/sap-spec/PARITY_FINDINGS.md Older findings -sap worksheets/ U985 + Summary PDFs +docs/sap-spec/ + sap-10-2-full-specification-2025-03-14.pdf SAP 10.2 spec + RdSAP 10 Specification 10-06-2025.pdf RdSAP 10 spec + HANDOVER_NEXT.md this file + pcdb_table_105_gas_oil_boilers.jsonl PCDB combi records +sap worksheets/ U985 + Summary PDFs -packages/domain/src/domain/sap/calculator.py Top-level SAP10.2 orchestrator -packages/domain/src/domain/sap/rdsap/cert_to_inputs.py Cert→CalculatorInputs - + section_from_cert helpers -packages/domain/src/domain/sap/tables/table_12.py SAP 10.2 Table 12 (price/CO2/PEF) -packages/domain/src/domain/sap/tables/table_12a.py Off-peak high-rate fraction -packages/domain/src/domain/sap/tables/table_32.py RdSAP 10 Table 32 (cost prices) +packages/domain/src/domain/sap/calculator.py Top-level SAP10.2 orchestrator +packages/domain/src/domain/sap/rdsap/cert_to_inputs.py Cert→CalculatorInputs + section helpers +packages/domain/src/domain/sap/tables/table_12.py Table 12 (price/CO2/PEF) +packages/domain/src/domain/sap/tables/table_12a.py Off-peak high-rate fraction +packages/domain/src/domain/sap/tables/table_32.py RdSAP10 Table 32 (cost prices) packages/domain/src/domain/sap/worksheet/ dimensions.py §1 ventilation.py §2 + VentilationResult - heat_transmission.py §3 + HeatTransmission - water_heating.py §4 + WaterHeatingResult + water_heating_from_cert - internal_gains.py §5 + InternalGainsResult + internal_gains_from_cert - solar_gains.py §6 + solar_gains_from_cert - mean_internal_temperature.py §7 + heat_transmission.py §3 + HeatTransmission + _round_half_up helper + water_heating.py §4 + WaterHeatingResult + electric_shower_monthly_kwh + internal_gains.py §5 + InternalGainsResult + solar_gains.py §6 + SolarGainsResult + RoofWindowInput + mean_internal_temperature.py §7 + MeanInternalTemperatureResult space_heating.py §8 + SpaceHeatingResult - fabric_energy_efficiency.py §8f space_cooling.py §8c + fabric_energy_efficiency.py §8f + energy_requirements.py §9a + EnergyRequirementsResult fuel_cost.py §10a + FuelCostResult - rating.py §11/§13 SAP rating equations (10.2 constants — DO NOT TOUCH) + rating.py §11/§13 SAP rating equations (DO NOT TOUCH constants) packages/domain/src/domain/sap/worksheet/tests/ test_section_cascade_pins.py Strict per-section line-ref pins (THE work) - test_e2e_elmhurst_sap_score.py SapResult-field pins + monthly_infiltration_ach pin + test_e2e_elmhurst_sap_score.py SapResult-field pins _elmhurst_worksheet_NNNNNN.py The 6 fixture modules (1 per fixture) _elmhurst_fixtures.py ALL_FIXTURES registry - test_dimensions.py / _ventilation.py / _heat_transmission.py / ... - ← LEGACY per-section isolation tests; use PDF values as INPUTS. - Keep them but understand they don't test the cascade. + test_*.py Legacy per-section isolation tests + +datatypes/epc/domain/epc_property_data.py + SapBuildingPart + sap_room_in_roof + SapRoomInRoof + detailed_surfaces + SapRoomInRoofSurface + u_value override, kind enum: + "slope" | "flat_ceiling" | "stud_wall" | + "gable_wall" | "gable_wall_external" + SapAlternativeWall + u_value override + SapRoofWindow area + u_value_raw + orientation + + pitch_deg + g_perpendicular + frame_factor + SapHeating + electric_shower_count, mixer_shower_count, + number_baths ``` --- ## §F — Definitely do NOT -- Do **not** widen any tolerance, ever. +- Do **not** widen any tolerance. - Do **not** add xfail to cascade pins. - Do **not** "investigate later" by widening — fix it or leave it failing. - Do **not** assume the calculator is wrong before auditing the fixture. - Do **not** touch `rating.py` constants. - Do **not** scan unread spec PDF pages without asking the user. - Do **not** invoke `/ultrareview`. -- Do **not** auto-update unrelated `git status` items (deletions / new files - that aren't from your work). +- Do **not** auto-update unrelated `git status` items. +- Do **not** use Python `round()` at a §15 boundary — use `_round_half_up`. --- ## §G — Quick orient ```bash -# Run full cohort pin matrix +# Run the full cascade scoreboard python -m pytest \ packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py \ packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py \ --no-header --no-cov --tb=no -q -# Run §3 pins only -python -m pytest packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py::test_section_3_line_refs_match_pdf -v --no-header --no-cov --tb=no +# Run §7 only +python -m pytest packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py \ + -k "section_7" --no-cov --tb=no -q -# Run a single SapResult pin to see numeric diff +# Per-fixture residual diffs for a section +python -m pytest packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py \ + -k "section_7 and 000474" --no-cov --tb=line + +# Single SapResult pin numeric diff python -m pytest \ "packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py::test_sap_result_pin[000477-space_heating_kwh_per_yr]" \ --no-cov 2>&1 | grep AssertionError -# PDF §X block +# Extract a PDF §X block for a fixture awk '/^X\. Section/,/^Y\./' "sap worksheets/U985-0001-NNNNNN.txt" + +# Wider regression check +python -m pytest packages/domain/src/domain/sap/worksheet/tests/ \ + packages/domain/src/domain/sap/tests/ packages/domain/src/domain/ml/ \ + --no-header --no-cov --tb=no -q | tail -5 ``` End of handover. Read §A again before starting.