Model/domain/sap10_calculator/docs/HANDOVER_POST_S0380_95.md

14 KiB
Raw Blame History

Handover — post S0380.91..95 (party-wall + AP4/MEV + §5.14 floor + RIR insulation + Detailed-RR residual)

Branch: feature/per-cert-mapper-validation. HEAD fa6974bd. Predecessor: HANDOVER_POST_S0380_90.md.

Slices committed this session (S0380.91..95)

Five spec-cited slices targeting cert 000565 closure. The user clarified their primary metric is sap_score_continuous (not just integer sap_score), but is OK with temporary continuous-SAP drift as long as each slice closes a true spec-correct sub-component gap. Zero error is the eventual goal; achievable only when every component is spec-correct.

Slice Commit Spec Cert 000565 outcome
S0380.91 83218630 RdSAP 10 §5.10 Table 15 row 3 (PDF p.42) — "Cavity masonry filled: 0.2 W/m²K" party_walls 93.26 → 65.13 ✓ EXACT. sap_score 27 → 28 (Δ-2 → -1). SH +1460 → +631 (57% closed). Continuous SAP Δ-1.16 → -0.52. New synthetic SAP10 code WALL_CAVITY_FILLED_PARTY=11.
S0380.92 a7894b11 SAP 10.2 §2 (17a)/(18)/(23a)/(24c) AP4 + MEV sap_score 28 → 29 ✓ EXACT. cascade (18) pressure_test_ach 2.4037 → 2.0287 ✓ EXACT vs ws 2.0287. SH +631 → -367 (~75% closed). 5-layer slice: AP4 + MEV-decentralised plumbing across schema / extractor / mapper / cert_to_inputs. Coupling-aware bundling.
S0380.93 23aaa4fa RdSAP 10 §5.14 (PDF p.47) — "Floor above partially heated: 0.7 W/m²K" BP[1] floor U: 0.76 → 0.70 ✓ EXACT. floor_w_per_k 72.41 → 70.37 (Δ+10.74 → +8.70). sap_score 29 ✓ EXACT unchanged. Continuous SAP +0.26 → +0.30 (small drift).
S0380.94 78c57c0d RdSAP 10 Table 17 col 3b (PDF p.42-43) — "Stud wall PUR or PIR 400mm: 0.10 W/m²K" + extractor regex fixes BP[2] Stud Wall 2 cascade U: 2.30 → 0.10 ✓ EXACT. 4-layer slice: extractor regex ^\d+\+?\s*mm$ + mapper allow-list ("PUR or PIR") + canonical insulation_type "rigid_foam" + cascade _is_rigid_foam. roof_w_per_k 43.44 → 34.64 (closed 8.80 over-count). Continuous SAP Δ+0.26 → +0.51 (drift).
S0380.95 fa6974bd RdSAP 10 §3.10.1 + §3.9.1 (PDF p.21-22, p.24) — Detailed-RR residual area cascade thermal_bridging 116.89 → 129.35 ✓ vs ws 128.65 (Δ-11.76 → +0.70). total_external_area 779.27 → 862.34 ✓ vs ws 857.64 (Δ-78.37 → +4.70). Big-leverage closure of the cascade -78 m² area gap. sap_score 29 → 28 transient regression (continuous crossed 28.5 → 28.07). Continuous SAP Δ+0.51 → -0.44 (absolute residual slightly closer to zero).

Test baseline at HEAD fa6974bd: 585 pass + 9 expected 000565 fails (was 574 + 9 at start of session). Cohort (000474/000477/000480/ 000487/000490/000516) + 9 golden API + 38 cohort-2 API all unaffected via discriminators (S0380.91: scoped to new code 11; S0380.92: defaulted None for cohort certs without AP4/MEV; S0380.93: floor type "Above partially heated" only on cert 000565 Ext1; S0380.94: cohort uses "Mineral or EPS" not "PUR or PIR"; S0380.95: discriminator filters out true Detailed-mode lodgements with full shell enumeration).

Pyright net-zero per touched file across every slice.

Cert 000565 state (HEAD fa6974bd)

Fabric subtotals (post-S0380.95)

Component Cascade W/K Worksheet W/K Δ Notes
walls 601.22 604.07 -2.85 sub-spec
party_walls 65.13 65.13 ✓ EXACT S0380.91
floor 70.37 61.67 +8.70 BP[2] Ext2 200mm insulation extractor gap
roof 63.72 51.38 +12.34 BP[4] FC1 + BP[1] residual (see below)
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
HTC fabric 966.51 937.06 +29.45 Net cascade over by ~29 W/K

Ventilation subtotals (post-S0380.92)

Line Cascade Worksheet Δ
(18) pressure_test_ach 2.0287 2.0287 ✓ EXACT
(21) shelter-adj ach 1.7244 1.7244 ✓ EXACT
mean (25)m 2.1360 2.1360 ✓ EXACT (was +0.149 over)
mv_kind EXTRACT_OR_PIV_OUTSIDE (24c) ✓ correct
mv_system_ach 0.5 (23a) 0.5 ✓ EXACT

SapResult pins (HEAD fa6974bd)

Pin Cascade Worksheet Δ Cause
sap_score (int) 28 29 -1 Continuous below 28.5 threshold
sap_score_continuous 28.07 28.51 -0.44 Cascade SH over-count drives lower SAP
ecf 5.43 5.39 +0.05 Downstream
total_fuel_cost_gbp 4720.79 4680.26 +40.53 Downstream
co2_kg_per_yr 6497.82 6447.63 +50.20 Downstream
space_heating_kwh 59541.61 59008.35 +533.26 Cascade HTC over
main_heating_fuel 35031.76 34710.79 +320.96 SH × 1/COP=1.70
hot_water_kwh 3755.03 3755.03 ✓ 0 EXACT unchanged
lighting 1387.02 1384.84 +2.19 sub-spec
pumps_fans 255.00 252.52 +2.48 MEV PCDB external data

Continuous SAP journey across this session

Slice sap_score (int) sap_score_continuous Δ vs ws
Pre-S0380.91 27 27.35 -1.16
S0380.91 28 27.99 -0.52
S0380.92 29 28.77 +0.26
S0380.93 29 28.81 +0.30
S0380.94 29 29.02 +0.51
S0380.95 28 28.07 -0.44

User direction: continuous SAP residual is the primary metric. Temporary drift is OK when fixing real spec gaps; zero error achievable only when every component is spec-correct.

Open work — prioritised next slices

S0380.96 — BP[4] Flat Ceiling 1 "Unknown PUR or PIR" lodgement (highest leverage)

Spec-divergence question: worksheet shows U=0.15 for the lodgement "Unknown thickness, PUR or PIR" → matches Table 17 col 4 (flat ceiling, PUR/PIR) at 200mm row. So Elmhurst applies a convention "Unknown thickness + known material → assumed 200mm".

Per RdSAP 10 spec literal: _u_rr_table_17 with insulation_thickness _mm=None falls back to u_rr_default_all_elements (Table 18 col 4). For age band M = 0.15. Coincidence? Or is the spec text consistent?

Investigation needed:

  1. Verify Elmhurst's 200mm convention against RdSAP spec edge cases
  2. If "Unknown + known material" should fall back to Table 18 col 4 default, then U=0.15 for age M IS correct
  3. Cert 000565 BP[4] rir_age=M → u_rr_default_all_elements(ENG, M) returns 0.15 (per probe). So if the cascade routes Flat Ceiling 1 through insulation_thickness_mm=None (not 0), it would return 0.15 ✓

Current fixture state: BP[4] Flat Ceiling 1 has insulation_thickness _mm=0 (extractor stores "" → mapper returns 0). Worksheet expects the Table 18 col 4 fallback path (None → 0.15).

Cleanest fix: when extractor sees "Unknown" in insulation cell, store it. Mapper translates "Unknown" → insulation_thickness_mm=None (not 0). Cascade's existing _u_rr_table_17 handles None → u_rr_default_all_elements.

Expected closure:

  • BP[4] Flat Ceiling 1 cascade U: 2.30 → 0.15 ✓
  • roof_w_per_k: 63.72 → 53.97 (closes -10.75)
  • Then BP[1] residual +1.29 W/K remains → roof Δ ~+1.6
  • Continuous SAP: -0.44 → ~ -0.10 (much closer to zero)
  • Integer sap_score may flip back to 29

S0380.97 — BP[2] Ext2 floor 200mm insulation thickness extractor

Spec citation: RdSAP 10 §5.13 Table 20 (PDF p.47) — exposed/semi- exposed floor U-value by age band + insulation thickness. Cert 000565 Ext2 Summary §9 lodges "Insulation Thickness: 200 mm" but extractor's _floor_details_from_lines doesn't read it. Fixture: BP[2] ground floor floor_insulation_thickness=None → cascade returns 0.51 vs ws 0.22.

Slice span (multi-layer):

  1. Extractor: parse "Insulation Thickness" inside each §9 extension block
  2. Schema: FloorDetails.insulation_thickness_mm: Optional[int] (currently has insulation: str only)
  3. Mapper: plumb to SapBuildingPart.floor_insulation_thickness
  4. Cascade: already reads floor_ins_thickness and dispatches via u_exposed_floor (Table 20) — no cascade change needed.

Expected closure:

  • BP[2] floor U: 0.51 → 0.22 ✓
  • floor_w_per_k: 70.37 → 61.67 ✓ EXACT vs ws (closes +8.70)
  • Continuous SAP: cascade SH would DROP (less heat loss) → continuous SAP UP — drifts AWAY from worksheet. Per user direction OK if spec-correct.

S0380.98 — BP[1] residual formula refinement (lower priority)

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

Deferred (unchanged from earlier handovers)

  • MEV PCDB Table 4f component for pumps_fans +2.5 (external data)
  • HP SAP code → main_heating_category=4 mapper extension
  • 12 gas-combi PV certs at +0.5..+1.6 PE (no worksheets)
  • 5 SAP-residual API-only certs (no worksheets)

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/rdsap/tests/test_cert_to_inputs.py \
    domain/sap10_calculator/rdsap/tests/test_golden_fixtures.py \
    --no-cov -q

Expected: 585 pass + 9 expected test_sap_result_pin[000565-*] fails.

The 9 expected fails (verbatim):

sap_score
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
pumps_fans_kwh_per_yr

Files touched this session

File Slices Change
datatypes/epc/domain/epc_property_data.py .92, .93 SapVentilation.air_permeability_ap4_m3_h_m2 + SapVentilation.mechanical_ventilation_kind; SapFloorDimension.is_above_partially_heated_space
datatypes/epc/surveys/elmhurst_site_notes.py .92 VentilationAndCooling.air_permeability_ap4_m3_h_m2 + .mechanical_ventilation_type
backend/documents_parser/elmhurst_extractor.py .92, .94 §12.1/12.2 AP4 + MV-type parsing; _RIR_INSULATION_THICKNESS_RE extended to ^\d+\+?\s*mm$; allow-list adds "PUR or PIR"
datatypes/epc/domain/mapper.py .91, .92, .93, .94 CF→11 mapper entry; AP4 + MV-kind plumbing + _ELMHURST_MV_TYPE_TO_KIND + strict-raise; _is_floor_above_partially_heated_space helper; _RIR_INSULATION_TYPE_TO_SAP10 adds "PUR or PIR"/"PUR"/"PIR" → "rigid_foam"; _elmhurst_rir_insulation_thickness_mm regex extended
domain/sap10_calculator/rdsap/cert_to_inputs.py .92 ventilation_from_cert reads air_permeability_ap4_m3_h_m2 + resolves mechanical_ventilation_kind name → enum; MEV sets mv_system_ach=0.5
domain/sap10_ml/rdsap_uvalues.py .91, .93, .94 WALL_CAVITY_FILLED_PARTY=11 constant + u_party_wall CF branch (0.2); u_floor_above_partially_heated_space() helper (0.7); _RR_RIGID_FOAM_INSULATION_TYPES adds "rigid_foam"
domain/sap10_calculator/worksheet/heat_transmission.py .93, .95 Floor dispatch adds is_above_partial → u_floor_above_partially_heated_space() branch; Detailed-RR branch adds §3.10.1 residual area computation with has_roof_lodgement discriminator

Spec source quick-reference

  • SAP 10.2 full specification: domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf
    • §2 (p.12-13) — Infiltration / pressure test (AP50/AP4) — S0380.92
    • §2 (p.13, 133) — MEV (23a/24c) — S0380.92
  • RdSAP 10 specification: domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf
    • §3.9.1 (p.21-22) — Simplified A_RR formula — S0380.95
    • §3.10.1 (p.24) — Detailed RR residual area — S0380.95
    • §5.10 Table 15 (p.42) — Party-wall U-values — S0380.91
    • §5.14 (p.47) — Floor above partially heated — S0380.93
    • Table 17 col 3b (p.42-43) — Stud wall PUR/PIR — S0380.94
  • 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 .91..95 + remaining open-work analysis
  • MEMORY.md — index entry refreshed at HEAD fa6974bd

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 9 cert 000565 fails are the work queue.
  • Don't re-investigate any closed work: party-wall CF (.91), AP4/MEV (.92), §5.14 partially-heated (.93), RIR PUR or PIR (.94), §3.10.1 residual area (.95). All settled.
  • Don't add new helpers to domain/sap10_ml/ — that folder is on the deprecation path per project-sap10_ml-deprecation. New cascade helpers should land under domain/sap10_calculator/. (Editing existing sap10_ml files for incremental fixes is fine.)
  • 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 table
  • MEMORY.md — refresh HEAD + one-line summary

Good luck.