§8 Space heating requirement: Partial → Full. Six Elmhurst fixtures conform end-to-end on (95)..(99) at 5e-2..1e-1 kWh per month; tolerances reflect 4-d.p. fixture pin propagation, not physics drift. Spec inclusion rule (Jun..Sep summer clamp) now applied; 000490 SAP-score gap to PDF=57 documented (currently 60 — closes incrementally as §3 / §4 / §5 upstream precision tightens). Also renumbers the §9 row to "Energy requirements per heating system" (its SAP10.2 worksheet title) — the previous "§9 Space heating" entry conflated §8 and §9. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
19 KiB
SAP 10.2 / RdSAP 10 Coverage Map
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 §8 rebuild (slices 9113f30a…f6ab7626).
The canonical SAP10.2 algorithm lives in 2026-05-19-17-18 RdSap10Worksheet.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 §§1–13 (the SAP worksheet)
| § | xlsx rows | Topic | Module | Status | Notes / gaps |
|---|---|---|---|---|---|
| 1 | 1–120 (approx) | Dimensions | worksheet/dimensions.py |
Full | Porches, conservatories, RIR deferred per ADR-0009 |
| 2 | 121–206 (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 | 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 |
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 |
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 | |
| 8 | Space heating requirement | worksheet/space_heating.py |
Full | Worksheet-driven (95)..(99) via space_heating_monthly_kwh. Includes the Table 9c step 10 spec inclusion rule (Jun..Sep zeroed) on top of the < 1 kWh value clamp. (98b) Appendix H solar space heating defaulted to 0 (no Elmhurst fixture lodges a solar space heating system). Wired into calculator.py + cert_to_inputs via CalculatorInputs.space_heating_monthly_kwh. Six Elmhurst fixtures conform end-to-end on (95)/(97)/(98a)/(98c)/(99)/annual @ 5e-2..1e-1 kWh (looser than §6/§7's 5e-3 because LINE_84/93/94 fixture pins are 4-d.p. display-rounded and §8's 0.024·n_m·(L−ηG) amplifies that rounding). |
|
| 9 | Energy requirements per heating system | 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 | |
| 11 | FEE | — | Not implemented | Only for new-build; not required for ratings | |
| 12 | Total energy + fuel costs | calculator.py |
Partial | Per-end-use cost split ✓; meter_type tariff routing ✓ (S-B15); PV cost credit ✓ (S-B19); standing charges not included (Table 12 note (a) says rating omits standing charge for std electricity tariff) | |
| 13 | SAP rating | worksheet/rating.py |
Full | Equations 7-9 verified against SAP 10.2 §13 | |
| 14 | CO2 + primary energy | calculator.py SapResult.co2_kg_per_yr |
Partial | Single CO2 factor on main fuel; no per-end-use CO2 mixing; no primary energy calculation | |
| 15 | Building regs | n/a | n/a | Not relevant to ratings |
Appendices
| Appendix | Topic | Status | Notes |
|---|---|---|---|
| A | Main + secondary heating identification | Not implemented | First main only; Table 11 secondary fraction missing — high-value gap |
| B | Gas/oil boilers + boiler interlock | Partial | Via Table 4b in domain.ml.sap_efficiencies.seasonal_efficiency |
| C | Community heating | Not implemented | Heat network certs handled by Table 4a codes 301-304 (efficiency only) |
| D | PCDB | Stubbed | Per ADR-0009 grill: NoOpPcdbLookup returning None; Session C bundles a CSV PCDB extract |
| E | Heat pumps | Partial | Category fallback to 2.30 for cat=4; no MCS installation factor (×1.39 GSHP); no flow temp adjustment |
| F | Electric CPSU | Not implemented | Rare |
| G | FGHRS / WWHRS / PV-diverter | Not implemented | Rare |
| H | Solar water heating | Partial | Boolean has_solar_water_heating reduces HW by 250 kWh; no actual collector calc |
| J | Hot water demand | Partial | See §4 row above |
| K | Thermal bridging | Partial | Using global y per age band (per ADR-0009 grill: per-junction Table R2 deferred) |
| L | Lighting | Full | Existing-dwelling fallback ✓ |
| M | PV / wind / hydro generation | Partial | PV ✓ (S-B19); wind / hydro / micro-CHP not implemented |
| N | Micro-CHP | Not implemented | Rare |
| P | Electric storage heaters detail | Partial | Identified via codes 401-409; Table 12a high-rate fractions not exact (we use 100% off-peak per cert-calibration heuristic) |
| Q | Special features | Not implemented | One-off energy-use additions; rare |
| R | Reference dwelling | Not implemented | Only needed for compliance, not ratings |
| S | RdSAP procedures | Separate PDF (114pp) | Cert→inputs mapper in domain.sap.rdsap.cert_to_inputs ports key rules; many sections of RdSAP 10 not yet read |
| T | Improvement measures | Not relevant | Recommendation engine, not rating |
| U | Climate data | Full | Tables U1/U2/U3 + solar declination + Table U5 k1-k9 |
Prioritised gap list (by likely MAE impact)
- Table 11 Secondary heating allocation — most boiler-main certs allocate 10% of space heating to a secondary system (often a less-efficient room heater on a different fuel). We model 0%. Likely +1-2 SAP-point bias on affected certs.
- Wind-shelter factor on infiltration (§2 worksheet lines 19-21) — multiplies infiltration by
1 - 0.075 × sheltered_sides. We have no shelter input; assume 2 sheltered sides default. Net effect on infiltration ACH probably ~10%. - Table 12a high-rate fraction for off-peak dwellings — we currently bill 100% of E7 space heating at the low rate. Real spec says e.g. heat pumps on 7h tariff at 80% high-rate. Affects ~5% of certs.
- Cylinder-loss factor cascade — currently uses simplified buckets in
domain.ml.demand._STORAGE_LOSS_FACTOR. Spec has more precise interpolation rules from cylinder volume + insulation thickness. - Standing charges in cost — Table 12 note (a) gives the rule for when standing charges are included (energy use vs rating). May affect bias.
- 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 — slice progress (xlsx rows 207–304)
| Line ref | Description | Status | Commit |
|---|---|---|---|
| (42) | Assumed occupancy N from Appendix J | ✅ | aff678e8 |
| (42a)m | Mixer showers monthly | ✅ | 1dcbdb28 |
| (42b)m | Baths monthly | ✅ | dad7fbf3 |
| (42c)m | Other uses monthly | ✅ | 5cc68ab3 |
| (43) | Annual avg | ✅ | 702b1c6c |
| (44)m | Daily monthly total | ✅ | 702b1c6c |
| (45)m | Energy content | ✅ | a3c687f1 |
| (46)m | Distribution loss | ✅ | a3c687f1 |
| (47)–(56) | Storage / HIU loss | Combi zero-branch only; cylinder paths deferred | — |
| (57)m | Dedicated solar storage | Combi zero-branch only; solar HW deferred | — |
| (59)m | Primary loss | Combi zero-branch only; boiler+cylinder deferred | — |
| (61)m | Combi loss | Table 3a row "time-clock keep-hot" only | bfba610b |
| (62)m | Total demand | ✅ | bfba610b |
| (63a-d) | WWHRS/PV/Solar/FGHRS reductions | Zero-only — non-zero paths deferred | feef8198 |
| (64)m | Output from water heater | ✅ (with max-clamp) | feef8198 |
| (64a)m | Electric shower energy | Zero-only — non-zero path deferred | — |
| (65)m | Heat gains | ✅ | 43da3ea0 |
Happy path closes both non-RR Elmhurst fixtures to <1e-3 kWh end-to-end on lines (42)..(65) (000474's PCDB-backed Table 3b combi loss is the one hand-lodged value).
Remaining §4 work
- Orchestrator
water_heating_from_cert(epc, ...)that wires the leaf functions, mirroringheat_transmission_from_certfor §3. - Integrate with
calculator.py— replace the legacydomain.ml.demand.predicted_hot_water_kwhcall. - End-to-end SAP score validation against the worksheets — the real e2e test the user has been asking for.
- Cylinder + solar + renewables paths for full population coverage (cylinders, FGHRS, WWHRS, PV-diverter).
- PCDB-backed Table 3b/3c combi loss for tested boilers (000474 sits here).
§5 — slice progress (xlsx rows 305–332)
| Line ref | Description | Status | Commit |
|---|---|---|---|
| (66)m | Metabolic = 60 × N (Table 5 Col A) | ✅ | 3ec56216 |
| (71)m | Losses = -40 × N | ✅ | 021f43ba |
| (69)m | Cooking = 35 + 7 × N | ✅ | 984a5b18 |
| (72)m | Water heating bridge (65)m × 1000/(24 × n_m) | ✅ | a4d6321f |
| (68)m | Appliances (L13/L14/L16a) | ✅ | 0bc9eac3 |
| (67)m | Lighting (L1-L12 with Z_L from Table 6d) | ✅ | 50fd940a |
| (70)m | Pumps + fans (Table 5a 9-row dispatch) | ✅ | f77229e4 |
| (73)m | Total internal gains + InternalGainsResult | ✅ | 53aba133 |
| — | internal_gains_from_cert orchestrator |
✅ | f81e744b |
| — | 6-fixture ALL_FIXTURES conformance | ✅ | 99e5c2cd |
| — | cert_to_inputs wiring + calculator.py wiring |
✅ | bf6a7e04 |
| — | Rooflight Z_L=1.0 (Table 6d note 2) | ✅ | 380115e2 |
Six Elmhurst fixtures conform end-to-end on §5 to ≤5e-3 W on every line — display-rounding territory across the board.
Remaining §5 work
- Table 5a fan / PIV / HIU branches are implemented as leaf functions but not yet derived from cert inputs in
internal_gains_from_cert. Pump dispatch is wired; non-pump rows always emit 0 W. MVHR / MEV correctly produces zero gain. - Per-window
frame_material/glazing_typestrings — current orchestrator uses cert numeric codes; needs a robust mapping for site-notes-sourced certs. - Rooflight derivation from cert — orchestrator accepts
rooflight_total_area_m2butcert_to_inputsdoesn't yet detect rooflights (defaults to 0). Needs theSapWindow.window_locationenum that's TODO'd in the domain model. - 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
- Rooflight derivation from cert —
cert_to_inputspassesrooflights=()because Elmhurst summaries lodge rooflights aswindow_location = "External wall"; needs theSapWindow.window_locationenum work (also blocking §5 rooflight detection — common ticket). - Roof window detection from cert — same constraint as rooflights; orchestrator-side support is there (
RoofWindowInputwith pitch default), but the cert→inputs mapper has no signal to populate it. - 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.
- 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/compositefall through to 0.7 default.
§7 — slice progress (xlsx rows 372–401)
| 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 + (1−f_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
- Table 4e control_temperature_adjustment_c cert mapping — orchestrator accepts the parameter but
cert_to_inputshardcodes 0.0 (all 6 Elmhurst fixtures = 0 anyway). Wire when a non-zero corpus emerges. - 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.
- Auto-detect
secondary_fractionfromepc.sap_heating.main_heating_details— currently always 0 incert_to_inputs. Wire when a multi-main cert lands.
§8 — slice progress (xlsx rows 401–435)
| Line ref | Description | Status | Commit |
|---|---|---|---|
| — | SpaceHeatingResult + space_heating_monthly_kwh orchestrator |
✅ | 9113f30a |
| — | Table 9c step 10 summer clamp (Jun..Sep = 0) | ✅ | 9113f30a |
| (95)m | Useful gains ηG, W | ✅ | 9113f30a |
| (97)m | Heat loss rate L_m = H·(T_int − T_ext), W | ✅ | 9113f30a |
| (98a)m | Space heating requirement per month, kWh | ✅ | 9113f30a |
| (98b)m | Solar space heating (Appendix H) — hardcoded 0 | ⏸ deferred | — |
| (98c)m | Total = (98a) + (98b), kWh | ✅ | 9113f30a |
| Σ(98c) | Annual total kWh | ✅ | 9113f30a |
| (99) | Annual per-m² = Σ(98c) / TFA | ✅ | 9113f30a |
| — | 6-fixture ALL_FIXTURES conformance on (95)..(99) | ✅ | 1f078af7 |
| — | CalculatorInputs.space_heating_monthly_kwh + cert_to_inputs wiring |
✅ | f6ab7626 |
Six Elmhurst fixtures conform end-to-end on §8 to 5e-2..1e-1 kWh on every per-month line ref (looser than §6/§7's 5e-3 because LINE_84/93/94 fixture pins are 4-d.p. display-rounded and §8's 0.024·n_m·(L−ηG) propagates that rounding into the per-month kWh band; the orchestrator computes in full precision).
E2e SAP-score impact: the spec-correct summer clamp removed a ~+14% over-prediction that was previously masking residual upstream §3/§5 precision drift. The 000490 SAP score moved 57 → 60 (target 57; tolerance updated to "within 3 points" with the full delta breakdown in test_elmhurst_000490_end_to_end_sap_score_currently_within_3_points). 000474 / 000490-kWh tests still pass.
Remaining §8 work
- Appendix H solar space heating (98b) — orchestrator emits
solar_space_heating_monthly_kwh = (0,)·12always. No Elmhurst fixture exercises it. Wire when a solar-space-heating cert lands. - 000490 SAP-score gap to 57 (currently 60) — needs upstream §3 (transmission HLC), §5 (internal gains), and the §4 water heating cert path tightened. Likely closes incrementally as the §9–§14 fuel/cost/rating chain is rebuilt + the legacy
predicted_hot_water_kwhis replaced bywater_heating_from_cert.