Model/docs/HANDOVER_SUMMARY_AUDIT.md
Khalim Conn-Kowlessar 590cb97ef6 docs: session-9 close-out + session-10 handover (summary-report-based audit)
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>
2026-06-09 14:54:08 +00:00

145 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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):
1. **Error-bucket profiler** → scatter, no clean residual bias.
2. **Dropped-field scan** (raw-JSON field present but mapped-None) → every field plumbed.
3. **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.
4. **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.
5. **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:
1. **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*
`EpcPropertyData` so the description matches what we actually compute on.
2. **Pin the cascade to the worksheet line refs at abs=1e-4**`heat_transmission_section_from_cert`
for §3 (26)..(37), the water-heating/§4, §9a/§10a etc. Localise the divergence to a specific
line ref → extractor / mapper / calculator gap.
3. **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_current` BEFORE trusting its line refs; if the
repro diverges from lodged, the repro is the problem, not our cascade. (See
`reference_elmhurst_only_test_pattern` + the `_elmhurst_worksheet_000565` prototype for the
mapper-driven cascade-fixture shape.)
4. 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.76, 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%
- **`a0432977` glazing single/secondary/triple per RdSAP 10 Table 24 (THE BIG WIN, +16 certs).**
`_API_GLAZING_TYPE_TO_TRANSMISSION` only 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: profile `sap_windows[].glazing_type`,
decode vs `epc_codes.csv` `glazed_type`.
- **`872bc585` HW-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 branch `water_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.
- **`7878a969` fuel strict-raise** at the Table-12 factor boundary (the cert-8536 collision class).
- **`49fb6c1b` glazing g remap** (codes 4/5 → correct cascade g-slots) — correctness, 0 SAP impact.
- **`a7990edb` ROBUSTNESS GUARDS** (forcing functions): `_api_glazing_transmission` +
`_api_cascade_glazing_type` raise `UnmappedApiCode` on present-but-unmapped glazing;
`seasonal_efficiency` + `water_heating_efficiency` raise `UnmappedSapCode` on 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_schema` certs 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_wall` applies 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`.