# Handover — post Slices S0380.177..179 (+ infra/CI work) Branch: `feature/per-cert-mapper-validation`. **HEAD `af8e0d94`** (post merge from main). Predecessor: [`HANDOVER_POST_S0380_176.md`](HANDOVER_POST_S0380_176.md). ## TL;DR The 41-variant heating-systems corpus is now **36 EXACT + 5 pinned**. The only remaining residuals are the **5 community-heating (CH) variants** — all `SAP code 302/301/304` heat-network systems. Everything else (oil, electric, solid fuel, ASHP/GSHP, PCDB, "no system") is EXACT on all four metrics (ΔSAP/Δcost/ΔCO2/ΔPE). Three closure slices + four infra changes landed this session: | Slice / change | HEAD | Scope | |---|---|---| | S0380.177 | `5276282d` | **oil 6 boiler interlock from room-thermostat absence.** Control code 2101 ("no thermostatic control of room temperature") ⇒ no room thermostat ⇒ per RdSAP 10 §3 NOT interlocked despite cylinderstat=Yes (P960 "Boiler Interlock: No") ⇒ SAP 10.2 Table 4c(2) −5pp Space+DHW. New `_BOILER_NO_ROOM_THERMOSTAT_CONTROL_CODES={2101,2102}`; `no_interlock` ORs room-thermostat absence with stored-HW cylinderstat absence; Space −5pp leg now fires for Table 4b non-PCDB boilers. | | S0380.178 | `c054d712` | **oil 6 circulation pump ×1.3 for absent room thermostat.** SAP 10.2 Table 4f footnote a) (PDF p.175) "Multiply by 1.3 if room thermostat is absent" ⇒ 41 × 1.3 = 53.3 kWh = ws (230c). Closes oil 6 FULLY (same root cause as .177). | | S0380.179 | `f2062a2f` | **RdSAP 10 §10.7 electric-immersion default for "no system".** Cert lodges water code 999 (NON) + "cylinder present: No", but §10.7 substitutes an electric immersion on a Table 28 row-1 110 L cylinder + Table 29 row-1 insulation. New `_apply_rdsap_no_water_heating_system_default(epc)` rebinds the epc at the top of `cert_to_inputs` when `water_heating_code==999`. One fix closed HW (−594 kWh storage loss) AND the downstream space residual (+228, a HW-gains→MIT artifact). Closes "no system" FULLY. | | appliances+cooking | `2f039aeb` | Threaded `appliances_kwh_per_yr` + `cooking_kwh_per_yr` (Appendix L L13/L14/L16a + L20) onto `SapResult`/`CalculatorInputs` for ADR-0014 BillDerivation. **Output-only, zero rating drift.** | | test fixes | `0e484aaa` | Fixed 11 pre-existing CI failures from an absorbed PR: `test_appendix_u.py` signature drift + mislabelled "SAP 10.3"→10.2; `test_table_32.py` re-pinned oil(4)=5.44 / FAME(73)=7.64 to the worksheet-canonical values the table actually uses. | | corpus PDFs | `d1c87d84` | Committed the 82 heating-corpus PDF fixtures (`sap worksheets/heating systems examples/`) so CI can run the residual pins. | | **test move** | `d7d5084f` | **Moved all 5 calculator test dirs → `tests/domain/sap10_calculator/`** so CI (which collects `tests/`) runs them. SEE "Test layout changed" below — it changes every command. | ## ⚠ Test layout changed this session — commands are different now The calculator tests **moved** out of `domain/sap10_calculator/.../tests` into `tests/domain/sap10_calculator/{,worksheet,rdsap,climate,validation}`. Cross-imports were rewritten `domain.sap10_calculator.worksheet.tests` → `tests.domain.sap10_calculator.worksheet`. Any old handover command that references `domain/sap10_calculator/worksheet/tests/...` is STALE. **New full verification command** (replaces the old extended suite): ```bash PYTHONPATH=/workspaces/model python -m pytest \ tests/domain/sap10_calculator/ \ backend/documents_parser/tests/ \ --no-cov -q -p no:cacheprovider ``` Expected at HEAD: **~2221 pass, 1 skipped, 0 fail** (the 1 skip is the corpus blocked-variant `skipif`). The cascade-pin / golden / e2e conformance suites are all under `tests/domain/sap10_calculator/`. **Two gotchas:** 1. `load_cells` tests (`tests/domain/sap10_calculator/worksheet/test_{dimensions,ventilation,water_heating}.py`) pin against the gitignored `2026-05-19-17-18 RdSap10Worksheet.xlsx` at repo root. `_xlsx_loader.load_cells` `pytest.skip()`s when the xlsx is absent — so they run locally and skip in CI. If you're missing the xlsx locally, those skip (not fail). 2. **Uncommitted `pytest.ini` change** (came in with a main pull) REMOVES `tests/` + `domain/sap10_ml/tests` from `testpaths`. HEAD has them; the working tree strips them. This is NOT a slice change — confirm with the user before committing it, because removing `tests/` would un-collect the moved calculator tests. ## Current residual state at HEAD `af8e0d94` ### 36 variants EXACT (all four metrics < tolerance) ``` ashp, gshp, electric 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, oil 1, oil 2, oil 3, oil 4, oil 5, oil 6, oil pcdb 1, oil pcdb 2, oil pcdb 3, pcdb 1, pcdb 3, solid fuel 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, no system ``` ### 5 community-heating variants pinned | Variant | SAP code | ΔSAP_c | Δcost | ΔCO2 | ΔPE | Closure driver | |---|---:|---:|---:|---:|---:|---| | CH6 (CHP/Coal) | 302 | −7.4942 | +£172.68 | −2939.67 | +7481.57 | SAP 302 CHP credit + DLF=1.0 P960 quirk | | CH2 (CHP/Gas) | 302 | +0.5277 | −£12.16 | −1435.09 | +1123.01 | SAP 302 CHP credit (CO2 + PE) | | CH4 (CHP/Oil) | 302 | +0.5277 | −£12.16 | −4401.85 | +111.58 | SAP 302 CHP credit (CO2) | | CH3 (HP/Elec) | 304 | +0.0000 | −£0.00 | −98.92 | −457.54 | (372) electrical-distribution + HP COP | | CH1 (Boilers/Gas) | 301 | +0.0000 | −£0.00 | −23.60 | −208.23 | (372) electrical-distribution factor | Blocked tier: **empty**. ## Open fronts ranked by leverage ### 1. SAP 302 CHP CO2/PE credit cascade (3 variants — CH2/CH4/CH6) — HIGHEST Closes the big CO2/PE residuals on CH2/CH4 AND the −7.49 SAP on CH6 simultaneously. Spec: block 13b PE (PDF p.153) + 12b CO2 — the displaced-electricity CHP credit lines (worksheet (363)-(366), (464)/(466)/(468)): ``` Space heating from CHP (307a) × 100 ÷ (362) = ... (363) less credit emissions −(307a)×(361) ÷ (362) = ... (364) Water heated by CHP (310a) × 100 ÷ (362) = ... (365) less credit emissions −(310a)×(361) ÷ (362) = ... (366) Heat from heat source 2 [(307b)+(310b)] × 100 ÷ (467b) (468) ``` RdSAP 10 §C defaults (verified vs CH2/CH4/CH6 worksheet (461)/(462)): CHP overall eff 75%, heat-to-power 2.0 → heat_eff 50% / electric_eff 25%; boiler eff 80%. The `.172` scaling helper already keys on `_HEAT_NETWORK_HEAT_SOURCE_EFFICIENCY` — add code 302 there once the split formula is in place; the `.173` predicate `_is_community_heating_hw_from_main` auto-activates. **⚠ UNRESOLVED per-line caveat — walk before hypothesising.** The Elmhurst worksheet (463) energy column = `spec_formula × 0.8523` uniformly across non-CHP heat-network rows (the 0.8523 also shows in CH1 (467)). It is NOT RdSAP 10 / SAP 10.2 spec-derived. Per [[feedback-spec-floor-skepticism]] / [[feedback-software-no-special-handling]], DUMP the worksheet per-line and reconcile 0.8523 before baking any CHP formula into the cascade. Likely 2-3 slices. ### 2. CH1/CH3 (372)/(472) electrical-distribution CO2/PE — DEFERRED CH1/CH3 are SAP + cost EXACT; only CO2/PE remain. Worksheet (372) CO2 factor = 0.1994 (block 11a) / 0.2114 (block 11b); PE = 1.7591 / 2.1872. These don't match ANY Table 12 / 12d / 12e weighting derivable from the (307) or (307)+(310) heating-demand monthly profile. (313) annual = 0.01 × (307) ONLY (verified across 5 variants, NOT 0.01 × (307+310) as the spec text says). **Don't guess** — reverse-engineer the 0.1994 factor from a wider variant set or find BRE documentation first. ### 3. CH6 DLF=1.0 P960 quirk — architectural, likely pin-forever P960 input lodges `Distribution Loss: Two adjoining dwellings...` + `Distribution Loss Value: 0.0` → ws (306) = 1.0000, but the Summary doesn't carry anything distinguishing CH6 from CH4. Per §C3.1 the manual-DLF override is legal but not surfaced by the Summary. Recommendation: pin + document once the CHP credit lands. ## Discipline (carried from every prior handover) - **Per-line walk worksheet → spec → fix.** All 3 slices this session landed via per-line P960 dumps. Don't form a spec hypothesis without per-line data (the 0.8523 + 0.1994 factors are the live examples). - **Spec-floor skepticism cuts BOTH ways** — a spec-correct fix often EXPOSES the next residual (oil 6 .177→.178; "no system" HW→space). Apply the spec uniformly; the surfaced residual is the next target. - **SAP 10.2 ONLY, never 10.3.** - **Don't conflate `main_heating_category` and `sap_main_heating_code`** — the Elmhurst mapper leaves `category=None` on Table 4b liquid-fuel boilers; cascade gates must check both. - **Target is < 1e-4 vs worksheet** — ΔSAP=0.07 is NOT closed. Re-pin smaller; never widen tolerance, never xfail. - **One slice = one commit**, spec citation in the message, trailer `Co-Authored-By: Claude Opus 4.8 `. ## Memories to load (in order) ``` project-heating-systems-corpus # HEAD af8e0d94, 36 EXACT + 5 pinned feedback-sap-10-2-only-never-10-3 feedback-software-no-special-handling feedback-spec-floor-skepticism feedback-worksheet-not-api-reference feedback-spec-citation-in-commits feedback-verify-handover-claims feedback-zero-error-strict feedback-commit-per-slice feedback-aaa-test-convention feedback-e2e-validation-philosophy feedback-abs-diff-over-pytest-approx feedback-one-e-minus-4-across-the-board reference-unmapped-sap-code reference-unmapped-api-code project-oil-price-spec-divergence ``` ## Master doc Architecture + API + validation: [`SAP_CALCULATOR.md`](SAP_CALCULATOR.md) (§8 "Elmhurst-mirrored spec divergences" carries .163 HW dual-rate annual + .164 §12.4.4 summer-immersion). If the CHP 0.8523 multiplier resolves to an Elmhurst-vs-spec divergence, add §8.3. ## Good luck.