diff --git a/domain/sap10_calculator/docs/HANDOVER_POST_S0380_176.md b/domain/sap10_calculator/docs/HANDOVER_POST_S0380_176.md new file mode 100644 index 00000000..b0ef38c4 --- /dev/null +++ b/domain/sap10_calculator/docs/HANDOVER_POST_S0380_176.md @@ -0,0 +1,277 @@ +# Handover — post Slices S0380.174..176 + +Branch: `feature/per-cert-mapper-validation`. **HEAD `326066ee`**. +Predecessor: [`HANDOVER_POST_S0380_173.md`](HANDOVER_POST_S0380_173.md). + +## TL;DR + +Three slices landed on the heating-systems corpus: +- **`.174`** closed §4 (62)m HW for all 5 community-heating variants; + CH1 HW EXACT. +- **`.175`** wired §14.1 Community Heating "Heating Controls SAP" into + the mapper; CH1 + CH3 SAP / cost EXACT. +- **`.176`** added Table 4b combi sub-row fall-through to the (61)m + default gate; **oil 3 + oil 4 FULLY EXACT on all four metrics**. + +**41 variants total → 34 EXACT + 7 pinned** (was 36 + 5 pre-`.174`). +Pinned count drops from 9 (in `.173` handover) to 7 because oil 3 + +oil 4 fully closed; CH1 + CH3 reshape from "SAP/cost-pinned" to +"SAP/cost EXACT + CO2/PE pinned" pending the (372) electrical- +distribution Elmhurst factor mystery. + +| Slice | HEAD | Scope | +|---|---|---| +| S0380.174 | `4876140a` | **§4 storage + primary loss for community heating.** SAP 10.2 §4 line 1482 "Heat networks": primary circuit loss for insulated pipework + cylinderstat applies. Table 2b note b verbatim system-type list ("boiler / warm air / heat pump") OMITS community heating — ×0.9 multiplier doesn't apply. Three changes: new `_HEAT_NETWORK_PIPEWORK_INSULATION_FRACTION = 1.0`, new branch in `_primary_loss_applies` (heat-network main + WHC ∈ {901, 902, 914} → True), new `_table_2b_note_b_multiplier_applies` predicate gating ×0.9 by system type. CH1 HW useful (62)m closes 2339.24 → 2658.01 EXACT vs ws; HW fuel kWh closes 3391.90 → 3854.12 EXACT; (65)m heat gains 793.51 → 1221.62 EXACT. Cost/SAP signs flipped — exposed pre-existing §7 MIT +0.46 K over-count. | +| S0380.175 | `eda07d12` | **§14.1 Community Heating heating_controls_sap extraction.** All 5 CH variants lodge "Heating Controls SAP: 2306" in §14.1 (bare 4-digit form), not in §14.0 "Main Heating Controls Sap" (which is empty for CH certs). Pre-slice mapper read only §14.0 → `main_heating_control=''` → cascade defaulted to `control_type=2` (off-hours (7, 8)). Code 2306 (Table 4e Group 3) → control_type=3 (off-hours (9, 8)), which closes the +0.46 K MIT (92)m residual that `.174` surfaced. Two changes: `_elmhurst_sap_control_code` accepts bare integer form, `_map_elmhurst_sap_heating` falls through to `mh.community_heating.heating_controls_sap` when §14.0 is empty. CH1 + CH3 ΔSAP_c -1.0572 → +0.0000 EXACT; Δcost +£24.36 → -£0.00 EXACT. | +| S0380.176 | `326066ee` | **Table 4b combi sub-row dispatch for (61)m default.** SAP 10.2 §4 line 7702 + Table 4b row names: codes 128/129/130 are explicit combi sub-rows ("Combi oil boiler, ..."). Pre-slice `_table_3a_combi_loss_default_applies` gated only on `main_heating_category ∈ {1, 2, 3, 6}`; Elmhurst mapper leaves the category None on Table 4b liquid-fuel boilers so the cascade fell through to (61)m=0. Added `_TABLE_4B_COMBI_OR_CPSU_CODES` fall-through (set already exists in symmetric `_primary_loss_applies` Table 4b branch — see `.146`). **oil 3 + oil 4 ALL FOUR METRICS EXACT** (ΔSAP +2.5863/+2.5603 → ±0.0000, Δcost -£62/-£57 → ±0.00, ΔPE -967/-885 → ±0.00). Cohort 9 → 7 pinned. | + +Extended handover suite at HEAD: **933 pass + 1 skipped, 0 fail.** +Pyright net-zero on all affected files. + +## Current residual state at HEAD `326066ee` + +### Cascade-OK tier (41 variants — all populated corpus folders) + +**34 variants EXACT** on all four metrics (|ΔSAP| < 1e-3, |Δcost| < +£0.01, |ΔCO2| < 0.1 kg, |ΔPE| < 0.1 kWh): + +``` +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 pcdb 1, oil pcdb 2, oil pcdb 3, +pcdb 1, pcdb 3, +solid fuel 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 +``` + +**7 variants pinned**: + +| Variant | SAP code | ΔSAP_c | Δcost | ΔCO2 | ΔPE | Closure driver | +|---|---:|---:|---:|---:|---:|---| +| CH6 (CHP/Coal) | 302 | −7.49 | +£172.68 | −2939.67 | +7481.57 | DLF=1.0 P960 quirk + CHP credit | +| oil 6 (B30K) | 126 | +3.05 | −£69.79 | −240.66 | −1112.66 | -5pp interlock penalty on non-combi | +| no system | 699 | +1.18 | −£27.15 | −49.83 | −562.44 | §A.2.2 portable-electric defaults | +| CH4 (CHP/Oil) | 302 | +0.53 | −£12.16 | −4401.85 | +111.58 | SAP 302 CHP credit (CO2) | +| CH2 (CHP/Gas) | 302 | +0.53 | −£12.16 | −1435.09 | +1123.01 | SAP 302 CHP credit (CO2 + PE) | +| CH3 (HP/Elec) | 304 | +0.0000 | −£0.00 | −98.92 | −457.54 | (372) electrical-distribution CO2/PE + (367) HP scaling | +| CH1 (Boilers/Gas) | 301 | +0.0000 | −£0.00 | −23.60 | −208.23 | (372) electrical-distribution CO2/PE Elmhurst factor | + +### Blocked tier (0 variants) + +**Empty** (since `.170`). `_BLOCKED_BY_MISSING_MAIN_FUEL_TYPE` tuple +in `test_heating_systems_corpus.py` remains empty; the parametrised +raise-test is `pytest.mark.skipif`'d. + +## Open fronts ranked by leverage + +### 1. SAP 302 CHP CO2/PE credit cascade (3 variants — CH2, CH4, CH6) + +Highest cohort leverage: closes ~−8 SAP-equivalent across CH6 + the +big CO2 / PE residuals on CH2 / CH4 simultaneously. Spec block 13b +PE (PDF p.153) + 12b CO2: + +``` +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: CHP overall eff 75%, heat-to-power ratio 2.0 → +heat_eff 50% + electric_eff 25%; boiler eff 80%. Verified against +CH2/CH4/CH6 worksheet (461)/(462) = 25% / 50% exactly. + +**Per-line walk caveat (unresolved).** The Elmhurst worksheet (463) +energy column = spec_formula × 0.8523 uniformly across non-CHP +heat-network rows. The 0.8523 multiplier appears in CH1 (467) too. +Mechanism unidentified; not RdSAP 10 / SAP 10.2 spec-derived. Walk +the worksheet per-line before forming hypotheses. + +Likely 2-3 slices: (a) CHP credit + boiler-side eff for SH; (b) +mirror for HW path; (c) the 0.8523 multiplier if load-bearing. The +`.172` scaling helper already keys on +`_HEAT_NETWORK_HEAT_SOURCE_EFFICIENCY` — add 302 there with weighted +overall eff once the split formula is in place. The `.173` predicate +`_is_community_heating_hw_from_main` also gates on table membership +and will pick up SAP 302 automatically. + +### 2. oil 6 (B30K) −5pp interlock cascade (1 variant, ΔSAP +3.05) + +Per-line walk in this session: cascade Eq D1 outputs (winter, summer) = +(80, 68) → annual eff ~73% at Jan; ws (217)m Jan = 73.07. Cascade gives +HW kWh 3823.38 vs ws 4099.59. Back-solving the worksheet: applying -5pp +interlock penalty to BOTH winter and summer ((75, 63)) reproduces ws +(217)m Jan = 73.07 EXACTLY. + +Cascade `eq_d1_interlock_penalty_pp` is gated on `no_interlock` = +"cylinder thermostat absent". For oil 6 the cert lodges +`cylinder_thermostat = 'Y'` so cascade sets penalty=0. But the +worksheet applies -5pp anyway — likely a different gate for non-PCDB +Table 4b regular boilers vs PCDB Table 105 boilers. + +Probable 1-slice fix: extend the interlock-penalty gate to fire for +Table 4b non-combi boiler codes (124-127) when... [need spec citation +on the exact rule — investigate SAP 10.2 §9.4.11 interlock conditions +for Elmhurst's interpretation]. ΔSAP_c +3.05 → ±0.0000 expected; +closes 4 metrics on a single variant. + +### 3. "no system" §A.2.2 portable-electric defaults (1 variant, ΔSAP +1.18) + +Carried over from `.169`. Cascade thinks dwelling more efficient than +worksheet. Probable §A.2.2 portable-electric defaults gap +(responsiveness / control / Table 11). Probably 1 slice. + +### 4. CH1 / CH3 (372)/(472) electrical-distribution CO2/PE (deferred) + +Worksheet (372) CO2 factor = 0.1994 (block 11a, rating cascade) and +0.2114 (block 11b, demand cascade). PE factor = 1.7591 / 2.1872. +These don't match any Table 12 / 12d / 12e weighting I could derive +from the SH (307) or (307)+(310) heating-demand monthly profile. + +(313) annual = 0.01 × (307) only — confirmed across all 5 CH variants +(NOT 0.01 × ((307)+(310)) as spec text says). Once a factor source is +identified, cascade should add an electricity-for-heat-distribution +contribution to CO2/PE for heat-network mains. + +Deferred this session. Either reverse-engineer the Elmhurst formula +from a wider set of variants or find BRE documentation on the (372) +factor convention. + +### 5. CH6 DLF=1.0 lodging in P960 (architectural — pinned forever) + +P960 input lodges `Distribution Loss: Two adjoining dwellings sharing +a single heating system` + `Distribution Loss Value: 0.0` → ws (306) += 1.0000. Summary lodges nothing distinguishing CH6 from CH4. Per +spec §C3.1 the manual-DLF override is legal but the Summary doesn't +carry it. Two paths: (a) extend extractor to surface §17 Additional +Information when Elmhurst eventually lodges it; (b) accept as pinned. +Recommendation: **(b) — pin and document**. + +## Critical discipline reinforced this session + +**Per-line walk worksheet → spec → fix.** All three slices landed via +per-line worksheet dumps confirming the spec rule before +implementation: +- `.174` probed ws (56)/(57)/(59) and back-solved p=1.0 + TF=0.6 from + the spec literal. +- `.175` traced the cascade's MIT divergence to control_type=2 vs + expected 3, then back-solved from worksheet (89) util_rest = 0.9898 + + (90) T_rest = 16.11 confirming off-hours (9, 8). +- `.176` ran the cascade `_apply_water_efficiency` trace to find + `annual=1935.37` (= (45)) being passed when ws uses (62) = 2535.37 + (= (45) + (46) + (61)) — exposing the missing combi_loss. + +**Spec-floor skepticism cuts BOTH ways.** `.174`'s spec-correct fix +EXPOSED the §7 MIT bug that pre-slice offsetting bugs had masked. The +chain `.174` → `.175` followed [[feedback-software-no-special-handling]]: +apply spec-correct fix uniformly; the surfaced residual is the next +slice's target, not a regression. + +**Pin diagnoses before forming hypotheses.** The (372) Elmhurst factor +0.1994 doesn't match any Table 12 derivation. Rather than guess, +session pivoted to the next-tractable front (oil 3/4 combi loss) which +closed cleanly. The (372) deferred entry documents what's known and +what's tried. + +**Don't conflate `main_heating_category` and `sap_main_heating_code`.** +Two `.176` slices ago, similar Elmhurst mapper artifact: the FAME oil +boilers lodge `sap_main_heating_code=128/129` but the mapper leaves +`main_heating_category=None`. Cascade dispatch helpers that gate on +either field must check BOTH. The `_TABLE_4B_COMBI_OR_CPSU_CODES` set +already existed for the symmetric `_primary_loss_applies` branch +(per `.146`); adding the same fall-through to +`_table_3a_combi_loss_default_applies` was a 4-line change with +exact closure on 8 metrics (oil 3 + oil 4 × SAP/cost/CO2/PE). + +## Standard slice workflow (unchanged) + +1. Read spec page + identify rule (or Elmhurst worksheet pattern) +2. Probe one variant; verify diagnosis via monkey-patch / direct walk +3. Write failing AAA test (literal `# Arrange / # Act / # Assert`) +4. Implement helper / dispatch entry / mapper extension +5. Re-pin affected variants (DO NOT widen tolerance) +6. Run extended handover suite (command below) +7. Pyright net-zero check (`git stash` → pyright → `git stash pop` → pyright) +8. If mirroring Elmhurst against spec literal: add a row to + `SAP_CALCULATOR.md §8 "Elmhurst-mirrored spec divergences"`. The + ≥2-cert rule applies unless the new divergence shares its shape + with an already-documented row. +9. Commit with spec citation + `Co-Authored-By: Claude Opus 4.7 ` +10. Update `project-heating-systems-corpus` + `MEMORY.md` index + +## Test baseline at HEAD `326066ee` + +```bash +PYTHONPATH=/workspaces/model python -m pytest \ + backend/documents_parser/tests/test_summary_pdf_mapper_chain.py \ + backend/documents_parser/tests/test_heating_systems_corpus.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_heat_transmission.py \ + domain/sap10_calculator/worksheet/tests/test_internal_gains.py \ + domain/sap10_calculator/worksheet/tests/test_solar_gains.py \ + domain/sap10_calculator/worksheet/tests/test_dimensions.py \ + domain/sap10_calculator/worksheet/tests/test_rating.py \ + domain/sap10_calculator/worksheet/tests/test_ventilation.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 \ + domain/sap10_calculator/tests/test_table_12a.py \ + --no-cov -q +``` + +Expected: **933 pass + 1 skipped, 0 fail.** + +## Memories to load (in order) + +``` +project-heating-systems-corpus # HEAD 326066ee +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-golden-residuals-near-zero +feedback-one-e-minus-4-across-the-board +feedback-bigger-slices-for-uniform-work +reference-unmapped-sap-code +reference-unmapped-api-code +project-oil-price-spec-divergence +``` + +## What NOT to do + +- **Don't reference SAP 10.3** — track 10.2 deliberately. +- **Don't widen pin tolerances** — re-pin smaller or find the spec gap. +- **Don't add empirical gates** to keep cohort pins stable. +- **Don't re-investigate Slices .91..176** — all settled. +- **Don't add new helpers to `domain/sap10_ml/`** — on deprecation path. +- **Don't treat ΔSAP=0.07 as "closed"** — target is <1e-4 vs worksheet. +- **Don't form a spec hypothesis without per-line data.** The (372) + Elmhurst factor 0.1994 is unexplained; don't bake guesses into the + cascade. Reverse-engineer with more variants first, or find BRE + documentation. +- **Don't conflate `main_heating_category` and `sap_main_heating_code` + in cascade gates.** The Elmhurst mapper leaves `category=None` on + Table 4b liquid-fuel boilers; gates must check both fields. + +## Master doc + +The canonical architecture + API + validation doc lives at +[`domain/sap10_calculator/docs/SAP_CALCULATOR.md`](SAP_CALCULATOR.md) +(§8.1 + §8.2 documented). If the (372) Elmhurst-factor mystery +resolves and the formula turns out to be an Elmhurst-vs-spec +divergence, add §8.3. + +## Good luck.