Session 9 ran five independent data-driven audits (profiler, dropped-field scan, CO2/PE reconciliation, cross-provider LIG parity, HW-demand reconciliation) — all converged on diffuse remaining gap — and shipped glazing Table-24 (+16 certs) + HW-only heat-network DLF, taking 54.90% -> 56.8% within-0.5. The data-driven seam is exhausted; session 10 switches to worksheet-level ground truth via the summary-report-based per-cert audit. New agent prompt at HANDOVER_SUMMARY_AUDIT.md with method, starter candidate certs, ruled-out list, and conventions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 KiB
Handover — API SAP accuracy, SESSION 10: summary-report-based per-cert audit
You're continuing API→SAP accuracy work on branch feature/per-cert-mapper-validation in
/workspaces/model, HEAD 872bc585. This is a long-lived working branch — NEVER PR to
main; the user pushes/PRs when ready. 31 commits ahead of origin, unpushed.
THE GOAL (measurable, unchanged)
100% of API records with a lodged SAP compute within 0.5 SAP of the API's
energy_rating_current. Headline gauge:
PYTHONPATH=/workspaces/model python scripts/eval_api_sap_accuracy.py
Current: 56.8% within-0.5 (within-1.0 72.2%, within-2.0 84.8%, mean|err| 1.197, median 0.438,
signed −0.229, 909 computed / 0 raises, 100 unsupported_schema). Writes _results.csv to the
cache. Re-profile with scripts/profile_api_error.py --min-n 12; component decomposition with
scripts/decompose_api_cost_error.py. ~1009 cached API JSONs at /tmp/epc_2026_sample
(EPC_SAMPLE_CACHE overrides).
⚠️ THE PIVOT — why this session is DIFFERENT
The previous session (9) ran FIVE independent data-driven audit angles, all of which converged on the same conclusion — the clean systematic levers are harvested and the remaining gap is diffuse (data-fidelity matching the reference software, data-composition, per-cert scatter):
- Error-bucket profiler → scatter, no clean residual bias.
- Dropped-field scan (raw-JSON field present but mapped-None) → every field plumbed.
- CO2/PE reconciliation vs lodged → systematic +15% but it's a factor-basis difference (our SAP 10.2 Table 12 vs the lodged EPC's published basis), NOT demand (cost-SAP matches), NOT scope (our CO2/PE correctly exclude appliances/cooking per spec line 326). Off-goal — CO2/PE don't feed the cost-based SAP rating.
- Cross-provider parity (LIG-21.0 vs 21.0.1, same builder): LIG under-rates −0.59, cleanest in cavity (LIG −0.45 vs standard −0.01) — but the wall U computes CORRECTLY; the cause is diffuse (composition: more solid-brick/system-built/non-PCDB mains, all under-rating in BOTH datasets; plus per-cert scatter). No recoverable LIG-specific mapping bug.
- HW-demand reconciliation vs lodged HW cost → median residual ≈ £0, well-calibrated. The high-HW/m² certs are small flats (SAP HW floor effect) and are ACCURATE.
The data-driven seam is mined out. The user (correctly — they drove the glazing find) wants to switch to worksheet-level ground truth: the summary-report-based audit. Do NOT re-run the five angles above expecting a new clean bug; pursue per-cert worksheet pins instead.
THE METHOD — summary-report-based audit (this session's loop)
For a chosen cert, the user generates two Elmhurst worksheets from the cert's OWN API JSON
(/tmp/epc_2026_sample/<cert>.json): the P960 (full SAP worksheet, line refs (1a)..(486))
and the Summary. Your loop:
- Describe the cert field-by-field FIRST (so the user can reproduce it in Elmhurst): dwelling
type, TFA, age band, every building part (wall/roof/floor construction + insulation + thickness),
windows, the heating system (sap_main_heating_code, category, control, emitter, fuel, PCDB index),
water heating (whc, fuel, cylinder), ventilation, PV. Use the mapper to dump the mapped
EpcPropertyDataso the description matches what we actually compute on. - Pin the cascade to the worksheet line refs at abs=1e-4 —
heat_transmission_section_from_certfor §3 (26)..(37), the water-heating/§4, §9a/§10a etc. Localise the divergence to a specific line ref → extractor / mapper / calculator gap. - VALIDATE BEHAVIOUR against the LODGED SAP, not blindly against the user's repro. The user's
Elmhurst repros are APPROXIMATE (they often pick a slightly-wrong system / inputs). Confirm the
repro's continuous SAP ≈ the lodged
energy_rating_currentBEFORE trusting its line refs; if the repro diverges from lodged, the repro is the problem, not our cascade. (Seereference_elmhurst_only_test_pattern+ the_elmhurst_worksheet_000565prototype for the mapper-driven cascade-fixture shape.) - One confirmed cause = one TDD slice = one commit (conventions below).
Starter candidate certs (clean gas, single-building-part, schema 21.0.1 — NOT electric-fabric
tail, NOT LIG, NOT deproven). |err| in 0.7–6, good worksheet targets:
8700-1771-0622-8501-3963 -5.77 gas cat2 whc=903 (electric immersion HW on a gas main — odd)
2135-2729-0509-0142-6226 +5.29 sapcode 119
4700-6865-0122-1501-3963 -5.19 detached gas boiler
0700-6754-0922-3505-3963 +4.35 whc=911 (gas boiler/circulator for water only)
9093-3060-2207-6506-0204 +3.60 sapcode 502 cat9 (WARM AIR main) + whc=950 — see SESSION-9 below
0330-2817-5590-2096-7831 -2.95 gas cat2 mid-terrace
The full list (81 clean candidates) regenerates from the profiler/_results.csv. The user may pick
their own certs from domain knowledge — let them drive selection; your job is the field-by-field
description + the line-ref pin.
WHAT SHIPPED IN SESSION 9 (don't redo) — 54.90% → 56.8%
a0432977glazing single/secondary/triple per RdSAP 10 Table 24 (THE BIG WIN, +16 certs)._API_GLAZING_TYPE_TO_TRANSMISSIONonly mapped double-glazing [1,2,3,13,14]; single (5/15, U 4.8), secondary (4/11/12), triple (6/8/9/10) returned None → silent u_window default U=2.5. Single glazing at half its real heat loss was the killer. Method that found it: profilesap_windows[].glazing_type, decode vsepc_codes.csvglazed_type.872bc585HW-only heat-network DLF (whc 950/951/952). The Table 12c distribution loss fired only for_is_heat_network_main AND whc∈{901,902,914}; HW-only heat networks missed it entirely. Added a whc-gated branchwater_eff = plant_eff / DLF(RdSAP §10, spec p.36). All 3 corpus whc=950 certs improved in |err|; cert 9093 still +3.60 — its residual is the warm-air main (sapcode 502, cat 9), a SEPARATE cause and a good worksheet candidate.7878a969fuel strict-raise at the Table-12 factor boundary (the cert-8536 collision class).49fb6c1bglazing g remap (codes 4/5 → correct cascade g-slots) — correctness, 0 SAP impact.a7990edbROBUSTNESS GUARDS (forcing functions):_api_glazing_transmission+_api_cascade_glazing_typeraiseUnmappedApiCodeon present-but-unmapped glazing;seasonal_efficiency+water_heating_efficiencyraiseUnmappedSapCodeon present-but-unmapped codes (was the blind 0.80/0.78 default). 0 current-corpus impact (tables complete) — these are guards. KEY for this session: if a worksheet-audit cert RAISES, that's the guard surfacing a real gap — map the code. Also re-verify: efficiency table already covers WHC 908 (multi-point gas) / 950 (HW heat network) — those are NOT unmapped bugs.
RULED OUT — do NOT re-chase (verified this session + DEPROVEN list in HANDOVER_API_PROFILING.md)
- The 100
unsupported_schemacerts are full-SAP NEW BUILDS (assessment_type="SAP", mean rating 86, transaction_type 6 = new dwelling). Structurally different (sap_walls/sap_roofs/ sap_openings with measured U-values, DER, construction_year). Out of scope for a retrofit product — do NOT build a parallel pipeline. They're already excluded from the 56.8%. - Solid brick (gas, −0.52): spec-faithful —
u_wallapplies RdSAP §5.7 Table 13 thickness; direction wrong for a thickness gap. Data-fidelity (old houses outperform as-built). - Roof code-8 sloping-ceiling "insulated"-no-thickness (cert 7921 −23): data-fidelity, we ≡ Elmhurst at uninsulated. meter_type=3 (Unknown meter): data-fidelity. Orientation code-9 drop: the East/West "fix" HURTS the gauge; conservatory-only spec rule; leave it.
- LIG-21.0 divergence, CO2/PE +15%, HW-demand over-estimate: all diffuse / off-goal (see THE PIVOT).
CONVENTIONS (non-negotiable)
One cause = one slice = one commit; spec citation (page+line) in the message (the user
explicitly asks us to confirm against the SAP 10.2 / RdSAP 10 PDFs in domain/sap10_calculator/ docs/specs/ before claiming a fix — see feedback_spec_citation_in_commits); AAA test headers
(# Arrange / # Act / # Assert); abs(x-y)<=tol not pytest.approx (strict-pyright);
private-symbol test imports single-line with # pyright: ignore[reportPrivateUsage]; SAP 10.2
only (ignore the 10.3 PDF); no tolerance-widening / xfail; RdSAP is deterministic — every fix is a
spec rule, apply uniformly even when it unmasks offsetting errors, but flag any within-0.5
regression to the user; pyright strict net-zero (baseline-compare via git stash; avoid
**dict unpacking into make_minimal_sap10_epc — explodes pyright); stage files BY NAME (the
tree carries unrelated scripts/ + sap worksheets/ changes — never git add -A); end commit
messages with Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>.
Regression gate after any calc/mapper change (goldens esp. 6035 + 000565 are the gate):
PYTHONPATH=/workspaces/model python -m pytest tests/domain/sap10_calculator/ \
domain/sap10_ml/tests/ datatypes/epc/ backend/documents_parser/tests/ -q
IGNORE these pre-existing fails (not yours): test_total_floor_area, the 2 stone-wall U tests
in test_rdsap_uvalues.py, the flaky test_other_client_error_propagates (passes in isolation).
ARCHITECTURE (quick map)
API path = EpcPropertyDataMapper.from_api_response(doc) → from_rdsap_schema_21_0_1 →
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES) → calculate_sap_from_inputs(...). Fabric U via
domain/sap10_ml/rdsap_uvalues.py (u_wall/u_roof/u_floor/u_window) feeding
worksheet/heat_transmission.py (per-BP loop). HW in cert_to_inputs §4 + worksheet/water_heating.py.
Efficiency in domain/sap10_ml/sap_efficiencies.py (seasonal_efficiency / water_heating_efficiency,
now strict-raising). Fuel cost/CO2/PE: tables/table_12.py + tables/table_32.py. SAP equation:
worksheet/rating.py (ECF = 0.42·cost/(TFA+45)). The §3 breakdown helper for pins:
cert_to_inputs.heat_transmission_section_from_cert(epc) → HeatTransmission (every (26)..(37) line
ref). KEY INSIGHT: the gov-API JSON is the published OUTPUT of RdSAP software, not its input —
route fields Elmhurst doesn't consume to the spec default.
READ ALSO
docs/HANDOVER_API_PROFILING.md— the full SESSION-3..9 log + the load-bearing DEPROVEN list.- Auto-memories:
project_per_cert_mapper_validation_state,reference_unmapped_sap_code,reference_unmapped_api_code,reference_fuel_code_collision,feedback_software_no_special_handling,feedback_spec_citation_in_commits,feedback_worksheet_not_api_reference,reference_elmhurst_only_test_pattern.