Model/docs/sap-spec/SPEC_COVERAGE.md
Khalim Conn-Kowlessar bb827803ac docs: SPEC_COVERAGE §8 row flip to Full + slice progress table
§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>
2026-05-20 22:55:17 +00:00

19 KiB
Raw Blame History

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 9113f30af6ab7626).

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 §§113 (the SAP worksheet)

§ 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 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)

  1. 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.
  2. 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%.
  3. 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.
  4. 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.
  5. Standing charges in cost — Table 12 note (a) gives the rule for when standing charges are included (energy use vs rating). May affect bias.
  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 — slice progress (xlsx rows 207304)

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

  1. Orchestrator water_heating_from_cert(epc, ...) that wires the leaf functions, mirroring heat_transmission_from_cert for §3.
  2. Integrate with calculator.py — replace the legacy domain.ml.demand.predicted_hot_water_kwh call.
  3. End-to-end SAP score validation against the worksheets — the real e2e test the user has been asking for.
  4. Cylinder + solar + renewables paths for full population coverage (cylinders, FGHRS, WWHRS, PV-diverter).
  5. PCDB-backed Table 3b/3c combi loss for tested boilers (000474 sits here).

§5 — slice progress (xlsx rows 305332)

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

  1. 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.
  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 332371)

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 certcert_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.

§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.

§8 — slice progress (xlsx rows 401435)

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

  1. Appendix H solar space heating (98b) — orchestrator emits solar_space_heating_monthly_kwh = (0,)·12 always. No Elmhurst fixture exercises it. Wire when a solar-space-heating cert lands.
  2. 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_kwh is replaced by water_heating_from_cert.