Model/domain/sap10_calculator/docs/HANDOVER_POST_S0380_176.md
Khalim Conn-Kowlessar 168b1941ea docs: handover post S0380.174..176
Three slices landed: §4 storage+primary loss for community heating
(.174), §14.1 heating_controls_sap extraction (.175), Table 4b combi
sub-row dispatch (.176). Cohort moves from 36 EXACT + 5 pinned to 34
EXACT + 7 pinned — net 2 new full-EXACT closures (oil 3 + oil 4) +
2 reshape (CH1/CH3 SAP/cost EXACT, CO2/PE pinned on the (372)
electrical-distribution Elmhurst-factor mystery).

933 pass + 0 fail at HEAD 326066ee. Pyright net-zero.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 13:42:42 +00:00

15 KiB
Raw Blame History

Handover — post Slices S0380.174..176

Branch: feature/per-cert-mapper-validation. HEAD 326066ee. Predecessor: 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 <noreply@anthropic.com>
  10. Update project-heating-systems-corpus + MEMORY.md index

Test baseline at HEAD 326066ee

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 (§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.