Model/domain/sap10_calculator/docs/HANDOVER_POST_S0380_109.md
Khalim Conn-Kowlessar 98a4b5b9e6 docs: handover + next-agent prompt post S0380.105..109 (MEV trifecta + window routing + Connected gable + §5.7/5.8 brick formula)
Captures the 5-slice session that took cert 000565 continuous SAP
from +0.0182 → -0.0059 (magnitude 67% smaller) via spec-cited
intermediate-value closures.

  HANDOVER_POST_S0380_109.md     full state + per-slice movement
                                 + per-pin journey + lessons learned
  NEXT_AGENT_PROMPT_POST_S0380_109.md   focused briefing pointing
                                 at S0380.110 (Lighting g×FF closure
                                 — leading remaining residual at
                                 -2.17 kWh) and S0380.111 (roof
                                 window U formula refinement).

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

17 KiB
Raw Blame History

Handover — post S0380.105..109 (MEV CO2/PE + window routing + Connected gable + §5.7/5.8 brick formula)

Branch: feature/per-cert-mapper-validation. HEAD efb203f7. Predecessor: HANDOVER_POST_S0380_103.md.

Slices committed this session (S0380.105..109)

Five spec-cited slices targeting cert 000565 continuous-SAP closure. The MEV trifecta completed first (.105/.106), then a routing fix (.107) surfaced and re-shaped the fabric residuals, then two spec-correct fabric closures (.108/.109) drove the fabric residual from -0.99 W/K → +0.03 W/K and continuous SAP from +0.0182 → -0.0059 (magnitude 67% smaller).

Slice Commit Spec Cert 000565 outcome
S0380.105 8a3aaf7a SAP 10.2 Table 12a Grid 2 + Table 12d (PDF p.191, p.194) — MEV CO2 split pumps_fans_co2_kg_per_yr ✓ EXACT (35.3349 vs ws (267)). Total CO2 sign-flipped -1.81 → -2.12 (exposed downstream main_heating CO2 -2.43).
S0380.106 8effa2d0 SAP 10.2 Table 12a Grid 2 + Table 12e (p.191, p.195) — MEV PE split pumps_fans_pe_kwh_per_yr ✓ EXACT (383.3797 vs ws (281)). PE 62228.49 → 62227.06. MEV cascade trifecta cost/CO2/PE COMPLETE.
S0380.107 b7fa5f74 RdSAP 10 §3.7.1 (PDF p.21) + §8.2 (p.50) — window/rooflight routing New 4-rule heuristic uses BP roof type alongside glazing/U. Closes windows ✓ EXACT. Net fabric HTC -0.99 → +0.33 W/K. Continuous SAP +0.0182 → -0.0128 (magnitude 30% smaller). Integer SAP TRANSIENTLY 29→28 (crossed 28.5 rounding boundary). S0380.103 cost test reframed to pin rate not total.
S0380.108 9159e91f RdSAP 10 §3.9.2 step (d) + Table 4 row 4 (PDF p.22-23) — Connected RR gables New connected_wall kind: deducts area from A_RR but skips W/K. Closes roof +1.59 → +0.30 W/K (81% closed) + TB +0.71 → +0.15 (79%) + area +4.70 → +1.02 m² (78%). Integer SAP RECOVERED to 29 ✓ EXACT. Continuous SAP sign-flipped under (-0.0128 → +0.0293).
S0380.109 efb203f7 RdSAP 10 §5.7 Table 13 + §5.8 Table 14 (PDF p.41-42) — solid brick + insulation formula §5.7+§5.8 chain replaces Table-6 bucket for SOLID_BRICK + lodged thickness + External/Internal insulation. Also adds Table 6 footnote (a) cap on §5.6 stone formula (only when not dry-lined). Walls -1.54 → +0.01 W/K (essentially closed). Continuous SAP magnitude 80% improved (+0.0293 → -0.0059). All SH-driven downstream residuals magnitude-reduced 65-80%.

Test baseline at HEAD efb203f7: 608 pass + 7 expected test_sap_result_pin[000565-*] fails. Pyright net-zero per touched file across every slice.

Cert 000565 state (HEAD efb203f7)

Fabric subtotals — essentially closed

Component Cascade W/K Worksheet W/K Δ Status
walls 604.08 604.07 +0.01 sub-spec float drift
party_walls 65.13 65.13 ✓ EXACT
floor 61.67 61.67 ✓ EXACT
roof 51.68 51.38 +0.30 sub-spec (S0380.108 closed 81%)
windows 11.48 11.48 ✓ EXACT S0380.107
roof_windows 3.15 3.58 -0.43 cascade U formula gap (see §A below)
doors 11.10 11.10 ✓ EXACT
thermal_bridging 128.80 128.65 +0.15 sub-spec (S0380.108 closed 79%)
total external area 858.66 857.64 +1.02 sub-spec (S0380.108 closed 78%)
total W/K 937.09 937.06 +0.03 essentially closed

SapResult pins (HEAD efb203f7)

Pin Cascade Worksheet Δ Status
sap_score (int) 29 29 ✓ EXACT S0380.108
sap_score_continuous 28.5028 28.5087 -0.0059 80% smaller than .104 baseline
ecf 5.3874 5.3866 +0.0008 50% smaller than .104
total_fuel_cost_gbp 4680.78 4680.26 +0.52 was -1.62 (.104) / -2.62 (.108)
co2_kg_per_yr 6448.34 6447.63 +0.72 was -1.81 (.104)
space_heating_kwh_per_yr 59020.02 59008.35 +11.67 was -27.5 (.104)
main_heating_fuel_kwh_per_yr 34717.66 34710.79 +6.87 was -16.2 (.104)
hot_water_kwh_per_yr 3755.03 3755.03 ✓ EXACT unchanged
lighting_kwh_per_yr 1382.67 1384.84 -2.17 rooflight g×FF default-vs-lodged drift
pumps_fans_kwh_per_yr 252.5159 252.5159 ✓ EXACT S0380.102
pumps_fans_co2_kg_per_yr 35.3349 35.3349 ✓ EXACT S0380.105
pumps_fans_pe_kwh_per_yr 383.3797 383.3796 ✓ EXACT S0380.106

Continuous SAP journey

Slice Δ vs ws Notes
Pre-S0380.105 +0.0182 Post-S0380.103 baseline (MEV cost split)
S0380.105 +0.0182 CO2 doesn't feed ECF — no continuous change
S0380.106 +0.0182 PE doesn't feed ECF either
S0380.107 -0.0128 Window routing fix; 30% magnitude reduction; integer SAP transiently 28
S0380.108 +0.0293 Connected gable deduction; integer SAP back to 29; sign-flipped
S0380.109 -0.0059 Solid brick §5.7+§5.8; 80% magnitude reduction from .108

Magnitude trajectory: 0.0182 → 0.0128 → 0.0293 → 0.0059. Net 67% improvement from session start.

Open work — prioritised next slices

S0380.110 — Lighting rooflight g×FF default-vs-lodged drift (low-medium leverage)

Current residual: -2.17 kWh/yr (cascade UNDER ws). After S0380.107 windows correctly route to sap_roof_windows, the cascade applies the Appendix L L2a daylight factor formula with rooflight contribution using _G_LIGHT_DEFAULT = 0.80 and _FRAME_FACTOR_DEFAULT = 0.70 regardless of the lodged glazing/frame on each rooflight.

For cert 000565:

  • Item 2 (Ext2 NR rooflight, 1.2 m², Triple glazing PVC frame): actual g×FF = 0.70 × 0.70 = 0.49 (cascade uses 0.56)
  • Item 5 (Ext4 A rooflight, 0.5 m², Double glazing Wood frame): actual g×FF = 0.80 × 0.70 = 0.56 (cascade uses 0.56 ✓)

Area-weighted: cascade overstates G_L by ~0.052 × 1.7 m² → DF slightly too low → lighting kWh slightly low.

Spec: SAP 10.2 Appendix L L2a (PDF p.~74) — G_L numerator should use each window's own g_perpendicular and frame_factor, not defaults.

Fix location: domain/sap10_calculator/worksheet/internal_gains.py function _daylight_factor_from_cert, the rooflight_g_l_numerator computation around line 613-618 — iterate epc.sap_roof_windows and use each one's actual g_perpendicular + frame_factor instead of defaults.

Expected closure: lighting -2.17 → ~0 kWh/yr. Tiny continuous-SAP ripple (lighting feeds CO2/cost/PE via the Table 12 monthly factors).

S0380.111 — Roof window U formula refinement (low leverage)

Current residual: -0.43 W/K (cascade UNDER ws). Cascade computes roof window effective U via 1 / (1/U_raw + 0.04) = 1.852 for U_raw = 2.0. Worksheet uses U_eff = 2.1062 for the same raw U.

Reverse-engineered: 1/2.1062 = 0.4748; 0.5 (=1/U_raw) - 0.4748 = 0.0252 — so the spec correction for roof windows differs from the vertical-window +0.04 by a factor of 0.0648.

Spec hunt: SAP 10.2 §3.2 / Table 6c (PDF p.51). Table 6c has a distinct "U-value** (roof window)" column with values higher than the vertical-glazing column (by typically ~+0.2-0.3 W/m²K). The exact correction depends on the spec's definition of roof-window surface resistances vs vertical-window film coefficients.

Likely fix: in heat_transmission.py the roof-window effective U should use a lookup keyed on the lodged glazing type rather than a flat +0.04 correction (which is the SAP10.2 §3.2 "windows" formula, not the rooflight one).

Expected closure: roof_windows -0.43 → 0 W/K. HTC change +0.43 W/K → continuous SAP -0.0015 (cascade more under). The roof_windows closure makes the residual SHIFT in the same direction as current -0.0059, so net continuous SAP slightly worse before lighting closes.

S0380.112 — Walls precision +0.01 W/K (sub-spec)

Tiny float rounding artifact in BP[0] alt_wall_1 (granite + dry-line): cascade computes raw U=2.3405, ws displays U=2.34, A×U product diff is 0.01 W/K. Rounding to 2 d.p. in the §5.6 dry-line path was added in S0380.109 — verify it fires for this case.

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)

What this session learned

Pattern: spec-correct intermediate fixes can sign-flip end-result residuals

Each of S0380.107, .108, .109 shifted end-result residuals (cost, CO2, SH, continuous SAP) by amounts larger than the closure itself — because the cascade's pre-slice residuals were partially CANCELLING. Removing one mis-handled component exposes other residuals that were masked.

The user's stated philosophy makes this explicit:

"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."

The trajectory bears this out: 5 slices → continuous SAP magnitude 0.0182 → 0.0059 (67% improvement) through multiple sign-flips along the way.

Pattern: existing snapshot tests need updating when they pin downstream metrics

S0380.103 cost test (test_summary_000565_mev_fans_cost_uses_table_ 12a_grid_2_fans_for_mech_vent_rate) was originally written against total_fuel_cost_gbp with a tight < +£0.05 threshold. After S0380.107 broke that threshold (cascade catches up on fabric HTC and total cost shifts by £2+), the test was reframed to pin inputs.pumps_fans_fuel_cost_gbp_per_kwh directly — the specific metric S0380.103 closes, decoupled from downstream changes.

Similarly, golden cert 6035 PE/CO2 pins were updated in S0380.109 per feedback-golden-residuals-near-zero — the cascade got closer to the actual EPC value, which is the intended direction.

When future slices fire on a cert that's pinned with downstream metrics, the same pattern applies: update the pin or reframe to a narrower intermediate.

MEV PCDB arc — architecture summary (unchanged from .103 handover)

The S0380.98..106 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)
        reads epc.mechanical_ventilation_index_number, .wet_rooms_count,
              .mechanical_vent_duct_type
        invokes mev.py helpers
    ↓
_table_4f_additive_components(epc) adds MEV → pumps_fans_kwh_per_yr

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)

For CO2 (S0380.105):
    _pumps_fans_co2_factor_kg_per_kwh(tariff, mev_kwh, total_pumps_fans_kwh, monthly)
        → kWh-weighted blend of FANS_FOR_MECH_VENT + ALL_OTHER_USES Table 12d factors

For PE (S0380.106):
    _pumps_fans_primary_factor(tariff, mev_kwh, total_pumps_fans_kwh, monthly)
        → kWh-weighted blend of FANS_FOR_MECH_VENT + ALL_OTHER_USES Table 12e factors

All three helpers fall back to the existing ALL_OTHER_USES rate on STANDARD-tariff certs and no-MEV certs (cohort-safe). The MEV cascade trifecta is now COMPLETE for cert 000565.

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/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: 608 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 residual lighting -2.17 kWh + roof window U formula gap -0.43 W/K + sub-spec walls float drift +0.01 W/K. The first two are the open S0380.110 / S0380.111 work fronts.

Files touched this session

File Slices Change
domain/sap10_calculator/rdsap/cert_to_inputs.py .105, .106 _pumps_fans_co2_factor_kg_per_kwh + _pumps_fans_primary_factor helpers + wire into call sites
datatypes/epc/domain/mapper.py .107, .108 Survey-aware _is_elmhurst_roof_window predicate; _elmhurst_bp_roof_type helper; Connected-gable routing to new connected_wall kind
domain/sap10_calculator/worksheet/heat_transmission.py .108, .109 connected_wall branch (deducts area, no W/K); pass wall_thickness_mm to per-BP main wall u_wall
domain/sap10_ml/rdsap_uvalues.py .109 _u_brick_thin_wall_age_a_to_e (§5.7 Table 13); _r_insulation_table_14 (§5.8 Table 14 interpolation); §5.7+§5.8 branch in u_wall; Table 6 footnote (a) cap on §5.6 stone (only when not dry-lined); 2 d.p. rounding on §5.6 dry-line result
domain/sap10_calculator/rdsap/tests/test_golden_fixtures.py .109 Re-pin cert 6035 PE/CO2 expectations
backend/documents_parser/tests/test_summary_pdf_mapper_chain.py .105, .106, .107, .108, .109 AAA tests for each slice; S0380.103 test reframed to pin cost rate directly

Spec source quick-reference

  • SAP 10.2 full specification: domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf
    • §3.2 / Table 6c (p.51) — Window/rooflight U formula — S0380.111 target
    • §10a Table 12a Grid 2 (p.191) — Other electricity uses high-rate fraction — S0380.105, .106
    • §10b Table 12d (p.194) — Monthly CO2 factors — S0380.105
    • §10c Table 12e (p.195) — Monthly PE factors — S0380.106
    • Appendix L L2a (p.~74) — Daylight factor G_L — S0380.110 target
  • RdSAP 10 specification: domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf
    • §3.7.1 (p.21) — Window vs roof window classification — S0380.107
    • §3.9.2 step (d) (p.23) — Connected gable area deduction — S0380.108
    • §5.6 (p.40) + Table 12 (p.41) — Stone wall thin-wall formula — S0380.109 (cap)
    • §5.7 (p.41) + Table 13 (p.41) — Brick wall U₀ by thickness — S0380.109
    • §5.8 (p.41-42) + Table 14 (p.42) — Insulation R formula — S0380.109
    • §8.2 (p.50) — Glazed walls/roof routing — S0380.107
    • Table 4 row 4 (p.22) — Connected gable U=0 — S0380.108
    • Table 6 footnote (a) (p.34) — §5.6 formula cap — S0380.109
  • 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 — appended .105/.106/.107/.108/.109 closures + open-work analysis
  • MEMORY.md — refreshed at HEAD efb203f7

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. When a slice surfaces a downstream pin that drifts (e.g. the integer SAP rounding flip in S0380.107), bring it back via a complementary closure in a subsequent slice (S0380.108 pattern).
  • Don't re-investigate any closed work (.91..109). 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/. (S0380.109 extended existing helpers in rdsap_uvalues.py — acceptable since the file is the current authoritative wall-U-value table and a migration plan hasn't yet landed for it specifically.)
  • Don't avoid spec-correct closures because continuous SAP drifts away or sign-flips — user explicitly OK'd transient drift. Zero error achievable only when every component is spec-correct.
  • Don't pin downstream-only metrics with tight thresholds — S0380.103 cost test pattern. Pin the narrowest intermediate the slice changes.

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.