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>
48 KiB
SAP 10.2 / RdSAP 10 Coverage Map
Tracks which sections of the SAP 10.2 specification are implemented in packages/domain/src/domain/sap/. Per ADR-0009 the calculator is built from the spec, not reverse-engineered from cert data. This doc is the worksheet-driven roadmap for what remains.
Updated 2026-05-21 after §8c (slices cf28eec4…f3797066), §8f (43cc16bc), §9a single-main slices (2b5fc6a5…380b6781), PCDB Table 105 integration (fe04cd3a…a104dd55), PCDB fixture lodgement (1b43c95c…15d6b781), §10a Fuel costs (0f255165…adfa7f60), 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 §§1–13 (the SAP worksheet)
| § | xlsx rows | Topic | Module | Status | Notes / gaps |
|---|---|---|---|---|---|
| 1 | 1–120 (approx) | Dimensions | worksheet/dimensions.py |
Full | Porches, conservatories, RIR deferred per ADR-0009 |
| 2 | 121–206 (approx) | Ventilation | worksheet/ventilation.py |
Partial | No mechanical ventilation (MVHR/MEV), no wind-shelter factor, no pressure-test override (worksheet lines 17-18), no AP4 override (worksheet line 19) |
| 3 | 121–207 | Heat transmission | worksheet/heat_transmission.py |
Full (non-RR) | LINE_31/33/36/37 exact for both non-RR Elmhurst fixtures (000474, 000490). Suspended-timber + Table 20 exposed-floor routes wired. RR sub-areas (gable/slope/stud-wall) deferred until SapRoomInRoof carries them. Global y-factor (Table R2 per-junction deferred). |
| 4 | 207–304 | Hot water + Appendix J + 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_kwh → InternalGainsResult.lighting_kwh_per_yr → inputs.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)
- 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 overridesseasonal_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 viaseasonal_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. - Table 11 Secondary heating allocation — most boiler-main certs allocate 10% of space heating to a secondary system (often a less-efficient room heater on a different fuel). We model 0%. Likely +1-2 SAP-point bias on affected certs.
- Wind-shelter factor on infiltration (§2 worksheet lines 19-21) — multiplies infiltration by
1 - 0.075 × sheltered_sides. We have no shelter input; assume 2 sheltered sides default. Net effect on infiltration ACH probably ~10%. - Table 12a high-rate fraction for off-peak dwellings — we currently bill 100% of E7 space heating at the low rate. Real spec says e.g. heat pumps on 7h tariff at 80% high-rate. Affects ~5% of certs.
- Cylinder-loss factor cascade — currently uses simplified buckets in
domain.ml.demand._STORAGE_LOSS_FACTOR. Spec has more precise interpolation rules from cylinder volume + insulation thickness. - Standing charges in cost — Table 12 note (a) gives the rule for when standing charges are included (energy use vs rating). May affect bias.
- Per-junction thermal bridging (Table R2) — only relevant when assessor lodged junction-count data, otherwise global y is the spec answer for RdSAP-driven assessments.
Status now: 100-cert MAE 4.49, 300-cert MAE 5.45, bias near zero (±0.2). Worksheet-driven phase begins with Secondary heating Table 11 as the next slice.
§4 — slice progress (xlsx rows 207–304)
| Line ref | Description | Status | Commit |
|---|---|---|---|
| (42) | Assumed occupancy N from Appendix J | ✅ | aff678e8 |
| (42a)m | Mixer showers monthly | ✅ | 1dcbdb28 |
| (42b)m | Baths monthly | ✅ | dad7fbf3 |
| (42c)m | Other uses monthly | ✅ | 5cc68ab3 |
| (43) | Annual avg | ✅ | 702b1c6c |
| (44)m | Daily monthly total | ✅ | 702b1c6c |
| (45)m | Energy content | ✅ | a3c687f1 |
| (46)m | Distribution loss | ✅ | a3c687f1 |
| (47)–(56) | Storage / HIU loss | Combi zero-branch only; cylinder paths deferred | — |
| (57)m | Dedicated solar storage | Combi zero-branch only; solar HW deferred | — |
| (59)m | Primary loss | Combi zero-branch only; boiler+cylinder deferred | — |
| (61)m | Combi loss | Table 3a 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
- Cylinder + solar + renewables paths for full population coverage (cylinders, FGHRS, WWHRS, PV-diverter, solar HW). Currently zero-branch placeholders.
- 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. - 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.
- Electric CPSU → Appendix F path for water-eff cascade.
- (64a) Instant electric shower kWh routing — line currently always 0 from
_hot_water_fuel_kwh_per_yr; wire when an electric-shower cert lodges. - Appendix L lighting predictor (
domain.ml.demand.predicted_lighting_kwh) — separate ticket per memoryproject_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 305–332)
| Line ref | Description | Status | Commit |
|---|---|---|---|
| (66)m | Metabolic = 60 × N (Table 5 Col A) | ✅ | 3ec56216 |
| (71)m | Losses = -40 × N | ✅ | 021f43ba |
| (69)m | Cooking = 35 + 7 × N | ✅ | 984a5b18 |
| (72)m | Water heating bridge (65)m × 1000/(24 × n_m) | ✅ | a4d6321f |
| (68)m | Appliances (L13/L14/L16a) | ✅ | 0bc9eac3 |
| (67)m | Lighting (L1-L12 with Z_L from Table 6d) | ✅ | 50fd940a |
| (70)m | Pumps + fans (Table 5a 9-row dispatch) | ✅ | f77229e4 |
| (73)m | Total internal gains + InternalGainsResult | ✅ | 53aba133 |
| — | internal_gains_from_cert orchestrator |
✅ | f81e744b |
| — | 6-fixture ALL_FIXTURES conformance | ✅ | 99e5c2cd |
| — | cert_to_inputs wiring + calculator.py wiring |
✅ | bf6a7e04 |
| — | Rooflight Z_L=1.0 (Table 6d note 2) | ✅ | 380115e2 |
Six Elmhurst fixtures conform end-to-end on §5 to ≤5e-3 W on every line — display-rounding territory across the board.
Remaining §5 work
- Table 5a fan / PIV / HIU branches are implemented as leaf functions but not yet derived from cert inputs in
internal_gains_from_cert. Pump dispatch is wired; non-pump rows always emit 0 W. MVHR / MEV correctly produces zero gain. - Per-window
frame_material/glazing_typestrings — current orchestrator uses cert numeric codes; needs a robust mapping for site-notes-sourced certs. - Rooflight derivation from cert — orchestrator accepts
rooflight_total_area_m2butcert_to_inputsdoesn't yet detect rooflights (defaults to 0). Needs theSapWindow.window_locationenum that's TODO'd in the domain model. - Column B reduced-gain forms (L12a / L16) for new-build DPER/TPER calculations — deferred until we onboard a new-build cert.
§6 — slice progress (xlsx rows 332–371)
| Line ref | Description | Status | Commit |
|---|---|---|---|
| — | Table 6d Z-solar lookup (winter / heating column) | ✅ | da5909de |
| (74)..(81) | Per-orientation solar gain (N..NW) | ✅ | 4b83e702 |
| (82) | Roof windows — RdSAP10 Table 24 pitch 45° default, Z=1.0 | ✅ | 4b83e702 |
| (82a) | Rooflights — SAP10.2 §U3.2 horizontal, Z=1.0 | ✅ | 4b83e702 |
| (83) | Total solar gains 12-tuple | ✅ | 4b83e702 |
| — | RoofWindowInput + RooflightInput + SolarGainsResult |
✅ | 4b83e702 |
| — | solar_gains_from_cert orchestrator |
✅ | 4b83e702 |
| — | Manufacturer window_transmission_details cascade |
✅ | d56fef4d |
| — | 6-fixture ALL_FIXTURES conformance (83) + (84) ≤5e-3 W | ✅ | 377caea2 |
| — | CalculatorInputs.solar_gains_monthly_w + per-month lookup |
✅ | 376cdb6b |
| — | cert_to_inputs swap legacy _solar_gains_w → orchestrator |
✅ | cd2bd9ce |
| — | Delete _solar_gains_w + WindowInput + _window_inputs |
✅ | a0ce45c9 |
Six Elmhurst fixtures conform end-to-end on §6 to ≤5e-3 W on every month of (83) total solar gains and (84) total gains (the latter cross-checks §5 LINE_73 plus §6 LINE_83). 144 monthly assertions across 6 fixtures, all GREEN.
Remaining §6 work
- Rooflight derivation from cert —
cert_to_inputspassesrooflights=()because Elmhurst summaries lodge rooflights aswindow_location = "External wall"; needs theSapWindow.window_locationenum work (also blocking §5 rooflight detection — common ticket). - Roof window detection from cert — same constraint as rooflights; orchestrator-side support is there (
RoofWindowInputwith pitch default), but the cert→inputs mapper has no signal to populate it. - Multi-window-type per orientation — orchestrator sums all windows of one orientation regardless of glazing type, matching the Elmhurst worksheet's per-orientation Σ. The xlsx repeats (74)..(82a) blocks per "window type" for diagnostic display; we collapse to per-orientation totals. Faithful per-type printout would need a richer result dataclass.
- Table 6c metal frame factor — orchestrator ships 0.8 (spec); the deleted legacy lookup had 0.83 (silent bug). Frame-material strings outside
metal/aluminium/wood/pvc/upvc/compositefall through to 0.7 default.
§7 — slice progress (xlsx rows 372–401)
| Line ref | Description | Status | Commit |
|---|---|---|---|
| — | Per-zone η fix + MeanInternalTemperatureResult + mean_internal_temperature_monthly orchestrator |
✅ | fa49d7b9 |
| (85) | T_h1 = 21 °C scalar | ✅ | fa49d7b9 |
| (86)m | η_living utilisation at Ti=T_h1 | ✅ | fa49d7b9 |
| (87)m | MIT living = T_h1 − u1 − u2 | ✅ | fa49d7b9 |
| (88)m | T_h2 elsewhere heating temp (Table 9) | ✅ | fa49d7b9 |
| (89)m | η_elsewhere utilisation at Ti=T_h2 | ✅ | fa49d7b9 |
| (90)m | MIT elsewhere = T_h2 − u1 − u2 | ✅ | fa49d7b9 |
| (91) | f_LA living area fraction | ✅ | fa49d7b9 |
| (92)m | Blended MIT = f_LA·T_1 + (1−f_LA)·T_2 | ✅ | fa49d7b9 |
| (93)m | Adjusted MIT (+ Table 4e adj, default 0) | ✅ | fa49d7b9 |
| (94)m | η_whole utilisation at Ti=(93) — feeds §8 | ✅ | fa49d7b9 |
| — | Two-main case 1 weighted-R via secondary_fraction | ✅ | 13c2c651 |
| — | 6-fixture ALL_FIXTURES conformance (85)..(94) ≤5e-3 | ✅ | ff5d8c70 |
| — | CalculatorInputs + cert_to_inputs wiring; drop η iteration |
✅ | 8ec9da47 |
| — | Delete legacy single-η mean_internal_temperature_c |
✅ | a7f39685 |
Six Elmhurst fixtures conform end-to-end on §7 to ≤5e-3 °C / unitless on every per-zone line ref every month — 588 monthly assertions across 6 fixtures, all GREEN. The pre-rebuild single-η path drifted by 6.6e-3 °C on 000490 Jan (92)m; the per-zone fix closes that gap by ~70×.
Remaining §7 work
- Table 4e control_temperature_adjustment_c cert mapping — orchestrator accepts the parameter but
cert_to_inputshardcodes 0.0 (all 6 Elmhurst fixtures = 0 anyway). Wire when a non-zero corpus emerges. - Two-main case 2 (different parts heated separately) — needs (203) > 1−(91) branch + conditional T_2 averaging + per-system Table 4e adj weighting. Case 1 (both heat whole house) wired with synthetic test; case 2 deferred.
- Auto-detect
secondary_fractionfromepc.sap_heating.main_heating_details— currently always 0 incert_to_inputs. Wire when a multi-main cert lands.
§8 — slice progress (xlsx rows 401–435)
| Line ref | Description | Status | Commit |
|---|---|---|---|
| — | SpaceHeatingResult + space_heating_monthly_kwh orchestrator |
✅ | 9113f30a |
| — | Table 9c step 10 summer clamp (Jun..Sep = 0) | ✅ | 9113f30a |
| (95)m | Useful gains ηG, W | ✅ | 9113f30a |
| (97)m | Heat loss rate L_m = H·(T_int − T_ext), W | ✅ | 9113f30a |
| (98a)m | Space heating requirement per month, kWh | ✅ | 9113f30a |
| (98b)m | Solar space heating (Appendix H) — hardcoded 0 | ⏸ deferred | — |
| (98c)m | Total = (98a) + (98b), kWh | ✅ | 9113f30a |
| Σ(98c) | Annual total kWh | ✅ | 9113f30a |
| (99) | Annual per-m² = Σ(98c) / TFA | ✅ | 9113f30a |
| — | 6-fixture ALL_FIXTURES conformance on (95)..(99) | ✅ | 1f078af7 |
| — | CalculatorInputs.space_heating_monthly_kwh + cert_to_inputs wiring |
✅ | f6ab7626 |
Six Elmhurst fixtures conform end-to-end on §8 to 5e-2..1e-1 kWh on every per-month line ref (looser than §6/§7's 5e-3 because LINE_84/93/94 fixture pins are 4-d.p. display-rounded and §8's 0.024·n_m·(L−ηG) propagates that rounding into the per-month kWh band; the orchestrator computes in full precision).
E2e SAP-score impact: the spec-correct summer clamp removed a ~+14% over-prediction that was previously masking residual upstream §3/§5 precision drift. The 000490 SAP score moved 57 → 60 (target 57; tolerance updated to "within 3 points" with the full delta breakdown in test_elmhurst_000490_end_to_end_sap_score_currently_within_3_points). 000474 / 000490-kWh tests still pass.
Remaining §8 work
- Appendix H solar space heating (98b) — orchestrator emits
solar_space_heating_monthly_kwh = (0,)·12always. No Elmhurst fixture exercises it. Wire when a solar-space-heating cert lands. - 000490 SAP-score gap to 57 (currently 60) — needs upstream §3 (transmission HLC), §5 (internal gains), and the §4 water heating cert path tightened. Likely closes incrementally as the §9–§14 fuel/cost/rating chain is rebuilt + the legacy
predicted_hot_water_kwhis replaced bywater_heating_from_cert.
§8c — slice progress (xlsx rows 435–466)
| 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
- Table 5a exclusion in cooling gains (103) —
cert_to_inputscurrently passesmonthly_total_gains_w = (0,)*12to 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 acooling_gains_from_certhelper mirroringinternal_gains_from_cert+solar_gains_from_cert. Triggered by the first cooling-enabled cert. - RdSAP cooled-area defaulting (105) —
cert_to_inputscurrently passescooled_area_fraction = 0.0always. Forhas_fixed_air_conditioning=Truecerts 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. - 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 onMonthlyEntry.space_cool_requirement_kwhbut doesn't propagate into fuel costs or CO2. Triggered by the first cooling-enabled cert. - §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 466–470)
| 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
- §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. - Σ(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 470–614)
| 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
- PCDB integration — see Prioritised gap list item 1. Blocks PDF-derived ALL_FIXTURES pinning + closes 000490 e2e gap. ADR-0010 §4 prerequisite.
- 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. - 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. - Table 4f pumps/fans breakdown (230a)-(230h) → (231) — replace opaque
CalculatorInputs.pumps_fans_kwh_per_yrscalar with per-source sub-lines. Likely a separate sweep slice once aux electricity becomes load-bearing for ranking. - Appendix Q items (236)/(237) — placeholder until a Q-item cert lands.
- (238) total delivered energy on SapResult — promote from
intermediatedict when §10a or §13 requires it as a named output.
§10a — slice progress (xlsx rows ~614–740)
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
- §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. - Table 12a cert→Table12aSystem mapping for off-peak electric mains. Currently
cert_to_inputs._fuel_costreturns 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. - Table 13 immersion + HP-DHW-only WH fractions —
Table12aSystem.IMMERSION_OR_HP_DHW_ONLYraisesNotImplementedError; populate when first immersion fixture lands. - Electric CPSU → Appendix F fractions —
Table12aSystem.ELECTRIC_CPSUraises; populate when first CPSU fixture lands. - 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.
- (247a) Instant electric shower kWh routing — (64a) currently always 0 from
_hot_water_fuel_kwh_per_yr. Wire when electric-shower cert lodges. - (252) per-row Appendix M/N split — populate (233a)..(235d) when wind / hydro / micro-CHP fixtures land.
- (253)/(254) Appendix Q — zero placeholders; populate when a Q-item cert lands.
- 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 tofuel_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_response → cert_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.