Model/domain/sap10_calculator/docs/HANDOVER_POST_S0380_90.md
Khalim Conn-Kowlessar edb1e6b892 docs: handover + next-agent prompt post S0380.85..90 (BP main-wall closure + SH-channel discovery + strict-raise series)
Session summary documents covering 6 slices:

- S0380.85 — Curtain Wall §5.18 dispatch (cascade walls 443 → 555.93 W/K)
- S0380.86 — §5.6 thin-wall stone + §5.8 dry-line (555.93 → 602.40,
              worksheet 604.07, 0.27% residual)
- S0380.87 — Table 4e GROUP 2 HP control codes — single-line spec
              dispatch bug; SH residual +7924 → +1460 (82%), sap_score
              23 → 27, largest single-slice movement of session
- S0380.88 — Full Table 4e Groups 0-7 + UnmappedSapCode strict-raise
- S0380.89 — Table 4d responsiveness + screed-UFH bug fix + strict raise
              (latent bug found via strict-raise rollout)
- S0380.90 — 6 dispatch sites strict-raise + UnmappedSapCode shared
              module + GOV.UK API digit-string meter_type bug fix

HANDOVER_POST_S0380_90.md covers full state, cumulative-closure table,
strict-raise philosophy, and which 6 dispatch sites were closed.

NEXT_AGENT_PROMPT_POST_S0380_90.md briefs the next-slice work:
S0380.91 (RdSAP 10 Table 15 row 3 "Cavity masonry filled" U=0.2 in
u_party_wall — closes ~+1000 kWh of the remaining +1460 SH residual
on cert 000565 Ext1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 09:52:35 +00:00

14 KiB
Raw Blame History

Handover — post S0380.85..90 (BP main-wall closure + SH-channel discovery + strict-raise series)

Branch: feature/per-cert-mapper-validation. HEAD 9bfb8524. Predecessor: HANDOVER_POST_S0380_84.md.

Slices committed this session (S0380.85..90)

Six spec-cited slices: two BP main-wall closures (Curtain Wall + thin- wall stone) followed by SH-channel investigation that surfaced the S0380.87 single-line bug (the dominant SH residual driver), then a 3-slice strict-raise series that closed the calculator's cascade- dispatch silent-fallback inventory.

Slice Commit Spec Cert 000565 outcome
S0380.85 647c1aad RdSAP 10 §5.18 (PDF p.48) — "U= 2.0 W/m²K for pre-2023 curtain walls; for post-2023, U-values as for windows below Table 24" Cascade walls 443 → 555.93 W/K (+112). BP[2] Ext2 Curtain Wall U: 0.60 → 1.40 per worksheet (29a).
S0380.86 6c8bbbc9 RdSAP 10 §5.6 Table 12 (PDF p.40) thin-wall stone formula + §5.8 + Table 14 dry-line Cascade walls 555.93 → 602.40 W/K (worksheet 604.07; 0.27% residual). BP[0] alt1 U: 0.32 → 2.34 EXACT vs worksheet. Plus SapAlternativeWall.wall_thickness_mm field added (per feedback-no-misleading-insulation-type).
S0380.87 c0328f4e SAP 10.2 Table 4e GROUP 2 (PDF p.172-173) — HP control code 2207 → control type 3 Largest single-slice movement of the session: sap_score 23 → 27; SH residual +7924 → +1460 kWh (82% closed); sap_score_continuous +4.71 closer. Mechanism: wrong control type → wrong elsewhere off-hours per Table 9 → MIT_elsewhere over by +0.5 °C → over-counted SH.
S0380.88 1b3bbbf7 SAP 10.2 Table 4e (PDF p.171-174) all 8 groups Introduced UnmappedSapCode(ValueError) strict-raise. Extended _CONTROL_TYPE_BY_CODE to all 40 codes (Groups 0-7). Corpus audit closed 3 silent mis-classifications (codes 2307, 2401, 2603).
S0380.89 6d02d205 SAP 10.2 Table 4d (PDF p.170) Fixed a latent bug: _responsiveness had if emitter == 2: return 0.25 — treating screed UFH as concrete-slab UFH. Spec is R=0.75 (3× under-spec). Bug was latent because _first_main_heating picks main[0] and all cohort+golden certs lodge radiators (emitter=1) on main[0].
S0380.90 9bfb8524 Bundled 6 dispatch-site closures _pv_pitch_deg, _pv_overshading_factor, tariff_from_meter_type, _tariff_high_low_rates_p_per_kwh, _heat_network_dlf, _secondary_heating_fraction_for_category all flipped to strict-raise. UnmappedSapCode promoted to shared domain/sap10_calculator/exceptions.py. Fixed a second latent bug: GOV.UK API lodges meter_type='2' as digit-string on 125 golden certs — silently fell through to STANDARD via the str-dict miss; now routes via int-cast short-circuit.

Test baseline at HEAD 9bfb8524: 574 pass + 9 expected test_sap_result_pin[000565-*] fails. Pyright net-zero on every touched file. Cohort + golden + cert 9501 unaffected.

Cert 000565 state (HEAD 9bfb8524)

Pin Cascade Worksheet Δ Cause
sap_score (int) 27 29 2 Remaining +1460 SH residual; closes when party-wall CF U=0.2 + ventilation gap close
sap_score_continuous 27.3534 28.5087 1.16 Downstream of SH residual
ecf 5.5066 5.3866 +0.12 Downstream
total_fuel_cost_gbp 4784.29 4680.26 +104 Downstream
co2_kg_per_yr 6581.12 6447.63 +133 Downstream
space_heating_kwh 60468.18 59008.35 +1460 Party-wall CF over-count + ventilation +27 W/K
main_heating_fuel 35569.52 34710.79 +859 Follows SH via 1/COP
hot_water_kwh 3755.03 3755.03 ✓ 0 EXACT §4 cascade 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

Cumulative closure across this session

Pin post-.84 .85 .86 .87 .88 .89 .90
sap_score 26 24 23 27 27 27 27
sap_score_continuous 26.50 23.94 22.64 27.35 27.35 27.35 27.35
space_heating_kwh +2591 +6348 +7924 +1460 +1460 +1460 +1460
cascade walls W/K 443 555.93 602.40 602.40 602.40 602.40 602.40

S0380.85+.86 closed the BP main-wall cascade gap (walls 443 → 602 W/K, worksheet 604). Per feedback-verify-handover-claims the predicted SH closure didn't materialise — instead exposed a separate +8 k kWh SH-channel over-count that S0380.87 traced to a single-line spec dispatch bug. S0380.88-90 forecloses that bug pattern across the calculator.

Why the BP-wall slices made SH "worse" before S0380.87 fixed it

Per the diagnosis at the end of S0380.86:

  • Pre-S0380.84: cascade walls 322 (under by 282) + party 153 (over by 88) ≈ cancelled at HTC level. SH residual ~0.
  • Post-S0380.84: party fixed, walls still under. Cascade HTC under. But SH cascade was OVER worksheet — meaning a separate non-fabric SH-channel over-count was masked by the wall under-count.
  • Each spec-correct +1 W/K of cascade walls added ~33.5 kWh of cascade SH (consistent ratio across .85/.86/.87). Closing wall under-count exposed the SH-channel over-count fully.
  • S0380.87 traced it: cert 000565 main_heating_control=2207 (HP zone control) silently mapped to type 2 instead of type 3 → wrong elsewhere off-hours → MIT_elsewhere +0.5 °C → SH +4500 kWh.

Strict-raise series (S0380.88..90) — calculator philosophy change

User mandate ("we keep debugging silent fallbacks") prompted the strict-raise rollout. New module:

domain/sap10_calculator/exceptions.pyUnmappedSapCode(ValueError).

Mirror of mapper-side UnmappedApiCode / UnmappedElmhurstLabel. Principle: distinguish "lodging absent" (None / 0 / "" — cascade default OK per RdSAP §6.2.3 "assume as-built") from "lodging present but unmapped" (raise so spec-coverage gap surfaces at test time). Strict-raise applies to CODE DISPATCH sites; VALUE defaults (u_wall, u_floor, ...) remain total per cascade-helper docstring.

Eight dispatch sites flipped:

  1. _control_type (S0380.87 → .88)
  2. _responsiveness (S0380.89 — fixed bug)
  3. _pv_pitch_deg (S0380.90)
  4. _pv_overshading_factor (S0380.90)
  5. tariff_from_meter_type (S0380.90 — fixed bug)
  6. _tariff_high_low_rates_p_per_kwh (S0380.90)
  7. _heat_network_dlf (S0380.90)
  8. _secondary_heating_fraction_for_category (S0380.90)

Both latent bugs (S0380.89 + S0380.90) were exclusively surfaced by the strict-raise rollout — corpus audit caught the dispatch coverage gaps that would otherwise have stayed silent.

Open work — prioritised next slices

S0380.91 — u_party_wall Table 15 row 3 "Cavity masonry filled"

Highest-leverage remaining single-cause closure for cert 000565.

Per RdSAP 10 §5.10 Table 15 (PDF p.42) "U-values of party walls":

Party wall type U


Solid masonry / timber / system 0.0 Cavity masonry unfilled 0.5 Cavity masonry filled 0.2 ← cert 000565 Ext1 lodges CF Unknown, house 0.25 Unknown, flat / maisonette 0.0

The current u_party_wall at domain/sap10_ml/rdsap_uvalues.py:1022 does not have a CF (Cavity masonry filled) branch — both CU (unfilled) and CF (filled) currently route to U=0.5. Spec value for CF is 0.2.

Cert 000565 Ext1 lodges Party Wall Type CF Cavity masonry filled; the cascade over-counts party_wall by (0.5 - 0.2) × Ext1_party_area ≈ +28 W/K. At the cascade rate of 33.5 kWh per W/K, this maps to **+1000 kWh of the remaining +1460 SH residual**.

The existing mapper at datatypes/epc/domain/mapper.py:2196 already maps "CF" → SAP10 code 4 (Cavity) — same as CU — and the comment already flags this as a known approximation since S0380.64:

CF: 4, # Cavity masonry filled (cert 000565 Ext1) — RdSAP 10

Table 15 row 3 spec U=0.20. The cascade's u_party_wall

only returns 0.0 / 0.5 / 0.25 for code 4 today, so CF

rounds up to the conservative cavity-unfilled U=0.5 —

matches the existing `_API_PARTY_WALL_CONSTRUCTION_TO

_SAP10[3]` approximation until u_party_wall gains the

filled-cavity branch (TODO).

Slice span:

  1. Need a way to distinguish CF from CU in u_party_wall — currently the function takes a single party_wall_construction int. The Elmhurst mapper collapses both to code 4. Need either:

    • New party_wall_insulation_type parameter (filled / unfilled)
    • Or a new wall_construction int specifically for CF (e.g. 11)
    • Or a separate cert-side "party_wall_filled: bool" field

    The cleanest is option 2 (new wall_construction int) since it parallels the existing WALL_CAVITY=4 convention.

  2. Cohort + golden audit for party-wall CF lodgings — only cert 000565 Ext1 in the cohort has CF; golden API enum has its own party_wall _construction enum at _API_PARTY_WALL_CONSTRUCTION_TO_SAP10[3] which is also CF-aware per the comment (currently rounds to U=0.5).

Expected outcome: cert 000565 SH residual +1460 → ~+460 kWh; sap_score 27 → 28 (or 29 depending on continuous SAP rounding).

S0380.92+ — remaining smaller residuals

After S0380.91 the cascade should be within ~+500 kWh SH. Open work-items per project-cert-000565-recovery-state memory:

  • Ventilation infiltration +27 W/K over worksheet (~+900 kWh SH) — RdSAP 10 §5.15 / SAP 10.2 §3 line refs (24)..(25)
  • Doors 0 vs worksheet ~21 W/K (cascade missing doors entirely?)
  • Lighting +2.19 / pumps_fans +2.48 (sub-spec / MEV PCDB gap)

Deferred (unchanged from earlier handovers)

  • MEV PCDB Table 4f component for pumps_fans +2.5 (blocked on external data acquisition)
  • HP SAP code → main_heating_category=4 mapper extension
  • 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

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: 574 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

Files touched this session

File Slices Change
backend/documents_parser/elmhurst_extractor.py .85 Curtain Wall Age extraction in _wall_details_from_lines
backend/documents_parser/tests/test_summary_pdf_mapper_chain.py .85, .86 5 new tests (extractor + mapper + cascade pins)
datatypes/epc/surveys/elmhurst_site_notes.py:WallDetails .85 curtain_wall_age: Optional[str] = None
datatypes/epc/domain/epc_property_data.py .85, .86 SapBuildingPart.curtain_wall_age; SapAlternativeWall.wall_thickness_mm
datatypes/epc/domain/mapper.py .85, .86 Plumb new fields through _map_elmhurst_building_part + _map_elmhurst_alternative_wall (also rename wall_insulation_thickness mis-route → wall_thickness_mm)
domain/sap10_ml/rdsap_uvalues.py .85, .86 _u_curtain_wall helper (§5.18); _u_stone_thin_wall_age_a_to_e helper (§5.6); u_wall dispatch extended with both branches
domain/sap10_calculator/worksheet/heat_transmission.py .85, .86 Pass curtain_wall_age=part.curtain_wall_age and wall_thickness_mm=alt_wall.wall_thickness_mm to u_wall
domain/sap10_calculator/rdsap/cert_to_inputs.py .87..90 Full Table 4e GROUP 2 dispatch (.87) + full Groups 0-7 + strict raise (.88) + Table 4d screed-UFH bug fix + strict raise (.89) + 5 dispatch helpers strict-raise (.90); UnmappedSapCode import from shared module
domain/sap10_calculator/exceptions.py .90 NEWUnmappedSapCode(ValueError) shared exception class
domain/sap10_calculator/tables/table_12a.py .90 tariff_from_meter_type strict-raise + digit-string int-cast fix
domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py .87..90 13 new tests across the strict-raise series
domain/sap10_ml/tests/test_rdsap_uvalues.py .85, .86 8 new tests (Curtain Wall 3-test set + thin-wall stone 5-test set)

Spec source quick-reference

  • SAP 10.2 full specification: domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf
    • Table 4d (p.170) — heat emitter responsiveness R
    • Table 4e (p.171-174) — heating system controls (Groups 0-7)
    • Table 9 (p.182) — heating periods + temperatures
    • Table 9b (p.183) — off-period temperature reduction formula
    • Table 11 (p.188) — secondary-heating fraction by main category
    • Table 12c (p.193) — heat-network distribution loss factor
    • Table M1 — PV overshading factor ZPV
  • RdSAP 10 specification: domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf
    • §5.6 Table 12 (p.40) — uninsulated stone wall thin-wall formula
    • §5.8 + Table 14 (p.40-41) — dry-line + insulation R-value
    • §5.10 Table 15 (p.42) — party-wall U-values (S0380.91 target)
    • §5.18 (p.48) — curtain wall U-values
    • §11.1 — PV pitch enum
  • 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 — slice-by-slice closure table for .85..87 + SH-channel diagnosis
  • reference_unmapped_sap_codeNEW memory documenting the strict-raise pattern + which dispatch sites are now closed
  • project_sap10_ml_deprecationNEW memory recording the user-requested folder deprecation (any new cascade helpers should land under domain/sap10_calculator/ not domain/sap10_ml/)
  • MEMORY.md — index refreshed at HEAD 9bfb8524