docs: SPEC_COVERAGE §7 row flip to Full + slice progress table

§7 Mean internal temperature: Partial → Full. Six Elmhurst fixtures
conform end-to-end on (85)..(94) to ≤5e-3 °C / unitless on every per-zone
line ref every month (588 monthly assertions GREEN). Slice progress
table records the chain from per-zone η fix through legacy deletion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-20 21:48:29 +00:00
parent a7f39685a0
commit eec8fb6f4f

View file

@ -2,7 +2,7 @@
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-20 after §6 rebuild (slices `da5909de``a0ce45c9`).
Updated 2026-05-20 after §7 rebuild (slices `fa49d7b9``a7f39685`).
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`.
@ -16,7 +16,7 @@ The canonical SAP10.2 algorithm lives in [`2026-05-19-17-18 RdSap10Worksheet.xls
| 4 | 207304 | Hot water + Appendix J | `worksheet/water_heating.py` (new) + legacy `domain.ml.demand.predicted_hot_water_kwh` (calculator still calls this) | **Happy-path done** — see slice progress below. Worksheet-driven module lands line refs (42)..(65) for the combi-gas-boiler population (~70% of corpus). Cylinder + solar + renewables branches deferred. Calculator.py not yet wired to new module — next step. |
| 5 | Internal gains + Appendix L | `worksheet/internal_gains.py` | **Full** | Worksheet-driven (66)..(73), Table 5 Column A, Table 5a 9-row dispatch + heating-season mask, Appendix L L1-L12 with RdSAP §12-1 bulb defaults + Table 6d Z_L (light access factor). Wired into `calculator.py` via `cert_to_inputs`. Six Elmhurst fixtures conform end-to-end to ≤0.6% lighting / ≤0.2 W (73). |
| 6 | Solar gains + Tables 6b/6c/6d + Appendix U | `worksheet/solar_gains.py` | **Full** | Worksheet-driven (74)..(83). Table 6b g⊥ via manufacturer `window_transmission_details` first, Table 6b code lookup fallback; Table 6c FF by frame_material substring; Table 6d Z (heating column) by `OvershadingCategory`; roof windows pitched at RdSAP10 Table 24 default 45°; rooflights horizontal per §U3.2 p128. `solar_gains_from_cert` wired into `cert_to_inputs` + `calculator.py`. Six Elmhurst fixtures conform end-to-end to ≤5e-3 W on (83) + (84). |
| 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 |
| 7 | Mean internal temperature | `worksheet/mean_internal_temperature.py` | **Full** | Worksheet-driven (85)..(94) via `mean_internal_temperature_monthly`. Table 9c steps 1-9 sequential (per-zone η: (86) η_living at Ti=T_h1, (89) η_elsewhere at Ti=T_h2, (94) η_whole at Ti=(93)). Table 9b u-formula consumes weighted R for two-main case 1 (single-main is default). Wired into `calculator.py` + `cert_to_inputs` via two new `CalculatorInputs` fields. Six Elmhurst fixtures conform end-to-end to ≤5e-3 °C on all 9 line tuples + 2 scalars per month (588 assertions). Table 4e adj defaults 0 (cert-side mapping deferred — all 6 fixtures = 0); two-main case 2 (different parts heated separately) deferred. |
| 8 | Off-period temperature reduction | inline in `mean_internal_temperature.py` | Full | Table 9b implemented |
| 9 | Space heating | `worksheet/space_heating.py` | Partial | Single main system only — **no Table 11 secondary heating allocation** (10% fraction on most boilers — likely big MAE) |
| 10 | Cooling | — | Not implemented | Rare in UK dwellings; defer |
@ -144,3 +144,31 @@ Status now: 100-cert MAE 4.49, 300-cert MAE 5.45, bias near zero (±0.2). Worksh
2. **Roof window detection from cert** — same constraint as rooflights; orchestrator-side support is there (`RoofWindowInput` with pitch default), but the cert→inputs mapper has no signal to populate it.
3. **Multi-window-type per orientation** — orchestrator sums all windows of one orientation regardless of glazing type, matching the Elmhurst worksheet's per-orientation Σ. The xlsx repeats (74)..(82a) blocks per "window type" for diagnostic display; we collapse to per-orientation totals. Faithful per-type printout would need a richer result dataclass.
4. **Table 6c metal frame factor** — orchestrator ships 0.8 (spec); the deleted legacy lookup had 0.83 (silent bug). Frame-material strings outside `metal`/`aluminium`/`wood`/`pvc`/`upvc`/`composite` fall through to 0.7 default.
## §7 — slice progress (xlsx rows 372401)
| Line ref | Description | Status | Commit |
|---|---|---|---|
| — | Per-zone η fix + `MeanInternalTemperatureResult` + `mean_internal_temperature_monthly` orchestrator | ✅ | `fa49d7b9` |
| (85) | T_h1 = 21 °C scalar | ✅ | `fa49d7b9` |
| (86)m | η_living utilisation at Ti=T_h1 | ✅ | `fa49d7b9` |
| (87)m | MIT living = T_h1 u1 u2 | ✅ | `fa49d7b9` |
| (88)m | T_h2 elsewhere heating temp (Table 9) | ✅ | `fa49d7b9` |
| (89)m | η_elsewhere utilisation at Ti=T_h2 | ✅ | `fa49d7b9` |
| (90)m | MIT elsewhere = T_h2 u1 u2 | ✅ | `fa49d7b9` |
| (91) | f_LA living area fraction | ✅ | `fa49d7b9` |
| (92)m | Blended MIT = f_LA·T_1 + (1f_LA)·T_2 | ✅ | `fa49d7b9` |
| (93)m | Adjusted MIT (+ Table 4e adj, default 0) | ✅ | `fa49d7b9` |
| (94)m | η_whole utilisation at Ti=(93) — feeds §8 | ✅ | `fa49d7b9` |
| — | Two-main case 1 weighted-R via secondary_fraction | ✅ | `13c2c651` |
| — | 6-fixture ALL_FIXTURES conformance (85)..(94) ≤5e-3 | ✅ | `ff5d8c70` |
| — | `CalculatorInputs` + cert_to_inputs wiring; drop η iteration | ✅ | `8ec9da47` |
| — | Delete legacy single-η `mean_internal_temperature_c` | ✅ | `a7f39685` |
**Six Elmhurst fixtures conform end-to-end on §7 to ≤5e-3 °C / unitless** on every per-zone line ref every month — 588 monthly assertions across 6 fixtures, all GREEN. The pre-rebuild single-η path drifted by 6.6e-3 °C on 000490 Jan (92)m; the per-zone fix closes that gap by ~70×.
### Remaining §7 work
1. **Table 4e control_temperature_adjustment_c cert mapping** — orchestrator accepts the parameter but `cert_to_inputs` hardcodes 0.0 (all 6 Elmhurst fixtures = 0 anyway). Wire when a non-zero corpus emerges.
2. **Two-main case 2** (different parts heated separately) — needs (203) > 1(91) branch + conditional T_2 averaging + per-system Table 4e adj weighting. Case 1 (both heat whole house) wired with synthetic test; case 2 deferred.
3. **Auto-detect `secondary_fraction` from `epc.sap_heating.main_heating_details`** — currently always 0 in `cert_to_inputs`. Wire when a multi-main cert lands.