diff --git a/docs/sap-spec/SPEC_COVERAGE.md b/docs/sap-spec/SPEC_COVERAGE.md index d233a498..bbce6b11 100644 --- a/docs/sap-spec/SPEC_COVERAGE.md +++ b/docs/sap-spec/SPEC_COVERAGE.md @@ -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 §5 rebuild (slices `3ec56216`…`bf6a7e04`). +Updated 2026-05-20 after §6 rebuild (slices `da5909de`…`a0ce45c9`). 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`. @@ -15,7 +15,7 @@ The canonical SAP10.2 algorithm lives in [`2026-05-19-17-18 RdSap10Worksheet.xls | 3 | 121–207 | 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 | 207–304 | 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` | Partial | Per-orientation/pitch ✓; Tables 6b/6c lookups ✓; Z (overshading) hardcoded to 0.77 average; roof-lights treated as vertical | +| 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 | | 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) | @@ -118,3 +118,29 @@ Status now: 100-cert MAE 4.49, 300-cert MAE 5.45, bias near zero (±0.2). Worksh 2. **Per-window `frame_material` / `glazing_type` strings** — current orchestrator uses cert numeric codes; needs a robust mapping for site-notes-sourced certs. 3. **Rooflight derivation from cert** — orchestrator accepts `rooflight_total_area_m2` but `cert_to_inputs` doesn't yet detect rooflights (defaults to 0). Needs the `SapWindow.window_location` enum that's TODO'd in the domain model. 4. **Column B reduced-gain forms** (L12a / L16) for new-build DPER/TPER calculations — deferred until we onboard a new-build cert. + +## §6 — slice progress (xlsx rows 332–371) + +| Line ref | Description | Status | Commit | +|---|---|---|---| +| — | Table 6d Z-solar lookup (winter / heating column) | ✅ | `da5909de` | +| (74)..(81) | Per-orientation solar gain (N..NW) | ✅ | `4b83e702` | +| (82) | Roof windows — RdSAP10 Table 24 pitch 45° default, Z=1.0 | ✅ | `4b83e702` | +| (82a) | Rooflights — SAP10.2 §U3.2 horizontal, Z=1.0 | ✅ | `4b83e702` | +| (83) | Total solar gains 12-tuple | ✅ | `4b83e702` | +| — | `RoofWindowInput` + `RooflightInput` + `SolarGainsResult` | ✅ | `4b83e702` | +| — | `solar_gains_from_cert` orchestrator | ✅ | `4b83e702` | +| — | Manufacturer `window_transmission_details` cascade | ✅ | `d56fef4d` | +| — | 6-fixture ALL_FIXTURES conformance (83) + (84) ≤5e-3 W | ✅ | `377caea2` | +| — | `CalculatorInputs.solar_gains_monthly_w` + per-month lookup | ✅ | `376cdb6b` | +| — | `cert_to_inputs` swap legacy `_solar_gains_w` → orchestrator | ✅ | `cd2bd9ce` | +| — | Delete `_solar_gains_w` + `WindowInput` + `_window_inputs` | ✅ | `a0ce45c9` | + +**Six Elmhurst fixtures conform end-to-end on §6 to ≤5e-3 W on every month of (83) total solar gains and (84) total gains** (the latter cross-checks §5 LINE_73 plus §6 LINE_83). 144 monthly assertions across 6 fixtures, all GREEN. + +### Remaining §6 work + +1. **Rooflight derivation from cert** — `cert_to_inputs` passes `rooflights=()` because Elmhurst summaries lodge rooflights as `window_location = "External wall"`; needs the `SapWindow.window_location` enum work (also blocking §5 rooflight detection — common ticket). +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.