docs: handover + next-agent prompt post S0380.81..84 (Table 32 default + Table 12a Grid 2 CO2 + RR fold-in)

Captures the 4-slice arc this session — Table 32 default prices (sap_score
28 → 29 EXACT), Table 12a Grid 2 dual-rate CO2 (CO2 65% closed), extractor
gable_type recognition + mapper preservation of cert 9501, and the full
RR mapper + cascade fix per RdSAP 10 §3.9.2 + §3.10 + Table 4 (11
per-BP RR surface areas EXACT vs worksheet PDF).

Documents the BP main-wall residual −161 W/K diagnostic localising the
next slice block to two spec-cited cascade gaps: Curtain Wall (−112
W/K, no _ENG_WALL entry for WALL_CURTAIN=9) + thin-wall stone granite
alt (−47 W/K, _insulation_bucket short-circuit + thin-wall §6.6/§6.7
formula). Each spans extractor → datatype → mapper → cascade and is a
single coherent slice when the spec lookup is tractable.

NEXT_AGENT_PROMPT_POST_S0380_84.md prescribes S0380.85 (Curtain Wall)
as the highest-leverage next slice with audit + implementation +
expected-outcome details.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-29 23:33:18 +00:00 committed by Jun-te Kim
parent 3c41461811
commit 58d5376cfd
2 changed files with 447 additions and 0 deletions

View file

@ -0,0 +1,257 @@
# Handover — post S0380.81..84 (Table 32 default + Table 12a Grid 2 CO2 + RR fold-in)
Branch: `feature/per-cert-mapper-validation`. **HEAD `49622f55`**.
Predecessor: [`HANDOVER_POST_S0380_80.md`](HANDOVER_POST_S0380_80.md).
## Slices committed this session (S0380.81..84)
Four spec-cited slices, two clean closures + one extractor data-completion
+ one structural RR fix that surfaces the next named gap.
| Slice | Commit | Spec | Cert 000565 outcome |
|---|---|---|---|
| **S0380.81** | `9338914f` | RdSAP 10 §19.1 (PDF p.80-81) — "use Table 32 prices (not Table 12) for §10a/§10b" | sap_score 28 → **29 EXACT** at the (28.5) rounding boundary. Cost residual £+3.62 → £2.39. |
| **S0380.82** | `27ead127` | SAP 10.2 Table 12a Grid 2 (p.191) + Table 12d/12e (p.194-195) — "All other uses" off-peak dual-rate | CO2 residual 8.92 → **3.08 kg/yr** (65% closed). Lighting + pumps_fans + electric_shower CO2/PE factors now blend Table 12d/12e high-rate × low-rate codes per Grid 2 fraction on off-peak certs. |
| **S0380.83** | `ed8fdc6a` | RdSAP 10 §3.10 + Summary PDF §8.1 schema | Extractor recognises `"Exposed"` + `"Connected"` `gable_type` (was Party / Sheltered / "Connected to heated space" only). Mapper elif extended to preserve cert 9501. Pure data-extraction completion; zero cascade impact. |
| **S0380.84** | `49622f55` | RdSAP 10 §3.9.2 + §3.10 + Table 4 (p.22) | Mapper drops Connected gables, routes Exposed → `gable_wall_external` with lodged U, surfaces Common Walls, applies spec area formula `L × (0.25 + H)` and `Σ` per-common-wall gable correction. Cascade adds `common_wall` kind handler. **11 per-BP RR surface areas EXACT vs worksheet PDF**. Cascade walls 322 → 443 W/K (43% closer to worksheet 604); party 153 → 93 (68% closer to worksheet 65). cert 000565 sap_score temporarily regressed 29 → 26 — see §"Why the regression is the correct signal" below. |
**Test baseline at HEAD `49622f55`:** 555 pass + 9 expected
`test_sap_result_pin[000565-*]` cascade-gap fails. Pyright net-zero on
every touched file. Cohort + golden + cert 9501 unaffected.
## Cert 000565 state (HEAD `49622f55`)
| Pin | Cascade | Worksheet | Δ | Cause |
|---|---:|---:|---:|---|
| sap_score (int) | 26 | 29 | **3** | RR fold-in (S0380.84) exposed BP main-wall gap; see §"BP main-wall residual 161 W/K diagnostic" |
| sap_score_continuous | 26.4972 | 28.5087 | 2.01 | Downstream of HTC over-count via space_heating +2591 |
| ecf | 5.5970 | 5.3866 | +0.21 | Downstream of cost +£182.6 |
| total_fuel_cost_gbp | 4862.88 | 4680.26 | +182.6 | Downstream of space_heating fuel cost |
| co2_kg_per_yr | 6684.52 | 6447.63 | +236.9 | Downstream of HP electricity over-fuel-use |
| **space_heating_kwh** | **61599.61** | **59008.35** | **+2591.3** | **BP main-wall cascade gap** (Curtain Wall 112 W/K + thin-wall alt 47 W/K — see diagnostic below) |
| main_heating_fuel | 36235.07 | 34710.79 | +1524.3 | Follows space_heating via 1/COP |
| **hot_water_kwh** | **3755.03** | **3755.03** | **✓ 0 EXACT** | §4 cascade fully closed (S0380.77..80) |
| lighting | 1387.02 | 1384.84 | +2.19 | Sub-spec |
| pumps_fans | 255.00 | 252.52 | +2.48 | MEV PCDB record missing (external data) |
### §4 HW cascade line refs (all EXACT, unchanged)
| Line | Cascade | Worksheet | Δ |
|---|---:|---:|---:|
| (45)m sum energy_content | 1286.3266 | 1286.3266 | ✓ 0 |
| (46)m sum distribution_loss | 192.9490 | 192.95 | ✓ <1e-3 |
| (57)m sum solar_storage | 596.9725 | 596.9725 | ✓ <1e-4 |
| (59)m sum primary_loss | 1176.77 | 1174.79 | +1.98 |
| (61)m combi_loss | 0.00 | 0.00 | ✓ 0 |
| (62)m sum total_demand | 3060.07 | 3060.07 | ✓ <1e-3 |
| (64)m sum (after solar) | 2778.72 | 2778.7213 | ✓ <1e-4 |
| (64a)m electric shower | 702.94 | 702.94 | ✓ <1e-4 |
| (217)m water-heater eff | 0.74 | 0.74 | ✓ EXACT |
| (219) HW fuel kWh | 3755.03 | 3755.0288 | ✓ <1e-3 |
### Per-BP RR surface areas (all EXACT after S0380.84)
Verified against the cert 000565 U985 worksheet "External Walls" + "Party
Walls" sections at 4 d.p. precision:
| BP | Surface | Spec formula | Worksheet | Cascade |
|---|---|---|---:|---:|
| 0 | Main GW1 Exposed | 4 × 2.45 (Simplified, no CW) | 9.80 | 9.80 ✓ |
| 0 | Main GW2 Sheltered | 6 × 2.45 | 14.70 | 14.70 ✓ |
| 1 | Ext1 CW1 | 9 × (0.25 + 1.0) (Simplified + CW) | 11.25 | 11.25 ✓ |
| 1 | Ext1 CW2 | 5 × (0.25 + 1.8) | 10.25 | 10.25 ✓ |
| 1 | Ext1 GW2 Exposed | 8 × (0.25+9) ((91)²+(91.8)²)/2 | 16.08 | 16.08 ✓ |
| 2 | Ext2 GW2 Exposed | 3 × 8 (Detailed) | 24.00 | 24.00 ✓ |
| 3 | Ext3 CW1 | 5 × (0.25 + 1.5) (Simplified + CW) | 8.75 | 8.75 ✓ |
| 3 | Ext3 CW2 | 7.5 × (0.25 + 0.3) | 4.13 | 4.13 ✓ |
| 3 | Ext3 GW1 Exposed | 9 × (0.25+7) ((71.5)²+(70.3)²)/2 | 27.68 | 27.68 ✓ |
| 4 | Ext4 CW1 | 4 × 1 (Detailed) | 4.00 | 4.00 ✓ |
| 4 | Ext4 CW2 | 3.5 × 0.6 (Detailed) | 2.10 | 2.10 ✓ |
### Cumulative cert 000565 closure (S0380.77 → .84)
| Pin | .77→ | .78→ | .79→ | .80→ | .81→ | .82→ | .83→ | .84 |
|---|---:|---:|---:|---:|---:|---:|---:|---:|
| hot_water_kwh | +1399 | +260 | 238 | **✓0** | ✓0 | ✓0 | ✓0 | ✓0 |
| sap_score (int) | +1 | 1 | 0 | 1 | **✓0** | ✓0 | ✓0 | **3** ⚠ |
| sap_score_continuous | +0.60 | 0.04 | +0.06 | 0.04 | +0.03 | +0.03 | +0.03 | 2.01 ⚠ |
| ecf | 0.06 | +0.00 | 0.01 | +0.00 | 0.00 | 0.00 | 0.00 | +0.21 ⚠ |
| total_fuel_cost_gbp | 53 | +3 | 5 | +4 | 2 | 2 | 2 | +183 ⚠ |
| co2_kg_per_yr | (n/a) | (n/a) | 58 | 9 | 9 | **3** | 3 | +237 ⚠ |
S0380.84 ⚠ rows are the documented BP main-wall surfacing (NOT a
regression of S0380.84's RR fix itself).
## Why the regression is the correct signal
S0380.84 closed the RR cascade routing to spec correctness. The 11
per-BP RR surface areas pin EXACT vs worksheet at 4 d.p. The cascade
walls subtotal moved 322 → 443 W/K (worksheet 604, 43% closed); party
153 → 93 (worksheet 65, 68% closed).
Pre-S0380.84 the cascade had two ~equal-magnitude bugs of opposite
sign that mostly cancelled at the SH-pin level:
- **RR cascade**: Exposed gables wrongly routed to party_walls at
U=0.25 (cascade over-counts party_walls by ~88 W/K)
- **BP main-wall cascade**: Curtain Wall + thin-wall alt missing
(cascade under-counts walls by ~161 W/K)
S0380.84 closed the first one. The second is now exposed as a +2591
kWh space_heating residual. Per `[[feedback-spec-citation-in-commits]]`
and `[[feedback-spec-floor-skepticism]]` the spec-correct fix ships
even when the test pin temporarily regresses; the diagnostic signal
is sharper now.
## BP main-wall residual 161 W/K diagnostic
Probed per-BP at HEAD `49622f55`:
| BP | Cascade U | Worksheet U | Δ contribution | Spec gap |
|---|---:|---:|---:|---|
| 0 Main | 0.32 | 0.35 | 1.6 W/K | sub-spec (Solid Brick A age, 75mm External insulation) |
| 0 Main alt1 | 0.32 | 2.34 | **46.5 W/K** | `_insulation_bucket(thk=120, ins_present=False)` returns 100 not 0 (docstring intent vs current code) + thin-wall §6.6/§6.7 for U=2.34 |
| 1 Ext1 | 1.70 | 1.70 | ✓ 0 | ✓ Spec-correct (Stone Granite E age, Unknown insulation) |
| 2 Ext2 (Curtain Wall) | 0.60 | 1.40 | **112.2 W/K** | `WALL_CURTAIN=9` defined `rdsap_uvalues.py:116` but no `_ENG_WALL` table entry; `u_wall` falls through to default Cavity table |
| 3 Ext3 (basement) | 0.45 | 0.45 | ✓ 0 | ✓ Spec-correct |
| 4 Ext4 (basement) | 0.35 | 0.35 | ✓ 0 | ✓ Spec-correct |
Total: **160.3 W/K** (matches the observed 161 W/K).
### Gap #1 — Curtain Wall (largest, 112 W/K)
**Where:** [domain/sap10_ml/rdsap_uvalues.py:116](../../sap10_ml/rdsap_uvalues.py) — `WALL_CURTAIN: Final[int] = 9` is defined but has no entry in `_ENG_WALL` and is not in the `known_types` set at `u_wall:373-376`. When the cert lodges `wall_construction=9`, `u_wall` falls through to `_DEFAULT_WALL_BY_AGE` (default cavity) and returns the cavity-wall U for that age band.
**Cert 000565 BP[2] Ext2** lodges `Type: CW Curtain Wall` + `Curtain Wall Age: Post 2023` per Summary PDF §7. The "Curtain Wall Age" is a separate per-BP attribute from the dwelling-wide `construction_age_band` — the BP is age `H` (1991-1995) but the curtain wall itself was installed Post-2023. Worksheet uses Curtain Wall Post-2023 U=1.40.
**Slice span:**
1. Extractor (`backend/documents_parser/elmhurst_extractor.py`) — currently doesn't surface "Curtain Wall Age" from Summary §7
2. `datatypes/epc/surveys/elmhurst_site_notes.py` — add `curtain_wall_age` to `WallDetails`
3. `datatypes/epc/domain/epc_property_data.py` — add `curtain_wall_age` to `SapBuildingPart`
4. `datatypes/epc/domain/mapper.py` — thread through both API + Elmhurst paths
5. `domain/sap10_ml/rdsap_uvalues.py` — Curtain Wall U-value lookup by age; add `WALL_CURTAIN` to `known_types`
**Spec citation needed:** RdSAP 10 Table 6 or related — locate the canonical Curtain Wall U-values per age category. The worksheet says 1.40 for Post-2023; need to verify the full table.
### Gap #2 — Thin-wall alt stone granite (47 W/K)
**Where:** Two coupled bugs.
1. `domain/sap10_ml/rdsap_uvalues.py:160 _insulation_bucket` ignores `insulation_present=False` when `thickness_mm > 0`. Docstring says "when not present, the as-built (bucket 0) row applies regardless" but the code falls through to thickness-bucket selection. For BP[0] alt1 with `wall_insulation_type=4` (None) but `wall_insulation_thickness='120'`, bucket returns 100 not 0.
2. The `wall_insulation_thickness='120'` on `SapAlternativeWall` is actually the **WALL thickness** lodged in Summary §7 ("Alternative Wall 1 Thickness: 120 mm"), NOT an insulation thickness. Per [[feedback-no-misleading-insulation-type]] this should land on a new `SapAlternativeWall.wall_thickness_mm` field (mirror of `SapBuildingPart.wall_thickness_mm`).
3. Even with bucket 0, `_TYPICAL_STONE_UNINSULATED[0] = 1.7` + dry-lined adjustment = 1.32. Worksheet wants 2.34. This is the RdSAP 10 §6.6/§6.7 "thin-wall" formula for stone walls below typical thickness — needs implementing in `u_wall`.
**Slice span:**
1. Extractor — surface "Alt 1 Thickness" as wall thickness (currently mapped to `wall_insulation_thickness`)
2. `datatypes/epc/domain/epc_property_data.py``SapAlternativeWall.wall_thickness_mm: Optional[int]`
3. `datatypes/epc/domain/mapper.py` — populate `wall_thickness_mm` from Elmhurst extractor's alt-wall-thickness field
4. `domain/sap10_ml/rdsap_uvalues.py:160 _insulation_bucket` — short-circuit `if not insulation_present: return 0` per docstring intent (audit cohort for any cert with insulation_present=False AND thickness>0)
5. `domain/sap10_ml/rdsap_uvalues.py:329 u_wall` — RdSAP §6.6/§6.7 thin-wall formula keyed on `wall_thickness_mm` for stone constructions
## Open work — prioritised next slices
### S0380.85 — Curtain Wall (highest impact, 112 W/K of 161)
Extract `curtain_wall_age` per BP + add Curtain Wall U-value lookup keyed
on age. Multi-layer (extractor + datatypes + mapper + cascade); a single
slice if the spec lookup is tractable.
Spec citation candidate: RdSAP 10 Table 6 row for "CW Curtain Wall"
across age categories. The worksheet (cert 000565) gives Post-2023 →
U=1.40 as the empirical ground truth.
Expected impact: cert 000565 cascade walls 443 → 555 (worksheet 604).
HTC fabric 795 → 907. SH residual +2591 → +~800 kWh. sap_score should
move 26 → ~28 (still 1-2 short of 29 due to remaining alt1 gap).
### S0380.86 — Thin-wall alt stone granite (47 W/K)
Two coupled bugs in `rdsap_uvalues.py`:
1. `_insulation_bucket` short-circuit on `not insulation_present`
2. Thin-wall §6.6/§6.7 formula keyed on a new `wall_thickness_mm`
field on `SapAlternativeWall`
Plus extractor + datatype + mapper plumbing for the wall_thickness
field. After both gaps close: cascade walls 555 → ~610 (matches
worksheet 604). HTC → ~960. SH should close to ~72 (or smaller —
the original residual pre-S0380.84).
### Deferred (unchanged from post-S0380.80 handover)
- MEV PCDB Table 4f component for pumps_fans +2.5 (blocked on external
data acquisition; PCDF 500755 record needed)
- HP SAP code → main_heating_category=4 mapper extension (couples with
MEV; ship after MEV record acquired)
- 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
```bash
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: **555 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
```
(was 8 + sap_score EXACT at HEAD `27ead127`; sap_score moved into the
fail list at HEAD `49622f55` per "Why the regression is the correct
signal" above).
## Files touched this session
| File | Slices | Change |
|---|---|---|
| `domain/sap10_calculator/rdsap/cert_to_inputs.py` | S0380.81, .82 | Added `RDSAP_10_TABLE_32_PRICES`; switched default; new `_other_use_co2_factor_kg_per_kwh` + `_other_use_primary_factor` helpers; wired into pumps_fans / lighting / electric_shower CO2 + PE fields |
| `domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py` | S0380.81, .82 | 2 new tests + 3 re-pinned scalar assertions to Table 32 |
| `backend/documents_parser/elmhurst_extractor.py` | S0380.83 | Added "Exposed" + "Connected" to `gable_type` recognition set |
| `backend/documents_parser/tests/test_summary_pdf_mapper_chain.py` | S0380.83, .84 | 2 new tests (extractor gable_type + mapper RR routing/areas) |
| `datatypes/epc/domain/mapper.py` | S0380.83, .84 | Mapper RR routing per §3.10 Table 4; drops Connected, routes Exposed external, surfaces Common Walls with §3.9.2 spec area formula |
| `datatypes/epc/domain/epc_property_data.py` | S0380.84 | `SapRoomInRoofSurface.kind` docstring extended with `common_wall` |
| `domain/sap10_calculator/worksheet/heat_transmission.py` | S0380.84 | Added `common_wall` kind handler (walls += area × U) |
## Spec source quick-reference
- **SAP 10.2 full specification**: `domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf`
- Table 12a (p.191) — Grid 1 SH + WH + Grid 2 "All other uses" high-rate fractions
- Table 12d (p.194) — monthly CO2 factors for electricity
- Table 12e (p.195) — monthly PE factors for electricity
- **RdSAP 10 specification**: `domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf`
- §3.9 + §3.10 (p.30-35) — Simplified Type 1/2 + Detailed RR
- Table 4 (p.22) — RR surface variants (gable_wall U-value rules)
- §19.1 (p.80-81) — Table 32 prices for §10a/§10b
- Table 32 (p.95) — RdSAP unit prices + standing charges
- Table 6 — wall U-values by construction + insulation bucket (Curtain Wall entry needed for S0380.85)
- §6.6 / §6.7 — thin-wall stone formula (S0380.86)
- **SAP 10.3 at** `domain/sap10_calculator/docs/specs/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` — S0380.81/.82/.83/.84 entries
+ post-S0380.84 BP main-wall diagnostic table localising the 161
W/K residual to Curtain Wall + thin-wall alt
- `MEMORY.md` — index entry refreshed at HEAD `49622f55`

View file

@ -0,0 +1,190 @@
# Next-agent prompt — post S0380.84
Branch: `feature/per-cert-mapper-validation`.
HEAD: `49622f55`.
Read these in order before any tool call:
1. [`HANDOVER_POST_S0380_84.md`](HANDOVER_POST_S0380_84.md) — full state
2. [`HANDOVER_POST_S0380_80.md`](HANDOVER_POST_S0380_80.md) — predecessor (background; do not re-investigate)
Also load these memories before starting:
- `project_cert_000565_recovery_state` — cert 000565 slice history + per-BP diagnostic
- `feedback_sap_10_2_only_never_10_3`**CRITICAL** — never reference SAP 10.3 spec
- `feedback_spec_citation_in_commits` — quote spec text + page in commit messages
- `feedback_spec_floor_skepticism` — "spec-precision floor" framing usually hides a real spec rule
- `feedback_verify_handover_claims` — verify spec citations + numeric claims before implementing
- `feedback_zero_error_strict` — pyright net-zero per touched file
- `feedback_commit_per_slice` — one slice = one commit
- `feedback_aaa_test_convention` — every new test uses `# Arrange / # Act / # Assert`
- `feedback_no_misleading_insulation_type` — don't lodge `insulation_type` on uninsulated surfaces; "thickness" fields should track what they're named for
- `feedback_e2e_validation_philosophy` — component pins at <1e-3; SAP integer delta=0; no adaptive ceilings
- `feedback_golden_residuals_near_zero` — pin updates on golden certs are protocol when cascade closure surfaces real spec bugs
## State summary
This session shipped **S0380.81/.82/.83/.84** — Table 32 default
prices, Table 12a Grid 2 dual-rate CO2/PE, extractor gable_type
recognition, and the full RR fold-in cascade fix. cert 000565 sap_score
closed 28 → 29 EXACT at S0380.81; CO2 closed 65%; RR cascade structurally
spec-correct (11 per-BP surface areas EXACT vs worksheet at 4 d.p.).
**The S0380.84 RR fix surfaced the next named gap**: cert 000565 BP
main-wall residual 161 W/K, localised in the handover to two
spec-cited cascade gaps:
- **BP[2] Ext2 Curtain Wall: 112 W/K** (`WALL_CURTAIN=9` defined
but no `_ENG_WALL` table entry; `u_wall` falls through to default
Cavity for age H → 0.60, worksheet expects 1.40)
- **BP[0] Main alt1 thin-wall stone granite: 47 W/K**
(`_insulation_bucket(thk=120, ins_present=False)` returns 100 not 0
per docstring intent; plus `wall_insulation_thickness=120` is
mislabelled wall thickness; plus RdSAP §6.6/§6.7 thin-wall formula
for U=2.34 ground truth)
cert 000565 sap_score temporarily moved 29 → 26 because the RR fix
exposed the BP main-wall gap that was previously masking the SH
residual via cancellation. The cascade is more spec-correct now.
| Pin | Δ | Cause | Next slice |
|---|---:|---|---|
| sap_score | 3 | BP main-wall under-count | Closes when S0380.85 + .86 land |
| space_heating_kwh | +2591 | Curtain Wall + thin-wall alt | S0380.85 + .86 |
| main_heating_fuel | +1524 | Follows space_heating via 1/COP | Downstream |
| co2 / cost / ECF / continuous SAP | (large) | Downstream of HTC over-count | Downstream |
| **hot_water_kwh** | **✓ 0 EXACT** | §4 cascade closed | done |
| lighting | +2.19 | Sub-spec | low priority |
| pumps_fans | +2.48 | MEV PCDB missing | blocked on data |
## Recommended next slice — S0380.85 Curtain Wall closure
**This is the highest-leverage single change available** (closes 70% of
the BP main-wall gap; 112 of 161 W/K).
### Audit steps
1. Read RdSAP 10 Table 6 — find the Curtain Wall row. Cert 000565
worksheet pins U=1.40 for "Curtain Wall Post 2023" (cert age H).
Verify the per-age-category Curtain Wall U-values.
2. Read Summary §7 lodging for cert 000565 BP[2]:
```
Type: CW Curtain Wall
Curtain Wall Age: Post 2023
U-value Known: No
```
The "Curtain Wall Age" is a separate per-BP attribute (not the
dwelling `construction_age_band`). Need to extract + plumb through.
3. Probe the extractor's current Wall section parser to find where
to slot the `curtain_wall_age` extraction.
4. Probe `_ENG_WALL` table — find which constructions have age-keyed
lookups (e.g. `WALL_SYSTEM_BUILT`) to mirror for `WALL_CURTAIN`.
### Suggested implementation
1. **Extractor**:
`backend/documents_parser/elmhurst_extractor.py` — parse "Curtain
Wall Age" line from the per-BP Wall block (Summary §7).
2. **datatype**:
- `datatypes/epc/surveys/elmhurst_site_notes.py:WallDetails`
add `curtain_wall_age: Optional[str]` field
- `datatypes/epc/domain/epc_property_data.py:SapBuildingPart`
add `curtain_wall_age: Optional[str]` field
3. **Mapper**:
`datatypes/epc/domain/mapper.py` — thread `curtain_wall_age` through
both API + Elmhurst paths to `SapBuildingPart`.
4. **Cascade**:
`domain/sap10_ml/rdsap_uvalues.py`
- Add `WALL_CURTAIN` to `known_types` (line 373-376) so the code
selects the curtain wall lookup rather than falling through to the
cavity default
- Add `_ENG_WALL[(WALL_CURTAIN, 0)] = [...A-M U-values from Table 6]`
- Update `u_wall` signature to accept `curtain_wall_age` and
dispatch: when `wall_type == WALL_CURTAIN`, key the lookup on
`curtain_wall_age` ("Post 2023" / "Pre 2023" / etc.) instead of
the dwelling age band
5. **Failing test** (AAA): write the cascade-level pin first —
`test_summary_000565_ext2_curtain_wall_routes_to_u_value_1p40_per_rdsap_10_table_6`
asserts `heat_transmission_section_from_cert(epc).walls_w_per_k`
moves toward worksheet.
### Expected outcome
cert 000565 cascade walls 443 → 555 (worksheet 604). HTC fabric
795 → 907. SH residual +2591 → ~+800 kWh. sap_score should move 26
→ ~28 (still 1-2 short of 29 due to remaining alt1 gap).
After S0380.85 lands, S0380.86 (thin-wall alt) is the natural next
slice — closes the remaining 47 W/K + the dry-lining handling for
stone walls.
## Alternative — S0380.86 first (thin-wall alt stone granite)
Smaller in magnitude (47 W/K) and slightly more complex
(involves 3 coupled bugs in `rdsap_uvalues.py` + a datatype shape
change for `SapAlternativeWall.wall_thickness_mm` per
[[feedback-no-misleading-insulation-type]]). Less attractive as a
standalone slice; ship after S0380.85 lands.
## Standard workflow per slice
1. Read SAP 10.2 / RdSAP 10 spec page for the change — quote it in commit
2. Probe current cascade output, identify exact spec-vs-cascade gap
3. Write failing test FIRST (AAA structure)
4. Implement helper / change
5. Verify test passes
6. Run full handover suite (command in handover)
7. Check pyright on touched files — net-zero from baseline
8. Commit with spec citation
9. Update relevant memory if state changed
## How to run the baseline
```bash
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: **555 pass + 9 expected `test_sap_result_pin[000565-*]` fails**.
After S0380.85 lands, expected fails should drop to 7 or 8 (sap_score
likely still failing at Δ1 or Δ2 with the residual thin-wall gap
still open; main fail count reduction comes when S0380.86 also lands).
## What NOT to do
- **Don't re-investigate the RR cascade**. S0380.84 closed the structural
routing per RdSAP §3.9.2 + §3.10 + Table 4; the 11 per-BP RR surface
areas pin EXACT vs worksheet PDF.
- **Don't re-investigate Table 32 prices, Table 12a Grid 2 CO2/PE, or
the §4 HW cascade**. All spec-correct at HEAD `49622f55`.
- **Don't widen pin tolerances or xfail residual gaps**
([[feedback-zero-error-strict]]). The 9 cert 000565 fails are the
work queue.
- **Don't revert S0380.84** — the test-pin "regression" is the
spec-correct cascade exposing the next named gap; reverting puts
the cascade back into compensating-bugs state. Per
[[feedback-spec-citation-in-commits]] + [[feedback-spec-floor-skepticism]]
ship the spec-correct fix and close the surfaced gap.
- **Don't reference SAP 10.3** ([[feedback-sap-10-2-only-never-10-3]]).
- **Don't chase the 12 gas-combi PV certs or the 5 SAP-residual certs
without worksheets** — user has explicitly de-prioritised.
- **Don't apply the S0380.85 Curtain Wall lookup to any non-curtain-wall
construction**. Gate strictly on `wall_construction == WALL_CURTAIN`.
## Memory hygiene
After the next slice, update:
- `project_cert_000565_recovery_state` — final cumulative closure table
(especially if sap_score returns to EXACT after S0380.85+.86 land).
- If you ship the Curtain Wall lookup: consider adding a memory entry
for the spec citation if the Table 6 row is non-obvious to find.
Good luck.