docs: handover + next-agent prompt post S0380.96..103 (RIR Unknown + §9 floor + MEV PCDB arc + HP-on-E7 cost split)

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>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-30 16:13:33 +00:00
parent e3abe9b2b5
commit 7df3fef8bb
2 changed files with 540 additions and 0 deletions

View file

@ -0,0 +1,303 @@
# 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`](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:
1. **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.
2. **Internal gains** — pumps_fans gains contribution changes with
HP cat=4 path; verify against ws line (70) by month.
3. **Solar gains / utilization factor** — sub-spec window U-values
leak into solar gains too.
4. **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
```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/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 analysis
- `MEMORY.md` — index entry refreshed at HEAD `e3abe9b2`
## 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
under `domain/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 table
- `MEMORY.md` — refresh HEAD + one-line summary
Good luck.

View file

@ -0,0 +1,237 @@
# Next-agent prompt — post S0380.96..103
Branch: `feature/per-cert-mapper-validation`.
HEAD: `e3abe9b2`.
Read these in order before any tool call:
1. [`HANDOVER_POST_S0380_103.md`](HANDOVER_POST_S0380_103.md) — full state
2. [`HANDOVER_POST_S0380_95.md`](HANDOVER_POST_S0380_95.md) — predecessor (background)
Also load these memories before starting:
- `project_cert_000565_recovery_state` — slice history + per-pin state
- `reference_unmapped_sap_code` — calculator strict-raise pattern
- `project_sap10_ml_deprecation``domain/sap10_ml/` is on the
deprecation path; new cascade helpers should land under
`domain/sap10_calculator/`
- `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_verify_handover_claims` — verify spec citations + numeric
claims before implementing the prescribed fix
- `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 literal
`# Arrange / # Act / # Assert` headers
- `feedback_e2e_validation_philosophy` — component pins at <1e-3;
SAP integer delta=0; no adaptive ceilings
- `feedback_abs_diff_over_pytest_approx` — use `abs(x - y) <= tol`
instead of `pytest.approx` to keep pyright net-zero
- `feedback_spec_floor_skepticism` — skeptical of "spec-precision
floor" claims; verify the spec citation against the PDF first
## Critical user direction
The user's **primary metric is `sap_score_continuous`** (not just
integer `sap_score`). However the user has explicitly stated:
> "It's okay if we temp drift away from continuous SAP, as long as we
> are actually fixing true problems with the intermediate values.
> Eventually, I expect the error of continuous SAP to be zero but
> that is only possible if we fix all of the sub components and
> remain true to spec."
**Implication:** ship spec-correct slices even when they cause
transient continuous-SAP drift. Closing real intermediate-value bugs
is the path to zero error.
## State summary
This session shipped **S0380.96..103** — eight spec-cited slices.
The first two (.96, .97) closed remaining cert 000565 extractor /
mapper gaps; .98..102 built the entire MEV PCDB decentralised
cascade arc; .103 closed the Table 12a Grid 2 MEV-fan cost split.
1. **S0380.96** (`32a4cf20`) — RIR "Unknown" insulation → Table 18
col 4 default (RdSAP 10 §3.10.1). BP[4] FC1 U: 2.30→**0.15 ✓**.
2. **S0380.97** (`7121a86b`) — Floor §9 "Insulation Thickness"
extractor (RdSAP 10 §5.13 Table 20). BP[2] floor U:
0.51→**0.22 ✓ EXACT**. **sap_score 28→29 ✓ EXACT**. Continuous
SAP Δ -0.0001 (within 1e-4 strict floor).
3. **S0380.98** (`b3330821`) — PCDB Table 322 (Decentralised MEV)
ETL + parser + lookup foundation (PCDF Spec §A.19).
4. **S0380.99** (`433f4a49`) — PCDB Table 329 (MV In-Use Factors)
ETL + parser + lookup foundation (PCDF Spec §A.20).
5. **S0380.100** (`44fb8c07`) — SFPav + Table 4f (230a) cascade
helpers in `worksheet/mev.py` (SAP 10.2 §2.6.4).
6. **S0380.101** (`1b183f9c`) — HP SAP code 211-227 / 521-527 →
`main_heating_category=4` (SAP 10.2 Table 4a).
7. **S0380.102** (`a0413155`) — Wire MEV cascade into
`_table_4f_additive_components`. **pumps_fans_kwh_per_yr ✓
EXACT** (was +2.48 over). Schema + extractor + mapper for MV
PCDF index / wet rooms / duct type.
8. **S0380.103** (`e3abe9b2`) — MEV-fan cost split via Table 12a
Grid 2 `FANS_FOR_MECH_VENT` rate. cost residual Δ +£0.39 →
-£1.62 (sign flipped; SH cascade residual now exposed).
**Cert 000565 state at HEAD `e3abe9b2`:**
| Pin | Cascade | Worksheet | Δ |
|---|---:|---:|---:|
| **sap_score (int)** | **29** | 29 | **✓ EXACT** |
| sap_score_continuous | 28.5269 | 28.5087 | +0.0182 |
| ecf | 5.3850 | 5.3866 | -0.0016 |
| total_fuel_cost_gbp | 4678.6372 | 4680.2593 | -1.6221 |
| co2_kg_per_yr | 6445.8198 | 6447.6263 | -1.8065 |
| space_heating_kwh_per_yr | 58980.8225 | 59008.3499 | -27.5274 |
| main_heating_fuel_kwh_per_yr | 34694.6015 | 34710.7941 | -16.1926 |
| **pumps_fans_kwh_per_yr** | **252.5159** | 252.5159 | **✓ 0 EXACT** |
| **hot_water_kwh_per_yr** | 3755.0288 | 3755.0288 | **✓ 0 EXACT** |
| lighting_kwh_per_yr | 1387.0237 | 1384.8353 | +2.1884 |
## Recommended next slice — S0380.104 § Investigate §3-§8 SH cascade -27 kWh
**The current biggest residual driver.** Cert 000565 cascade
space_heating_kwh = 58980.82 vs ws 59008.35 → Δ -27.53 kWh under.
This propagates downstream to main_heating_fuel (-16.19 kWh under)
and total_fuel_cost (-£1.62 under). It is the dominant cause of
continuous-SAP residual +0.0182 OVER ws.
### Why it's now exposed
S0380.103 closed the +£2.01 MEV-cost over-count (Table 12a Grid 2
split). Pre-slice that over-count nearly cancelled the SH under-
count → cost looked +£0.39 over. Post-slice the SH under-count
shows through to cost / co2 / continuous SAP.
The SH cascade IS correct on the cohort fixtures (000474..000516 at
1e-4) so this is **cert-000565-specific**. The differentiators are:
- 5 building parts (Main + 4 extensions)
- Heat pump + gas combi WHC 914
- Detailed-RR with residual area (S0380.95 closure)
- MEV decentralised
- FGHRS, solar HW, draught lobby, basement walls (Ext3/Ext4),
Curtain Wall Post-2023 (Ext2), CF + CU party walls
### Investigation approach
1. **Probe per-month `space_heat_requirement_kwh`** vs ws line
(98c)m to identify which month(s) carry the residual:
```python
from domain.sap10_calculator.worksheet.tests._elmhurst_worksheet_000565 import build_epc
from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs
epc = build_epc()
inputs = cert_to_inputs(epc)
print("monthly SH:", inputs.space_heating_monthly_kwh)
# Compare to ws (98c)m line refs from U985-0001-000565.pdf
```
2. **Check the fabric subtotals** — net cascade HTC is +29.45 W/K
over ws. Closing roof BP[1] residual (+1.29 W/K, deferred) +
thermal_bridging (+0.70) brings it to +27.5. Walls are -2.85
under and windows/roof_windows offset.
Big +29 W/K HTC should DRIVE space_heating UP, but cascade SH is
-27 kWh UNDER. That means the cascade is OVER-counting heat
GAINS somewhere, or UNDER-counting demand by an offsetting factor.
3. **Check internal gains** — pumps_fans gains (line 70) changed
between cohort certs and cert 000565 (HP cat=4 → 0W heating-
season pump). Verify against ws line (70)m by month.
4. **Check solar gains** (line 74-83) — sub-spec window U could
propagate to gain magnitude.
5. **Check utilisation factor / mean-internal-temp solve** — multi-
BP cert with mixed age bands might hit a corner case.
Expected closure: continuous SAP +0.0182 → within 1e-4.
## Alternative next slice — S0380.105 § CO2 cascade MEV split
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.
The cascade applies a single Table 12d profile across all
pumps_fans. The worksheet integrates MEV (year-round) separately
from heating-season pumps + flue fans.
**Slice scope:** add an MEV-weighted CO2 factor analogous to
`_pumps_fans_fuel_cost_gbp_per_kwh`. Add field
`CalculatorInputs.pumps_fans_co2_factor_kg_per_kwh` resolution that
weights two streams.
Impact: -0.31 kg/yr → continuous SAP downstream marginal change.
This is the **lower-leverage** of the two open options. S0380.104
SH investigation is higher leverage.
## 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 below)
7. Check pyright on touched files — net-zero from baseline
(use `git stash` + re-run pyright to compute baseline)
8. Commit with spec citation + verbatim quote
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/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**.
After S0380.104 lands the expected fail count should drop by 5-7
(sap_score_continuous, ecf, total_fuel_cost_gbp, co2_kg_per_yr,
space_heating_kwh_per_yr, main_heating_fuel_kwh_per_yr) if the SH
cascade closes. Lighting (+2.19 kWh) is unrelated and survives as
its own slice.
## 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
under `domain/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 closure + open work-
items refresh
- `MEMORY.md` index — refresh HEAD + one-line summary
Good luck.