8 slices shipped this session: S0380.96 RIR Unknown insulation (RdSAP 10 §3.10.1) S0380.97 Floor §9 insulation thickness (RdSAP 10 §5.13 Table 20) S0380.98 PCDB Table 322 ETL+parser (PCDF Spec §A.19) S0380.99 PCDB Table 329 ETL+parser (PCDF Spec §A.20) S0380.100 MEV SFPav + (230a) helpers (SAP 10.2 §2.6.4) S0380.101 HP SAP code → cat=4 mapper (SAP 10.2 Table 4a) S0380.102 Wire MEV into pumps_fans (SAP 10.2 Table 4f 230a) S0380.103 MEV-fan cost split (SAP 10.2 Table 12a Grid 2) Cert 000565 at HEAD `e3abe9b2`: sap_score (int) ✓ EXACT pumps_fans_kwh_per_yr ✓ EXACT (was +2.48 over) hot_water_kwh_per_yr ✓ 0 EXACT sap_score_continuous Δ +0.0182 (SH cascade-driven) 7 expected fails (was 9) Next slice candidate: S0380.104 investigate §3-§8 space-heating cascade -27 kWh under-count (cert-000565-specific; cohort certs pass at 1e-4). Alternative: S0380.105 CO2 MEV split (mirror of .103 for Table 12d monthly factors). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
16 KiB
Handover — post S0380.96..103 (RIR Unknown + §9 floor extractor + MEV PCDB arc + HP-on-E7 cost split)
Branch: feature/per-cert-mapper-validation. HEAD e3abe9b2.
Predecessor: HANDOVER_POST_S0380_95.md.
Slices committed this session (S0380.96..103)
Eight spec-cited slices. The first two closed remaining cert 000565 extractor/mapper gaps (RIR "Unknown" insulation + floor §9 "Insulation Thickness"). Slices .98..102 built the MEV PCDB decentralised cascade arc end-to-end (Tables 322 + 329 + SFPav formula + HP-category mapper + wiring). The final slice .103 closed the Table 12a Grid 2 MEV-fan cost split, completing the HP-on-E7 cost cascade.
| Slice | Commit | Spec | Cert 000565 outcome |
|---|---|---|---|
| S0380.96 | 32a4cf20 |
RdSAP 10 §3.10.1 (PDF p.24) — "Unknown" insulation → Table 18 col 4 age-band default | BP[4] FC1 cascade U: 2.30 → 0.15 ✓ EXACT. roof_w_per_k Δ +12.34 → +1.59 (closed -10.75). Continuous SAP Δ -0.44 → -0.20. |
| S0380.97 | 7121a86b |
RdSAP 10 §5.13 Table 20 (PDF p.47) — exposed/semi-exposed floor U by age × thickness | BP[2] Ext2 floor U: 0.51 → 0.22 ✓ EXACT. floor_w_per_k ✓ EXACT. sap_score 28 → 29 ✓ EXACT. Continuous SAP Δ -0.0001 (within 1e-4). |
| S0380.98 | b3330821 |
PCDF Spec Rev 6b §A.19 — PCDB Table 322 Format 427/428 | Foundation only. Typed parser + ETL + decentralised_mev_record(pcdb_id) lookup. 48 records ingested. No cascade integration. |
| S0380.99 | 433f4a49 |
PCDF Spec Rev 6b §A.20 — PCDB Table 329 Format 430/432 | Foundation only. Typed parser + ETL + mv_in_use_factors_record(system_type). 5 records ingested. No cascade integration. |
| S0380.100 | 44fb8c07 |
SAP 10.2 §2.6.4 equation (1) + Table 4f line (230a) | New module worksheet/mev.py — mev_sfp_av + mev_decentralised_kwh_per_yr pure helpers. AAA tests pin cert 000565 worksheet values. No cascade integration. |
| S0380.101 | 1b183f9c |
SAP 10.2 Table 4a (PDF p.165) — Heat-pump category 4 | HP SAP codes 211-227 / 521-527 → main_heating_category=4 in _elmhurst_main_heating_category (Elmhurst path). Cert 000565 Main 1 (SAP 224) flipped None→4. Transient regression: pumps_fans 255 → 125 (offset bug exposed). |
| S0380.102 | a0413155 |
SAP 10.2 §2.6.4 + Table 4f (230a) — Wire MEV cascade into _table_4f_additive_components |
pumps_fans_kwh_per_yr 255 → 252.5159 ✓ EXACT. Schema + extractor + mapper for MV PCDF index / wet rooms / duct type. Elmhurst fan-count convention reverse-engineered from cert 000565 (TODO: validate on a 2nd MEV cert). |
| S0380.103 | e3abe9b2 |
SAP 10.2 Table 12a Grid 2 (PDF p.191) — FANS_FOR_MECH_VENT blended rate on off-peak |
MEV-fan cost weighting: 127.5 kWh at 11.6644 p/kWh + 125 kWh at 13.2440 p/kWh → effective 12.4467 p/kWh. cost Δ +£0.39 → -£1.62 (sign flipped; SH cascade residual exposed). |
Test baseline at HEAD e3abe9b2: 597 pass + 7 expected 000565
fails (was 585 + 9 at start of session, with .96+.97 closing the
sap_score integer fail and .102 closing the pumps_fans fail). The
ETL test count grew by ~25 with the new PCDB tables.
Pyright net-zero per touched file across every slice.
Cert 000565 state (HEAD e3abe9b2)
Fabric subtotals
| Component | Cascade W/K | Worksheet W/K | Δ | Status |
|---|---|---|---|---|
| walls | 601.22 | 604.07 | -2.85 | sub-spec |
| party_walls | 65.13 | 65.13 | ✓ EXACT | S0380.91 |
| floor | 61.67 | 61.67 | ✓ EXACT | S0380.97 |
| roof | 52.97 | 51.38 | +1.59 | residual +1.29 BP[1] formula |
| windows | 9.60 | 11.48 | -1.88 | sub-spec |
| roof_windows | 5.02 | 3.58 | +1.44 | sub-spec |
| doors | 11.10 | 11.10 | ✓ EXACT | full pipeline plumbing |
| thermal_bridging | 129.35 | 128.65 | +0.70 | S0380.95 |
| total external area | 862.34 | 857.64 | +4.70 | S0380.95 |
SapResult pins (HEAD e3abe9b2)
| Pin | Cascade | Worksheet | Δ | Status |
|---|---|---|---|---|
| sap_score (int) | 29 | 29 | ✓ EXACT | S0380.97 |
| sap_score_continuous | 28.5269 | 28.5087 | +0.0182 | SH cascade-driven |
| ecf | 5.3850 | 5.3866 | -0.0016 | SH cascade-driven |
| total_fuel_cost_gbp | 4678.6372 | 4680.2593 | -1.6221 | SH cascade-driven |
| co2_kg_per_yr | 6445.8198 | 6447.6263 | -1.8065 | mix: CO2 MEV split + SH |
| space_heating_kwh_per_yr | 58980.8225 | 59008.3499 | -27.5274 | §3-§8 cascade gap |
| main_heating_fuel_kwh_per_yr | 34694.6015 | 34710.7941 | -16.1926 | downstream of SH |
| hot_water_kwh_per_yr | 3755.0288 | 3755.0288 | ✓ 0 EXACT | unchanged |
| lighting_kwh_per_yr | 1387.0237 | 1384.8353 | +2.1884 | sub-spec |
| pumps_fans_kwh_per_yr | 252.5159 | 252.5159 | ✓ 0 EXACT | S0380.102 |
Continuous SAP journey across this session
| Slice | sap_score (int) | sap_score_continuous | Δ vs ws |
|---|---|---|---|
| Pre-S0380.96 | 28 | 28.07 | -0.44 |
| S0380.96 | 28 | 28.31 | -0.20 |
| S0380.97 | 29 | 28.5086 | -0.0001 (within 1e-4!) |
| S0380.98..100 | 29 | 28.5086 | -0.0001 (no cascade change) |
| S0380.101 | 29 | 28.6942 | +0.1855 (transient — HP cat=4 only, MEV not yet wired) |
| S0380.102 | 29 | 28.5043 | -0.0044 (MEV wired, restored balance) |
| S0380.103 | 29 | 28.5269 | +0.0182 (MEV cost split exposed pre-existing SH residual) |
Per user direction feedback-spec-floor-skepticism + feedback-spec-floor-skepticism: each slice closed a true spec- correct intermediate-value bug. The continuous-SAP residual is now driven by a §3-§8 SH cascade under-count (main_heating_fuel -16 kWh) that was previously masked by the +£2.01 pumps_fans cost over-count.
Open work — prioritised next slices
S0380.104 — Investigate §3-§8 space-heating cascade -27 kWh
The current biggest residual driver. main_heating_fuel_kwh is -16.19 kWh under ws (34694.60 vs ws 34710.79) → SH cost £1.58 under ws → continuous-SAP +0.0182 OVER ws.
Possible causes:
- Heat transmission HLC residual — fabric subtotals net to net ~+29 W/K (post-S0380.95 fabric snapshot). Walls -2.85, roof +1.59, thermal_bridging +0.70, total_external_area +4.70. Roof BP[1] residual formula gap (+1.29 W/K, deferred from S0380.95) is the largest single localised item.
- Internal gains — pumps_fans gains contribution changes with HP cat=4 path; verify against ws line (70) by month.
- Solar gains / utilization factor — sub-spec window U-values leak into solar gains too.
- Mean internal temperature / per-month solve — possible convergence-loop tolerance issue on this multi-BP cert.
Approach: probe per-month space_heat_requirement_kwh vs ws
line (98c)m to localise. The cohort certs (000474..000516) hit SH
at 1e-4 so the §8 orchestrator IS correct on simpler dwellings —
something cert-000565-specific (Detailed-RR + multi-BP + HP + MEV
- FGHRS + solar HW + draught lobby) is the differentiator.
Expected closure: continuous SAP +0.0182 → within 1e-4.
S0380.105 — CO2 cascade MEV split (Table 12d monthly factors)
Mirror of S0380.103 for CO2. Cert 000565 worksheet line (267):
Pumps, fans and electric keep-hot 252.5159 × 0.1412 = 35.3349
Cascade pumps_fans_co2_factor_kg_per_kwh = 0.14116 (kWh-weighted
Table 12d monthly factor for code 30) → 35.6453 kg → +0.31 over ws.
Cause: cascade uses a single Table 12d profile across all pumps_fans kWh. MEV fans have a different MONTHLY DISTRIBUTION than central- heating pumps + flue fans (MEV runs year-round at 0.5 ach; pumps run heating season only). The worksheet integrates separately.
Slice scope: add MevFanEntry-style CO2 helper + new
pumps_fans_co2_factor_kg_per_kwh resolution that weights the two
streams.
Impact: -0.31 kg/yr → continuous SAP downstream.
S0380.106 — PE cascade MEV split (Table 12e monthly factors)
Mirror of S0380.105 for primary energy. Analogous structure.
S0380.107 — BP[1] residual formula refinement (roof)
BP[1] Ext1 currently has residual +3.68 m² over worksheet (cascade
21.93 vs ws 18.25). The Simplified A_RR formula 12.5 × √(34/1.5)
gives 59.51 — minus 37.58 lodged walls = 21.93. Worksheet uses 18.25.
Hypothesis: Ext1's RR height = 3.0 m (not 2.45 m assumed by formula).
A height-aware formula like A_RR = perimeter × actual_RR_height
might match. Need investigation against multiple Detailed-mode certs
with non-2.45 RR heights.
Impact if closed: roof -1.29 W/K (residual drops by 3.68 × 0.35).
S0380.108 — Lighting +2.19 kWh trace residual
Cascade 1387.02 vs ws 1384.84. Sub-spec but breaks 1e-4 strict pin.
§5 Appendix L lighting cascade. Likely a per-cert-lodging gap (bulb count, fixed/non-fixed lighting fraction).
Deferred (unchanged from earlier handovers)
- 12 gas-combi PV certs at +0.5..+1.6 PE (no worksheets)
- 5 SAP-residual API-only certs (no worksheets)
MEV PCDB arc — architecture summary
The S0380.98..103 arc landed the entire MEV decentralised cascade end-to-end. Architecture (in dependency order):
PCDB pcdb10.dat
↓ ETL (etl.py, parser.py)
Table 322 (per-fan SFP) ←→ Table 329 (per-ducting IUF)
↓ runtime lookups (__init__.py)
decentralised_mev_record(pcdb_id) + mv_in_use_factors_record(system_type)
↓
worksheet/mev.py — pure helpers
mev_sfp_av(fan_entries) → §2.6.4 equation (1) avg SFP
mev_decentralised_kwh_per_yr(sfp_av, V) → Table 4f line (230a) kWh
↓
cert_to_inputs.py
_mev_decentralised_kwh_per_yr_from_cert(epc) — composer
reads epc.mechanical_ventilation_index_number, .wet_rooms_count,
.mechanical_vent_duct_type
builds Elmhurst per-fan count distribution
invokes mev.py helpers
↓
_table_4f_additive_components(epc) adds the MEV contribution
↓
pumps_fans_kwh_per_yr final cascade output
For COST (S0380.103):
_pumps_fans_fuel_cost_gbp_per_kwh(tariff, mev_kwh, total_pumps_fans_kwh)
→ kWh-weighted blended rate (FANS_FOR_MECH_VENT vs ALL_OTHER_USES)
CalculatorInputs.pumps_fans_fuel_cost_gbp_per_kwh: Optional[float]
Calculator legacy path uses it via `or other_fuel_cost_gbp_per_kwh`.
Open question for the next agent: the Elmhurst per-fan-count
convention in _mev_decentralised_kwh_per_yr_from_cert was reverse-
engineered from cert 000565 alone:
- Each PCDB-defined config (1..6) gets baseline count = 1
- Through-wall kitchen (5): wet_rooms_count fans total
- Through-wall other wet (6): wet_rooms_count + 1 fans total
When a 2nd MEV cert lands, validate this. May need to consult Elmhurst's documentation or the BRE-published SAP 10.2 implementation guide.
How to run the baseline
PYTHONPATH=/workspaces/model python -m pytest \
backend/documents_parser/tests/test_summary_pdf_mapper_chain.py \
backend/documents_parser/tests/test_elmhurst_extractor.py \
backend/documents_parser/tests/test_elmhurst_end_to_end.py \
domain/sap10_calculator/worksheet/tests/test_e2e_elmhurst_sap_score.py \
domain/sap10_calculator/worksheet/tests/test_appendix_h_solar.py \
domain/sap10_calculator/worksheet/tests/test_mev.py \
domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py \
domain/sap10_calculator/rdsap/tests/test_golden_fixtures.py \
domain/sap10_calculator/tests/test_pcdb_table_322_lookup.py \
domain/sap10_calculator/tests/test_pcdb_table_329_lookup.py \
--no-cov -q
Expected: 597 pass + 7 expected test_sap_result_pin[000565-*] fails.
The 7 expected fails (verbatim):
sap_score_continuous
ecf
total_fuel_cost_gbp
co2_kg_per_yr
space_heating_kwh_per_yr
main_heating_fuel_kwh_per_yr
lighting_kwh_per_yr
All driven by the §3-§8 SH cascade residual + lighting trace + CO2 MEV-split gap.
Files touched this session
| File | Slices | Change |
|---|---|---|
backend/documents_parser/elmhurst_extractor.py |
.96, .97, .102 | "Unknown" insulation token; §9 "Insulation Thickness" cell; §12.1 MV PCDF/Wet-Rooms/Duct-Type fields |
backend/documents_parser/tests/test_summary_pdf_mapper_chain.py |
.96, .97, .101, .103 | AAA tests for cert 000565 closures |
datatypes/epc/domain/mapper.py |
.96, .97, .101, .102 | _elmhurst_rir_insulation_thickness_mm → Optional[int]; floor insulation_thickness_mm plumbing; HP SAP-code → category 4; MV duct-type mapper + PCDF plumbing |
datatypes/epc/surveys/elmhurst_site_notes.py |
.97, .102 | FloorDetails.insulation_thickness_mm; VentilationAndCooling.mechanical_ventilation_pcdf_reference + .wet_rooms_count + .duct_type + .approved_installation |
domain/sap10_calculator/tables/pcdb/__init__.py |
.98, .99 | decentralised_mev_record + mv_in_use_factors_record lookups |
domain/sap10_calculator/tables/pcdb/etl.py |
.98, .99 | Table 322 + 329 typed ETL |
domain/sap10_calculator/tables/pcdb/parser.py |
.98, .99 | DecentralisedMevRecord + MvInUseFactorsRecord + parsers |
domain/sap10_calculator/tables/pcdb/data/pcdb_table_322_decentralised_mev.jsonl |
.98 | New file — 48 records |
domain/sap10_calculator/tables/pcdb/data/pcdb_table_329_mv_in_use_factors.jsonl |
.99 | New file — 5 records |
domain/sap10_calculator/worksheet/mev.py |
.100 | New module — mev_sfp_av + mev_decentralised_kwh_per_yr helpers |
domain/sap10_calculator/worksheet/tests/test_mev.py |
.100 | AAA tests pinning cert 000565 SFPav |
domain/sap10_calculator/rdsap/cert_to_inputs.py |
.102, .103 | _mev_decentralised_kwh_per_yr_from_cert composer; _pumps_fans_fuel_cost_gbp_per_kwh helper |
domain/sap10_calculator/calculator.py |
.103 | CalculatorInputs.pumps_fans_fuel_cost_gbp_per_kwh field + legacy cost path |
domain/sap10_calculator/tests/test_pcdb_etl.py |
.98, .99 | Added Tables 322, 329 to file list |
domain/sap10_calculator/tests/test_pcdb_table_322_lookup.py |
.98 | New file — 3 tests |
domain/sap10_calculator/tests/test_pcdb_table_329_lookup.py |
.99 | New file — 4 tests |
Spec source quick-reference
- SAP 10.2 full specification:
domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf- §2.6.4 (p.16) — Decentralised MEV SFPav equation (1) — S0380.100
- §5 Table 4a (p.165) — Heat-pump category 4 — S0380.101
- §5 Table 4f (p.174) — Annual electricity for fans / pumps — S0380.100, .102
- §5 Table 4g (p.176) — Default SFP for MV systems — S0380.99
- §10a Table 12a (p.191) — High-rate fractions on off-peak tariffs — S0380.103
- RdSAP 10 specification:
domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf- §3.10.1 (p.24) — Unknown insulation → Table 18 default — S0380.96
- §5.13 + Table 20 (p.47) — Exposed/semi-exposed floor U-values — S0380.97
- PCDF Spec Rev 6b:
domain/sap10_calculator/docs/specs/PCDF_Spec_Rev-06b_12_May_2021.pdf- §A.19 Format 427 (Decentralised MEV) — S0380.98
- §A.20 Format 430 (MV In-Use Factors) — S0380.99
- SAP 10.3 at
sap-10-3-full-specification-2026-01-13.pdf: DO NOT reference (feedback-sap-10-2-only-never-10-3)
Memory updated this session
project_cert_000565_recovery_state— slice-by-slice closure table for .96..103 + open-work analysisMEMORY.md— index entry refreshed at HEADe3abe9b2
What NOT to do
- Don't reference SAP 10.3 (feedback-sap-10-2-only-never-10-3).
- Don't widen pin tolerances or xfail residual gaps (feedback-zero-error-strict). The 7 cert 000565 fails are the work queue.
- Don't re-investigate any closed work (.91..103). All settled.
- Don't add new helpers to
domain/sap10_ml/— deprecation path per project-sap10_ml-deprecation. New cascade helpers belong underdomain/sap10_calculator/. - Don't avoid spec-correct closures because continuous SAP drifts away — user explicitly OK'd transient drift. Zero error achievable only when every component is spec-correct.
Memory hygiene
After the next slice, update:
project_cert_000565_recovery_state— append slice closure + refresh the open work-items tableMEMORY.md— refresh HEAD + one-line summary
Good luck.