docs: sweep stale handover, mark §3 Full, scaffold §4 slice plan

§3 close (LINE_31/33/36/37 exact for both non-RR Elmhurst worksheets) is
now landed across slices 344a9c9d..cf244762. HANDOVER_S3_CLOSE.md was
written as a mid-stream working brief; with §3 done it now creates doc
rot, so it's removed in favour of SPEC_COVERAGE.md as the single source
of truth.

SPEC_COVERAGE.md updates:
  - §3 marked Full (non-RR); RR sub-area deferral noted
  - §4 carries the ordered slice plan for the worksheet-driven rewrite
    (xlsx rows 207–304, line refs (42)..(65))
  - Hierarchy callout: the canonical SAP10.2 algorithm lives in the
    repo-root xlsx, not in any handover doc

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-20 15:18:46 +00:00
parent cf244762d5
commit d90827446a
2 changed files with 32 additions and 156 deletions

View file

@ -1,149 +0,0 @@
# Handover — Close §3, then Table 11 Secondary Heating
**Audience:** Fresh agent continuing the deterministic SAP 10.2 calculator
(`packages/domain/src/domain/sap/`). Read this document first, then skim
the two key source files listed below.
---
## What we're building
A deterministic SAP 10.2 calculator that replicates cert-software output
(Elmhurst, Stroma, etc.) exactly for RdSAP 10 input certs. The domain
concept is **Calculated SAP10 Performance** — see
`docs/adr/0009-deterministic-sap-calculator.md`. Progress is tracked in
`docs/sap-spec/SPEC_COVERAGE.md`.
The workflow is strict TDD: **one failing test → minimal implementation →
commit**. Each commit is one slice.
---
## Current state
### §1 Dimensions — DONE
All 6 Elmhurst fixtures pass exactly (`test_section_1_matches_elmhurst_worksheet`).
### §2 Ventilation — DONE
All 6 Elmhurst fixtures pass exactly (`test_section_2_matches_elmhurst_worksheet`).
### §3 Heat transmission — PARTIALLY DONE
What passes today:
- **Internal invariants** (all 6 fixtures): `(33) = Σ per-element`,
`(37) = (33) + (36)`.
- **Exact LINE_31 + LINE_36** (non-RR fixtures 000474 and 000490 only):
`test_section_3_non_rr_line_31_and_36_match_elmhurst_worksheet`.
What does NOT yet pass:
- **Exact LINE_33** (fabric heat loss) for any fixture. This is the
remaining §3 close task (see below).
- **RR sub-areas** (fixtures 000487, 000480, 000477, 000516): gable/
slope/stud-wall/flat-ceiling areas are not in `SapRoomInRoof`; these
fixtures are **formally deferred** — see gap notes in
`test_section_3_partial_match_against_elmhurst_worksheet`.
---
## Task A — Close LINE_33 for non-RR fixtures (investigation slice)
**Goal:** assert exact LINE_33 and LINE_37 for 000474 and 000490.
### The diagnostic gap
Running `heat_transmission_from_cert(epc, window_total_area_m2=0, door_count=actual)` on
000474 gives `fabric = 193.83 W/K`. The Elmhurst `LINE_33 = 209.11 W/K`.
The gap is +15.28 W/K — and it cannot be explained by window area alone,
because `u_wall (1.5) > u_window_eff (1.33)`, so adding windows would
*decrease* fabric heat loss, not increase it.
The gap is therefore in one or more of the other elements. Most likely
culprits, in priority order:
1. **Floor construction missing from fixture.**
`SapFloorDimension.floor_construction` is `None` in all Elmhurst
fixture files (field not set). Our `u_floor` fallback may not match
the Elmhurst value. The 000490 fixture comment records the expected
U-values explicitly: *"suspended timber ground floor on main (U=0.71),
exposed timber floor on Extension 1 (U=1.20)"*. Set the correct
`floor_construction` and `floor_insulation` codes on each
`SapFloorDimension` and see if the gap closes.
2. **Roof construction / insulation thickness missing from fixture.**
Similarly, `roof_insulation_thickness` may not be set on the building
parts. The Elmhurst cert will have a specific roof type and insulation
depth that drives a specific `u_roof`.
3. **Wall insulation re-check.** All fixtures use `wall_insulation_type=4`
(`_WALL_INSULATION_NONE`), giving `u_wall = 1.5` for cavity age B.
Confirm this matches the actual Elmhurst worksheet row.
### How to proceed
1. Read the EPC API field encoding for `floor_construction` and
`floor_insulation` in `datatypes/epc/domain/epc_property_data.py`
and `packages/domain/src/domain/ml/rdsap_uvalues.py` (the `u_floor`
function + its construction constants).
2. Look up the actual floor type for 000474 and 000490 from the PDF
(ask the user — PDFs were supplied manually; not stored in repo).
3. Set `floor_construction` + `floor_insulation` + `floor_insulation_thickness`
on the `SapFloorDimension` objects in the fixture files.
4. Re-run the debug calc (`r0.fabric` with `window_area=0`) and check
whether the gap collapses.
5. Once floor/roof are resolved, back-calculate window area:
`A_w = (LINE_33 - r0.fabric) / (window_u_eff - u_wall)`.
If the gap is now ≤ the window contribution, this formula should give
a physically plausible positive area (515 m² for a 2-storey terrace).
6. Add `WINDOW_TOTAL_AREA_M2: float` and `WINDOW_AVG_U_VALUE: float = 1.4`
constants to each non-RR fixture file.
7. Write a new parametrised test asserting exact LINE_33 and LINE_37 for
000474 and 000490. Commit as one slice.
---
## Task B — Table 11 Secondary Heating (highest-MAE-impact gap)
Per `SPEC_COVERAGE.md`, this is the **next priority after §3**.
Most boiler-main certs allocate ~10 % of space heating to a secondary
system (electric room heater or similar). We currently model 0 %. This
causes a systematic bias on the large majority of boiler certs.
**SAP 10.2 Table 11** gives the secondary fraction keyed on main-heating
type. **RdSAP 10 Appendix A** identifies the heating type from cert codes.
Starting point: `packages/domain/src/domain/sap/calculator.py` (entry
point) and `packages/domain/src/domain/sap/rdsap/cert_to_inputs.py`
(cert→inputs adapter). The `SapInputs` struct carries `main_heating_*`
fields — see how space heating demand is calculated and where a secondary
fraction would hook in.
---
## Key files to read
| File | Why |
|---|---|
| `packages/domain/src/domain/sap/worksheet/heat_transmission.py` | §3 implementation — `heat_transmission_from_cert` |
| `packages/domain/src/domain/sap/worksheet/tests/test_heat_transmission.py` | all §3 tests including the partial Elmhurst conformance test |
| `packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000474.py` | non-RR fixture to close |
| `packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000490.py` | non-RR fixture to close |
| `packages/domain/src/domain/ml/rdsap_uvalues.py` | all U-value lookups — `u_floor`, `u_wall`, `u_roof` |
| `docs/sap-spec/SPEC_COVERAGE.md` | overall progress tracker |
| `docs/adr/0009-deterministic-sap-calculator.md` | scope + architectural decisions |
Spec PDFs are at `docs/sap-spec/` — SAP 10.2 (March 2025), SAP 10.3
(Jan 2026), RdSAP 10 (June 2025).
The canonical reference Excel worksheet is at the repo root:
`2026-05-19-17-18 RdSap10Worksheet.xlsx`. A loader for it is at
`packages/domain/src/domain/sap/worksheet/tests/_xlsx_loader.py`.
---
## Test suite
```
python -m pytest packages/domain/src/domain/sap/worksheet/tests/ -q
# Should show 122 passed
```

View file

@ -2,16 +2,18 @@
Tracks which sections of the SAP 10.2 specification are implemented in `packages/domain/src/domain/sap/`. Per ADR-0009 the calculator is built from the spec, not reverse-engineered from cert data. This doc is the worksheet-driven roadmap for what remains.
Updated 2026-05-18 after S-B19.
Updated 2026-05-20 after §3 close (slices `344a9c9d``cf244762`).
The canonical SAP10.2 algorithm lives in [`2026-05-19-17-18 RdSap10Worksheet.xlsx`](../../2026-05-19-17-18%20RdSap10Worksheet.xlsx) at the repo root — each line ref `(1)..(486)` maps to a cell. The worksheet sub-modules under `packages/domain/src/domain/sap/worksheet/` implement those line refs directly; Elmhurst worksheets validate end-to-end via `tests/_elmhurst_worksheet_*.py`.
## Sections §§113 (the SAP worksheet)
| § | Topic | Module | Status | Notes / gaps |
|---|---|---|---|---|
| 1 | Dimensions | `worksheet/dimensions.py` | **Full** | Porches, conservatories, RIR deferred per ADR-0009 |
| 2 | Ventilation | `worksheet/ventilation.py` | Partial | No mechanical ventilation (MVHR/MEV), no wind-shelter factor, no pressure-test override (worksheet lines 17-18), no AP4 override (worksheet line 19) |
| 3 | Heat transmission | `worksheet/heat_transmission.py` | Partial | DwellingExposure for flats ✓, but uses global y-factor (Table R2 per-junction deferred); no roof-light separate U-value |
| 4 | Hot water + Appendix J | uses `domain.ml.demand.predicted_hot_water_kwh` | Partial | Instantaneous-system flag ✓; no shower/bath count adjustments, no FGHRS/WWHRS, no PV-diverter |
| § | xlsx rows | Topic | Module | Status | Notes / gaps |
|---|---|---|---|---|---|
| 1 | 1120 (approx) | Dimensions | `worksheet/dimensions.py` | **Full** | Porches, conservatories, RIR deferred per ADR-0009 |
| 2 | 121206 (approx) | Ventilation | `worksheet/ventilation.py` | Partial | No mechanical ventilation (MVHR/MEV), no wind-shelter factor, no pressure-test override (worksheet lines 17-18), no AP4 override (worksheet line 19) |
| 3 | 121207 | Heat transmission | `worksheet/heat_transmission.py` | **Full (non-RR)** | LINE_31/33/36/37 exact for both non-RR Elmhurst fixtures (000474, 000490). Suspended-timber + Table 20 exposed-floor routes wired. RR sub-areas (gable/slope/stud-wall) deferred until `SapRoomInRoof` carries them. Global y-factor (Table R2 per-junction deferred). |
| 4 | 207304 | Hot water + Appendix J | uses `domain.ml.demand.predicted_hot_water_kwh` | Partial (legacy) | **Worksheet-driven rewrite in progress** — see slice plan below. Instantaneous-system flag ✓; no shower/bath count adjustments, no FGHRS/WWHRS, no PV-diverter |
| 5 | Internal gains + Appendix L | `worksheet/internal_gains.py` | Full | Default occupancy + Appendix L lighting fallback |
| 6 | Solar gains + Tables 6b/6c/6d + Appendix U | `worksheet/solar_gains.py` | Partial | Per-orientation/pitch ✓; Tables 6b/6c lookups ✓; Z (overshading) hardcoded to 0.77 average; roof-lights treated as vertical |
| 7 | Mean internal temperature | `worksheet/mean_internal_temperature.py` | Partial | Living area fraction from Table 27 ✓ (per S-A7b); control_type from main_heating_control code ✓ (S-B5); control_temperature_adjustment_c always 0 |
@ -58,3 +60,26 @@ Updated 2026-05-18 after S-B19.
6. **Per-junction thermal bridging (Table R2)** — only relevant when assessor lodged junction-count data, otherwise global y is the spec answer for RdSAP-driven assessments.
Status now: 100-cert MAE 4.49, 300-cert MAE 5.45, bias near zero (±0.2). Worksheet-driven phase begins with **Secondary heating Table 11** as the next slice.
## §4 — current focus (xlsx rows 207304)
Line refs to implement in order (one TDD slice each):
| Line ref | Description | xlsx row |
|---|---|---|
| (42) | Assumed occupancy N from Appendix J formula | 209 |
| (42a)m | Hot water — mixer showers, monthly | 215 |
| (42b)m | Hot water — baths, monthly | 218 |
| (42c)m | Hot water — other uses, monthly | 221 |
| (43) | Annual average hot water (litres/day) | 223 |
| (44)m | Daily hot water usage, monthly | 226 |
| (45)m | Energy content of hot water, monthly | 229 |
| (46)m | Distribution loss = 0.15 × (45)m | 236 |
| (47)(56) | Storage volume + water storage / HIU loss path | 238263 |
| (57) | Dedicated solar storage adjustment | (after 263) |
| (58)(61) | Primary loss + combi loss | — |
| (62)m | Total monthly water heat requirement | — |
| (63a)(63d) | WWHRS / PV-diverter / Solar / FGHRS reductions | — |
| (64)m | Output from water heater | — |
| (64a)m | Electric-shower energy | — |
| (65)m | Heat gains from water heating | — |