Model/docs/sap-spec/SPEC_COVERAGE.md
Khalim Conn-Kowlessar 6c966ffe2b docs: handover for Table 3c two-profile combi loss → close 4 Elmhurst fixtures
Rewrites HANDOVER_NEXT.md for the next agent. Two-ticket sequence:

1. Table 3c (immediate): implement SAP10.2 Appendix J §J3 two-profile
   combi-loss formula + route PCDB records with separate_dhw_tests=2
   through it. Closes 000477/000480/000487/000516 from SAP delta
   +1/+12/+11/+12 to delta=0. Currently those fall through to Table 3a
   keep-hot 600 kWh/yr default = ~25× overshoot.
2. RdSAP API integration test (end-state): real RdSAP10 API response
   → EpcPropertyDataMapper → cert_to_inputs → SAP integer == lodged.
   User generating exotic fixtures to pressure-test first.

SPEC_COVERAGE §4 row updated to call out the Table 3c gap. ADR-0010
gains a "Cohort residual hunt + SAP 10.2 rating constants" amendment
documenting the 5 component closures (secondary heating, ventilation
cert lodgement, Table 4f pumps_fans, SAP 10.2 rating constants,
000477 partial) and naming the deferred Table 3c work.

Carries a PCDF parser concern: raw row at index 52 has 13.729 which
looks like F2-annual-kWh but parser reads F2 from fields[55] = 0.0.
Verify field positions per BRE PCDF Spec §7.11 before assuming F2=0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 12:14:00 +00:00

48 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), §9a single-main slices (2b5fc6a5380b6781), PCDB Table 105 integration (fe04cd3aa104dd55), PCDB fixture lodgement (1b43c95c15d6b781), §10a Fuel costs (0f255165adfa7f60), and §4 HW PCDB Table 3b + Equation D1 (760e25de, 02fc9e4d).

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 + Appendix D worksheet/water_heating.py + cert_to_inputs _water_heating_worksheet_and_gains + _apply_water_efficiency Closed for combi-gas (Table 3b row 1) — Table 3c two-profile pending. Worksheet line refs (42)..(65) + Appendix D §D2.1 (2) Equation D1 (water_efficiency_monthly_via_equation_d1) + Appendix J Table 3b row 1 (combi_loss_monthly_kwh_table_3b_row_1_instantaneous). 000474 + 000490 HW kWh match PDF to ≤0.1% (both lodge PCDB records with separate_dhw_tests=1). cert_to_inputs splits the §4 worksheet from the efficiency divisor: (45..65) runs early so §5 has (65)m heat gains; HW fuel kWh computed after §8 produces (98c)m for the Eq D1 cascade. PCDB Table 105 parser exposes 5 new combi-loss fields (separate_dhw_tests, r1, F1, F2, F3 + subsidiary_type + store_type) per BRE PCDF Spec v1.0 §7.11. Table 3c two-profile combi loss not implemented: PCDB records with separate_dhw_tests=2 (Vaillant ecoTEC sustain 24/28 — affects 000477, 000480, 000487, 000516 from the Elmhurst cohort) fall through to Table 3a "keep-hot time-clock" 600 kWh/yr default, 25× over spec-faithful ~24 kWh/yr. Next ticket — see HANDOVER_NEXT.md. Deferred: Cylinder + solar + WWHRS + PV diverter + FGHRS branches (no fixture yet); Table 3b storage / FGHRS rows (no fixture yet); Electric CPSU Appendix F path (no fixture yet).
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). Appendix L slice update: annual_lighting_kwh surfaced as a public leaf returning the worksheet-lodged (232) value (Σ L11 monthly distribution; cosine integral 0.998539). InternalGainsResult.lighting_kwh_per_yr exposes the same value so cert_to_inputs populates inputs.lighting_kwh_per_yr from the cascade — single source of truth shared with §5 (67). New worksheet-level per-component pin: internal_gains_from_cert(...).lighting_kwh_per_yr matches U985 (232) to abs=1e-4 for all 6 Elmhurst fixtures (000474:139.9452, 000477:201.6754, 000480:212.5531, 000487:227.6861, 000490:171.4217, 000516:230.8853).
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.
10a Fuel costs incl. micro-CHP worksheet/fuel_cost.py + tables/table_32.py + tables/table_12a.py Full (single-main + standard tariff) Worksheet-driven (240)..(255) via fuel_cost(...). 32-field FuelCostResult mirrors the §10a worksheet shape: (240a-e) main 1 + (241a-e) main 2 + (242a-e) secondary off-peak splits, (243-247) water heating, (247a) instant shower, (248) cooling, (249) pumps/fans, (250) lighting, (251) standing charges (Table 12 note (a) gating: gas standing + off-peak electricity standing), (252) PV credit (negative), (253-254) Appendix Q, (255) total clamped to ≥ 0. RdSAP10 Table 32 prices per ADR-0010 amendment (overrides SAP10.2 Table 12 for §10a/§10b). Tariff + Table12aSystem + OtherUse enums in table_12a.py for off-peak high-rate-fraction lookups (synthetic-tested; unreachable from RdSAP cert flow until Table12aSystem cert→row mapping lands). cert_to_inputs._fuel_cost precomputes for STANDARD-tariff certs; off-peak certs return zero sentinel so the calculator's legacy scalar _*_fuel_cost_gbp_per_kwh fallback fires (deferred). 000490 SAP rating ceiling tightened 6 → 2; 000474 ceiling loosened 2 → 4 reflecting upstream §4 HW + Appendix L lighting overestimates the pre-§10a wrong-table-but-cancels-kWh had masked. Deferred: per-row (252) PV/wind/hydro/μCHP split, Table 13 immersion fractions, Table 12a Table12aSystem cert→row mapping for off-peak electric mains, (230a)-(230g) per-row pumps/fans split, (247a) instant shower wiring, scalar fuel-cost-per-kWh field cleanup from CalculatorInputs.
12 Total energy + fuel costs (legacy heading — folded into §10a) calculator.py Folded into §10a row above Pre-§10a §12 row covered the inline cost arithmetic; that block was rewritten into the §10a orchestrator + cert_to_inputs precompute. Calculator delegates total_fuel_cost_gbp to inputs.fuel_cost.total_cost_gbp.
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 (cost + gains) Existing-dwelling L1-L12 cascade + RdSAP §12-1 bulb defaults + Table 6d Z_L. Closed both sides: §5 (67) gains side (lighting_monthly_w) + cost side (annual_lighting_kwhInternalGainsResult.lighting_kwh_per_yrinputs.lighting_kwh_per_yr). Replaces the legacy predicted_lighting_kwh heuristic which over-counted ~3× on the Elmhurst cohort. 000474 SAP integer closes to delta=0 vs PDF. Legacy heuristic kept in domain/ml/demand.py with deprecation note for the unmigrated domain.ml.ecf + domain.ml.transform callsites — see ADR-0010 amendment 2026-05-22.
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) — Table 105 done; Table 362 heat pumps + equation D1 monthly water + Appendix N HP factor remain. Gas/oil boilers (Table 105) now flow via PCDB: when a cert lodges main_heating_index_number, domain.sap.tables.pcdb.gas_oil_boiler_record(...) is consulted and the PCDB winter efficiency overrides seasonal_efficiency(...) for space heating + summer overrides Table 4a scalar for water heating (per Appendix D2.1). Two of the six golden corpus PCDB-listed certs drifted +1 SAP / -1.5 kWh/m² PE under the spec-faithful override (tolerance widened accordingly). Remaining: (a) Table 362 heat pumps still resolve via seasonal_efficiency(main_category=4) → 2.30 SCOP fallback; Appendix N in-use factor (0.95) + MCS factor (×1.39 GSHP) + design-flow-temp adjustment all deferred. (b) Equation D1 monthly water heating cascade (currently scalar approximation); ~single-digit-percent HW kWh under-precision for combi boilers. (c) Table 313 FGHRS, Table 353 WWHRS, Table 391 storage heaters, Table 506 HIU records are parsed into JSONL but unconsumed — wait for first cert that needs them.
  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 default + PCDB Table 3b row 1 (instantaneous non-storage) override bfba610b, 760e25de
(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). (61)m PCDB Table 3b row 1 lands the 000474 combi loss within 0.02% of PDF arithmetic. §4 HW slice 2 update: Equation D1 monthly cascade (Appendix D §D2.1 (2)) wired post-§8 closes HW fuel kWh — 000474: 2622 → 2292 (matches PDF 2292 to ≤0.1%); 000490: 3028 → 2847 (matches PDF 2851 to ≤0.1%). Closes the residual §10a "section 4 next ticket" debt named in project_section_4_hw_next_ticket memory + ADR-0010 §4-amendment of §10a slice 3.

Remaining §4 work

  1. Cylinder + solar + renewables paths for full population coverage (cylinders, FGHRS, WWHRS, PV-diverter, solar HW). Currently zero-branch placeholders.
  2. PCDB Table 3b storage / FGHRS rows (other than row 1) — the impl exposes profile_flag + r1/F1/F2/F3 fields but only row 1 (instantaneous non-storage) computes. Storage variants raise NotImplementedError-equivalent (fall through to Table 3a) until a fixture exercises.
  3. PCDB Table 3c (two-profile boilers, separate_dhw_tests = 2 or 3) — Table 3c uses r1, F2, F3, DVF (daily volume factor); parser exposes the fields, formula pending.
  4. Electric CPSU → Appendix F path for water-eff cascade.
  5. (64a) Instant electric shower kWh routing — line currently always 0 from _hot_water_fuel_kwh_per_yr; wire when an electric-shower cert lodges.
  6. Appendix L lighting predictor (domain.ml.demand.predicted_lighting_kwh) — separate ticket per memory project_section_4_hw_next_ticket "secondary upstream". 000474 lights 528 kWh/yr vs ~169 back-derived from PDF cost; tightening drops the 000474 cost residual from +9.2% closer to zero.

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

§10a — slice progress (xlsx rows ~614740)

Per ADR-0010 amendment, §10a costs source from RdSAP10 Table 32 (PDF page 95), not SAP10.2 Table 12. Three new modules + one composite slot on CalculatorInputs + one calculator delegation.

Slice What landed Commit
1 tables/table_32.py (28 fuel-row unit prices + 11-row standing charges + note (a) gating fn) + tables/table_12a.py (Tariff + Table12aSystem + OtherUse enums + SH/WH/other-use fraction lookups + tariff_from_meter_type cert resolver) + worksheet/fuel_cost.py (32-field FuelCostResult + kwargs fuel_cost(...) orchestrator with _split off-peak helper). 130 synthetic unit tests. 0f255165
2 CalculatorInputs.fuel_cost composite slot (default zero sentinel) + cert_to_inputs._fuel_cost precompute (Table 32 prices + note (a) standing-charge gating; off-peak certs return zero sentinel → calculator falls back to legacy scalar _*_fuel_cost_gbp_per_kwh helpers, deferred). Calculator delegates total_fuel_cost_gbp to inputs.fuel_cost.total_cost_gbp. 2 cert-round-trip conformance tests (000474 within e2e 15% tolerance; 000490 within 5%). e2e ceilings adjusted: 000490 6 → 2 (tightened — marquee close-out), 000474 2 → 4 (loosened — exposes upstream §4 HW + Appendix L), golden corpus ±7 → ±11 (oil price +55% Table 12 → Table 32). adfa7f60
3 Docs — ADR-0010 amendment, this SPEC_COVERAGE row, slice progress table. this commit

Line-ref status

Line ref Status Notes
(240a)-(240e) Full Main 1 off-peak split. STANDARD-tariff certs lodge high_rate_fraction=1.0 so (240c) = kWh × price + (240d) = 0. Off-peak certs defer to legacy scalar fallback (Table12aSystem cert→row mapping deferred).
(241a)-(241e) Zero-branch placeholder Main 2 — no multi-main fixture in corpus.
(242a)-(242e) Full (zero-valued) Secondary off-peak split. All 6 fixtures lodge zero secondary_fuel_kwh_per_yr.
(243)-(247) Full (single-rate path) Water heating off-peak split. STANDARD-tariff path active; Table 12a immersion / heat-pump-DHW (Table 13) deferred.
(247a) Zero-branch placeholder Instant electric shower kWh routing deferred (no fixture lodges one).
(248) Full Cooling cost at other_uses_gbp_per_kwh. Zero in 6 fixtures (f_C=0).
(249) Full (aggregate) Pumps/fans single-rate. Per-row (230a)-(230g) Table 12a split deferred (spec line 8076).
(250) Full (aggregate) Lighting single-rate. Per-row off-peak split deferred when first off-peak fixture lands.
(251) Full Standing charges via Table 12 note (a) — gas (mains gas £120, LPG £70) added when gas used for space/water; off-peak electricity standing added when off-peak meter in use; std electricity standing always omitted.
(252) Partial Single pv_credit_gbp scalar. Per-row PV / wind / hydro / micro-CHP split deferred.
(253)/(254) Zero-branch placeholder Appendix Q items — no Q-item cert in corpus.
(255) Full max(0, Σ all rows) clamp.

Six Elmhurst fixtures route through the new precompute (all meter_type="Single" → STANDARD tariff). 000490 cost closes to PDF within ~4%; 000474 widens to +10.7% (upstream §4 HW + Appendix L — see Remaining work).

Remaining §10a work

  1. §4 HW worksheet tightening — next ticket. 000474 HW kWh overestimates +14.4% (2622 vs 2292 PDF), Appendix L lighting overestimates ~3x. Pre-§10a wrong-table-but-cancels-kWh masked these. See project memory project_section_4_hw_next_ticket.
  2. Table 12a cert→Table12aSystem mapping for off-peak electric mains. Currently cert_to_inputs._fuel_cost returns the zero sentinel for non-STANDARD tariff certs so the calculator's legacy scalar fallback fires. Off-peak split awaits a real off-peak fixture + the row-mapper.
  3. Table 13 immersion + HP-DHW-only WH fractionsTable12aSystem.IMMERSION_OR_HP_DHW_ONLY raises NotImplementedError; populate when first immersion fixture lands.
  4. Electric CPSU → Appendix F fractionsTable12aSystem.ELECTRIC_CPSU raises; populate when first CPSU fixture lands.
  5. Per-row (230a)-(230g) pumps/fans split for off-peak tariffs (spec line 8076: "if off-peak tariff, list each of (230a) to (230g) separately and apply fuel price according to Table 12a"). Requires §9a Table 4f pumps/fans breakdown (see §9a remaining work item 4) as a prerequisite.
  6. (247a) Instant electric shower kWh routing — (64a) currently always 0 from _hot_water_fuel_kwh_per_yr. Wire when electric-shower cert lodges.
  7. (252) per-row Appendix M/N split — populate (233a)..(235d) when wind / hydro / micro-CHP fixtures land.
  8. (253)/(254) Appendix Q — zero placeholders; populate when a Q-item cert lands.
  9. Drop legacy scalar fuel-cost fields from CalculatorInputs (space_heating_fuel_cost_gbp_per_kwh, hot_water_fuel_cost_gbp_per_kwh, other_fuel_cost_gbp_per_kwh, secondary_heating_fuel_cost_gbp_per_kwh, pv_export_credit_gbp_per_kwh) — currently retained as a synthetic-test fallback. Drops when the ~33-occurrence test corpus migrates to fuel_cost=....

PCDB — slice progress (BRE pcdb10.dat ingestion)

Stage Description Status Commit
ETL parser + 8 tests domain.sap.tables.pcdb.parser: typed GasOilBoilerRecord + RawPcdbRecord. Ground-truth verified against ncm-pcdb.org.uk for Baxi 000098 / Potterton 000619 / Saunier 000732. Handles latin-1 encoding (degree-sign in addresses), 'obsolete' status string, '>70kW' range indicator. fe04cd3a
ETL run_etl writes 8 JSONL files One newline-delimited JSON file per table (105 typed; 122/143/313/353/362/391/506 raw). 17MB total. Runnable via PYTHONPATH=packages/domain/src python -m domain.sap.tables.pcdb.etl. Idempotent; commit JSONL alongside source pcdb10.dat. fe04cd3a
Runtime lookup gas_oil_boiler_record(pcdb_id) domain.sap.tables.pcdb loads Table 105 NDJSON at import; ~50ms one-off, O(1) lookups thereafter. Returns None for unknown PCDB IDs → caller falls back to Table 4a/4b cascade. 23678228
cert_to_inputs precedence (Table 105 only) Appendix D2.1: PCDB winter overrides main_heating_efficiency; PCDB summer overrides water_efficiency scalar. Heat-network DLF override still wins where applicable. None of the 6 Elmhurst fixtures lodge a PCDB pointer initially; corpus golden certs that do see real efficiency changes (golden tolerance widened ±5 → ±7). a104dd55
Elmhurst fixture PCDB lodgement: 000490 + 000474 _elmhurst_worksheet_000490.build_epc() lodges main_heating_index_number=10328 (Vaillant Ecotec Pro 28, winter 88.2%); _elmhurst_worksheet_000474.build_epc() lodges 16839 (Vaillant ecoTEC pro 28 VUW GB 286/5-3, winter 88.7%). 000474 e2e ceiling tightens 7 → 2 SAP points; 000490 widens 3 → 6 (spec-version drift on fuel cost — pre-amendment cert). make_main_heating_detail extended with main_heating_index_number / main_heating_data_source / sap_main_heating_code kwargs. 1b43c95c, 7d4f3d78
API → domain mapper-chain regression test test_api_to_domain_mapper_preserves_main_heating_index_number parametrised over the 4 PCDB-listed golden corpus certs. Pins that EpcPropertyDataMapper.from_api_responsecert_to_inputs chain surfaces the PCDB pointer + applies PCDB winter efficiency. Confirms no domain-model changes were needed — MainHeatingDetail.main_heating_index_number has existed since schema 17_1 and all mapper paths from 17_1+ pass it through verbatim. 15d6b781
Heat pump Appendix N cascade via Table 362 Apply Appendix N in-use factor (×0.95), MCS installation factor (×1.39 for GSHP MCS-installed), design flow temperature adjustment. Replace SCOP 2.30 Table 4a fallback for main_category=4. ⏸ deferred (typed Table 362 parser + Appendix N cascade)
Equation D1 monthly water heating cascade Spec D2.1 (2): η_water_monthly = (Q_space + Q_water) / (Q_space/winter + Q_water/summer). Promotes water_eff scalar → 12-tuple. Refactor of _hot_water_fuel_kwh_per_yr. ⏸ deferred (single-digit-% HW kWh precision for combi boilers)
Solid fuel boiler precedence via Table 122 PCDB override for main_category=3 (solid fuel) — typed parser + wiring. ⏸ deferred
Micro-CHP precedence via Table 143 PCDB override for micro-CHP systems (Appendix N path). ⏸ deferred
Ancillary cascades: Table 313 (FGHRS), 353 (WWHRS), 391 (HHR storage), 506 (HIU) Typed parsers + cert-side wiring per spec rules. JSONL files exist; consumers don't. ⏸ deferred
Table D1/D2/D3 condensing-boiler control-class corrections Apply Ecodesign control-class + design-flow-temp adjustments on top of PCDB winter efficiency. Requires cert lodgement of control class + flow temp. ⏸ deferred (no fixture lodges these yet)

Impact: Closes the ADR-0010 §4 prerequisite for gas/oil boilers. Heat pumps remain on SCOP 2.30 Table 4a fallback — ~19 SAP-point MAE on HP certs per ADR-0010 §4 persists until the Table 362 + Appendix N slice lands. §9a ALL_FIXTURES PDF-derived LINE_206/(211)/(215) pinning is still blocked: the 6 Elmhurst fixtures don't lodge main_heating_index_number, so adding PDF-grounded efficiency pins requires either (a) verifying each fixture's actual boiler against ncm-pcdb.org.uk + adding the PCDB ID to fixture builders, or (b) waiting for a real-corpus golden cert to validate against.