Model/docs/sap-spec/SPEC_COVERAGE.md
Khalim Conn-Kowlessar 53c393bfba docs: SPEC_COVERAGE §9a row + slice progress table + PCDB gap-list update
Adds §9a as a first-class row (consistent with §8c/§8f sub-section precedent). The §9 row updates from "Partial — single main only, no Table 11 secondary" to "Full (single-main + Table 11 secondary)" with a deferred list naming the four remaining slices: two-main system, cooling SEER, Table 4f pumps/fans breakdown, Appendix Q.

The PCDB gap-list entry (item 1) updates to flag §9a ALL_FIXTURES PDF-derived LINE_206/(211)/(215) pinning as blocked. The 88.2% figure that surfaced from a previous agent's notes cannot be verified without PCDB — corrected the narrative accordingly.

Per-§9a slice progress table mirrors §8c/§8f structure with line refs (201)..(238), commit shorthands, and a Remaining work list naming six follow-ups (PCDB integration, two-main, cooling SEER, Table 4f, Appendix Q, (238) on SapResult).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 08:41:23 +00:00

34 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-21 after §8c (slices cf28eec4f3797066), §8f (43cc16bc), and §9a single-main slices (2b5fc6a5380b6781).

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).
8c Space cooling requirement worksheet/space_cooling.py Full (no-AC zero-branch) Worksheet-driven (100)..(108) via space_cooling_monthly_kwh. Table 10a η_loss with 8-dp γ rounding + L=0 sentinel; Table 10b Q_cool with Jun-Aug inclusion mask + post-f_C × f_intermittent 1-kWh clamp per spec line 10321. Internal temp hardcoded 24 °C. Wired into calculator.py (MonthlyEntry.space_cool_requirement_kwh + SapResult.space_cooling_kwh_per_yr) + cert_to_inputs via CalculatorInputs.space_cooling_monthly_kwh. Six Elmhurst fixtures all has_fixed_air_conditioning=False → (107), (108) ≡ 0 — ALL_FIXTURES asserts (101)/(103)/(106)/(107)/(108) per fixture; synthetic positive test (γ=1 closed-form) covers the algebra. Deferred: RdSAP cooled-area defaulting rule + cooling_gains_from_cert (drops Table 5a items per spec 10280) + Table 10c SEER → cooling fuel kWh + fuel cost cascade (first non-zero-cooling cert triggers this slice).
8f Fabric Energy Efficiency (line ref (109)) worksheet/fabric_energy_efficiency.py Full (rating-conditions transparency) Spec line 7898: (109) = (98a) ÷ (4) + (108). fabric_energy_efficiency_kwh_per_m2_yr(...) is a single-scalar free function — no dataclass. Σ(98a) (pre Appendix H solar) added as SpaceHeatingResult.space_heating_requirement_kwh_per_yr so spec literal is honoured; for our corpus (98b)=0 so Σ(98a) = Σ(98c). cert_to_inputs precomputes FEE from local SpaceHeatingResult + SpaceCoolingResult; calculator passes through to SapResult.fabric_energy_efficiency_kwh_per_m2_yr. Six Elmhurst fixtures all (108)=0 → LINE_109 = LINE_99 exactly. §11 compliance conditions (different ventilation / HW / lighting / gains column) are deferred — current FEE is a rating-conditions transparency output, not a §11 compliance figure. Future §11 slice invokes the same function with §11-conditions upstream values.
9 Energy requirements per heating system (§9a worksheet block) worksheet/energy_requirements.py Full (single-main + Table 11 secondary) Worksheet-driven (201)..(215) via space_heating_fuel_monthly_kwh. (211)m = (98c)m × (204) × 100 / (206) and (215)m mirror; Σ → annuals. EnergyRequirementsResult dataclass mirrors the full §9a worksheet shape with 16 fields incl. (203)/(205)/(207)/(209)/(213)/(221) zero-branch placeholders. cert_to_inputs precomputes and stashes on CalculatorInputs.energy_requirements composite slot; calculator's _solve_month reads precomputed (211)m/(215)m directly (stops doing q/η inline). SapResult adds main_2_heating_fuel_kwh_per_yr and space_cooling_fuel_kwh_per_yr flat scalars (both zero in scope A). Deferred: (203)/(205)/(207)/(213) two-main system (first multi-main cert) + (209)/(221) cooling SEER (Table 10c lookup — first fixed-AC cert) + (230a)-(230h)/(231) Table 4f pumps/fans breakdown + (236)/(237) Appendix Q items + ALL_FIXTURES LINE_206/(211)/(215) PDF-derived pins (blocked on PCDB — see Boiler / heat-pump efficiency Manufacturer override in Prioritised gap list).
10 Cooling (spec heading — same content as §8c worksheet block) worksheet/space_cooling.py Full (no-AC zero-branch) See §8c row above.
11 FEE compliance conditions worksheet/fabric_energy_efficiency.py (the function exists; §11 conditions don't run yet) Partial (109) formula exposed via fabric_energy_efficiency_kwh_per_m2_yr. Spec §11 conditions (lines 2152-2164: 2-4 extract fans, instantaneous-electric shower, 125 l/day water, 185 lm/m² lighting at 66.9 lm/W, column (B) heating gains, column (A) cooling gains, etc.) not implemented — only relevant for new-build compliance.
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. Boiler / heat-pump efficiency Manufacturer override (PCDB integration)MainHeatingDetail lodges the PCDB pointer (main_heating_index_number) but no scalar efficiency. With NoOpPcdbLookup (ADR-0009 grill outcome #1) still in place, cert_to_inputs falls back to the SAP10 Table 4a category default (typically 0.80 for gas boilers, SCOP 2.30 for heat pumps) on every cert. Per ADR-0010 §4 this accounts for ~19 SAP points of MAE on heat-pump certs and most per-cert variance on the 78 % of gas-boiler certs lodging main_heating_data_source=1 (PCDB-typical 0.880.94 vs 0.80 default). Directly visible on 000490 e2e: inputs.main_heating_efficiency = 0.80 vs an unverified PDF Manufacturer-declared figure that surfaced from a previous agent's notes — the actual PDF value cannot be confirmed without PCDB. Closing requires a real PCDB CSV ingest + PcdbLookup Protocol impl + precedence wiring in cert_to_inputs._main_heating_efficiency and _water_efficiency_with_category_inherit. Promoted to prerequisite under ADR-0010, not a section-sweep slice. §9a ALL_FIXTURES PDF-derived LINE_206/(211)/(215) pinning is blocked on this — until PCDB lands, §9a conformance is at the synthetic + cert-round-trip level only (no PDF cross-check).
  2. 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.
  3. 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%.
  4. 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.
  5. 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.
  6. Standing charges in cost — Table 12 note (a) gives the rule for when standing charges are included (energy use vs rating). May affect bias.
  7. 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.

§8c — slice progress (xlsx rows 435466)

Line ref Description Status Commit
SpaceCoolingResult + space_cooling_monthly_kwh orchestrator cf28eec4
Table 10a utilisation_factor_loss leaf (γ=G/L, 8-dp rounding, L=0 sentinel, γ≤0 / γ=1 / γ>0∧≠1 branches) cf28eec4
(100)m Heat loss rate L_m = H·(24 T_e), W (Ti hardcoded 24 °C) cf28eec4
(101)m η_loss per month, Table 10a cf28eec4
(102)m Useful loss η·L, W cf28eec4
(103)m Cooling gains W (Table 5a exclusions deferred — see below) pass-through; Table 5a exclusion ⏸ deferred cf28eec4
(104)m Q_whole continuous = 0.024·(G η·L)·n_m, kWh; Jun-Aug only cf28eec4
Σ(104) Annual Q_whole total kWh cf28eec4
(105) Cooled-area fraction f_C — hardcoded 0 in cert_to_inputs (zero-branch); RdSAP cooled-area defaulting ⏸ deferred cf28eec4
(106)m Intermittency factor 0.25, Jun-Aug only cf28eec4
(107)m Q_cool = (104)·(105)·(106) with negative-or-< 1 kWh clamp per spec 10321 cf28eec4
Σ(107) Annual Q_cool kWh cf28eec4
(108) Annual per-m² = Σ(107) / TFA cf28eec4
6-fixture ALL_FIXTURES conformance on (101)/(103)/(106)/(107)/(108) 3b9fa936
CalculatorInputs.space_cooling_monthly_kwh + MonthlyEntry.space_cool_requirement_kwh + SapResult.space_cooling_kwh_per_yr + cert_to_inputs wiring f3797066
(105) Table 10c SEER → cooling fuel kWh ⏸ deferred

Six Elmhurst fixtures conform end-to-end on §8c to exact equality on (101)/(103)/(106)/(107)/(108) — no tolerance needed because every fixture has has_fixed_air_conditioning=False → f_C=0 → (107), (108) ≡ 0 and (101) ≡ 1.0 (γ=0 every month). (100)/(102)/(104) carry computed non-zero values per fixture and are not pinned in the ALL_FIXTURES test; the algebra is exercised by the synthetic-positive leaf/orchestrator tests in test_space_cooling.py (γ=1 closed-form branch with hand-computed 4.65 kWh end-to-end + γ ≤ 0 / γ=1 / γ>0∧≠1 / γ≈1 boundary / L=0 sentinel leaf tests).

E2e SAP-score impact: none. All Elmhurst fixtures lodge has_fixed_air_conditioning=False; cooling contribution to the rating is structurally zero. The wiring is in place so that the first non-zero-cooling cert triggers the (103) Table 5a exclusion + (105) RdSAP cooled-area defaulting slice atomically.

Remaining §8c work

  1. Table 5a exclusion in cooling gains (103)cert_to_inputs currently passes monthly_total_gains_w = (0,)*12 to the orchestrator. Spec line 10280 requires cooling G to drop Table 5a items (pumps/fans, intermittent appliances) and use column (A) of Table 5 throughout. Needs a cooling_gains_from_cert helper mirroring internal_gains_from_cert + solar_gains_from_cert. Triggered by the first cooling-enabled cert.
  2. RdSAP cooled-area defaulting (105)cert_to_inputs currently passes cooled_area_fraction = 0.0 always. For has_fixed_air_conditioning=True certs the RdSAP 10 spec gives a defaulting rule (cooled area not lodged; cert side derives from dwelling type or assumes whole-dwelling). Needs PDF lookup. Triggered by the first cooling-enabled cert.
  3. Table 10c SEER + cooling fuel kWh + fuel cost cascade — Q_cool (107) needs to drive an electricity-fuel-kWh path (Q_cool ÷ SEER) and through Table 12 onto SapResult.total_fuel_cost_gbp. Currently the cooling kWh sits on MonthlyEntry.space_cool_requirement_kwh but doesn't propagate into fuel costs or CO2. Triggered by the first cooling-enabled cert.
  4. §8f Fabric Energy Efficiency (109) = (98a)/TFA + (108) — only relevant for new-build compliance (FEE replaces SAP rating in some contexts). Picked up by §8f slice.

§8f — slice progress (xlsx rows 466470)

Line ref Description Status Commit
fabric_energy_efficiency_kwh_per_m2_yr free function (no dataclass) 43cc16bc
SpaceHeatingResult.space_heating_requirement_kwh_per_yr (Σ(98a) — pre Appendix H) 43cc16bc
(109) FEE = (98a)/(4) + (108), kWh/m²/yr 43cc16bc
6-fixture ALL_FIXTURES conformance (LINE_109 = LINE_99 since (98b)=0 + (108)=0) 43cc16bc
CalculatorInputs.fabric_energy_efficiency_kwh_per_m2_yr + SapResult.fabric_energy_efficiency_kwh_per_m2_yr + cert_to_inputs wiring 43cc16bc
§11 compliance conditions (separate worksheet re-run with §11 ventilation / HW / lighting / gains-column assumptions) ⏸ deferred

Six Elmhurst fixtures conform end-to-end on §8f to ≤5e-3 kWh/m² on LINE_109. The tolerance inherits from the 4-d.p. display-rounding floor of the LINE_98C_ANNUAL_KWH fixture pins (§8 conformance is at 1e-1 kWh on annual, which divides by TFA to ~5e-3 kWh/m²).

E2e SAP-score impact: none. (109) is a transparency output for rating runs — it doesn't feed ECF, fuel costs, or CO2. The wiring lets §11 compliance code consume FEE later via SapResult.fabric_energy_efficiency_kwh_per_m2_yr once §11 conditions are implemented.

Remaining §8f work

  1. §11 FEE compliance conditions — spec lines 2152-2164 require a separate worksheet run with: natural ventilation + 2-4 extract fans by TFA, instantaneous-electric shower + bath, 125 l/day water-use target, lighting capacity 185 lm/m² at 66.9 lm/W, column (B) of Table 5 for heating gains, column (A) for cooling gains, etc. This is a new-build compliance path; existing-dwelling ratings don't trigger it. Lands as fabric_energy_efficiency_under_section_11_conditions(...) when the first compliance use case emerges.
  2. Σ(98a) ≠ Σ(98c) regression coverage — when Appendix H solar space heating lands (currently (98b) ≡ 0), Σ(98a) will diverge from Σ(98c). The FEE function consumes Σ(98a) per spec; a new fixture with non-zero (98b) would assert that distinction holds.

§9a — slice progress (xlsx rows 470614)

Line ref Description Status Commit
space_heating_fuel_monthly_kwh orchestrator + EnergyRequirementsResult dataclass (16 fields, full worksheet shape) 2b5fc6a5
(201) Secondary heating fraction (Table 11) 2b5fc6a5
(202) Main heating total fraction = 1 (201) 2b5fc6a5
(203) Main 2 of main fraction ⏸ zero-branch (no two-main cert yet)
(204) Main 1 of total fraction = (202) × (1 (203)) 2b5fc6a5
(205) Main 2 of total fraction = (202) × (203) ⏸ zero-branch
(206) Main 1 efficiency % pass-through; cert source is SAP10 Table 4a category default (PCDB-blocked) 2b5fc6a5
(207) Main 2 efficiency % ⏸ zero-branch
(208) Secondary efficiency % 2b5fc6a5
(209) Cooling SEER (Table 10c) ⏸ deferred (see §8c remaining work)
(211)m Main 1 fuel kWh per month = (98c)m × (204) × 100 / (206) 2b5fc6a5
(211) Σ(211)m 2b5fc6a5
(213)m Main 2 fuel kWh per month ⏸ zero-branch
(213) Σ(213)m ⏸ zero-branch
(215)m Secondary fuel kWh per month = (98c)m × (201) × 100 / (208) 2b5fc6a5
(215) Σ(215)m 2b5fc6a5
(221) Cooling fuel kWh per yr = (107) ÷ (209) ⏸ deferred (Table 10c SEER)
CalculatorInputs.energy_requirements composite slot + SapResult.main_2_heating_fuel_kwh_per_yr + SapResult.space_cooling_fuel_kwh_per_yr + _solve_month refactor + cert_to_inputs wiring (atomic) 380b6781
6-fixture ALL_FIXTURES PDF-derived pins on (206)/(211)/(215) blocked on PCDB (see Prioritised gap list item 1)
(230a)-(230h) Table 4f pumps/fans breakdown (warm-air fans, oil aux, gas aux, keep-hot, solar pump, WWHRS pump) ⏸ deferred
(231) Σ(230a)-(230h) — Total electricity for pumps/fans/keep-hot ⏸ deferred — currently inputs.pumps_fans_kwh_per_yr is an opaque scalar
(236)/(237) Appendix Q items energy saved/used ⏸ deferred (no Q-item cert in corpus)
(238) Total delivered energy kWh/yr Partial — computed in intermediate["delivered_fuel_kwh_per_yr"]; not yet promoted to SapResult field

Four synthetic tests on the orchestrator cover: single-main no-secondary 80% efficiency, Table 11 secondary fraction split (211)+(215), summer-clamp zero propagation, and scope-A two-main / cooling-fuel placeholders. Cert round-trip test pins inputs.energy_requirements.main_1_fuel_kwh_per_yr == result.main_heating_fuel_kwh_per_yr to float equality — confirms the refactor preserves existing behaviour.

E2e SAP-score impact: zero (refactor only — _solve_month now reads precomputed (211)m/(215)m instead of doing q/η inline). The 000490 +3 SAP-score gap stays parked behind PCDB integration.

Remaining §9a work

  1. PCDB integration — see Prioritised gap list item 1. Blocks PDF-derived ALL_FIXTURES pinning + closes 000490 e2e gap. ADR-0010 §4 prerequisite.
  2. Two-main system (203)/(205)/(207)/(213) — populate from epc.sap_heating.main_heating_details[1] when a multi-main cert appears in fixtures. EnergyRequirementsResult already exposes the fields as zeros; first multi-main slice flips them from placeholders to real values.
  3. Table 10c SEER → (209)/(221) — cooling fuel kWh from (107) ÷ SEER. Same trigger as §8c remaining work (first fixed-AC cert); add Table 10c lookup table + wire energy_requirements.cooling_fuel_kwh_per_yr + SapResult.space_cooling_fuel_kwh_per_yr from zero to real value.
  4. Table 4f pumps/fans breakdown (230a)-(230h) → (231) — replace opaque CalculatorInputs.pumps_fans_kwh_per_yr scalar with per-source sub-lines. Likely a separate sweep slice once aux electricity becomes load-bearing for ranking.
  5. Appendix Q items (236)/(237) — placeholder until a Q-item cert lands.
  6. (238) total delivered energy on SapResult — promote from intermediate dict when §10a or §13 requires it as a named output.