From 67af2e9b43424a150c191969b52da5ca8ca33d6c Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 20 May 2026 23:03:15 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20handover=20for=20=C2=A78c=20Space=20coo?= =?UTF-8?q?ling=20+=20000490=20SAP-score=20diagnostic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two tickets in order for the next agent: 1. Ticket A — Investigate the 000490 +3 SAP overshoot. Corrects the previous agent's claim that "wiring water_heating_from_cert is the easy win"; that's already done. Real driver is the boiler efficiency cascade selecting 0.80 instead of the PDF Manufacturer-declared 0.882 (Vaillant Ecotec Pro). Time-boxed diagnostic; flag and defer if expensive. 2. Ticket B — §8c Space cooling (xlsx rows 435-466, lines (100)..(108)). All 6 Elmhurst fixtures = 0 cooling. Small slice; mirror §8 pattern. Includes spec anchors (Qcool formula sign, Jun-Aug inclusion rule), codebase pointers, slice plan, and the standard "do not" list. Co-Authored-By: Claude Opus 4.7 --- docs/sap-spec/HANDOVER_NEXT.md | 207 +++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 docs/sap-spec/HANDOVER_NEXT.md diff --git a/docs/sap-spec/HANDOVER_NEXT.md b/docs/sap-spec/HANDOVER_NEXT.md new file mode 100644 index 00000000..bb778920 --- /dev/null +++ b/docs/sap-spec/HANDOVER_NEXT.md @@ -0,0 +1,207 @@ +# Handover — §8c Space cooling + 000490 SAP-score diagnostic + +**For the agent picking up the next chunk of work.** Read this BEFORE invoking `/grill-me`. Read all of it. Caveman mode is the house style — terse, technical, no filler. + +Owner: `khalim@domna.homes`. Branch: `ara-backend-design-prd`. + +This handover covers **two tickets in order**: + +1. **Ticket A — Diagnose + (best-effort) close the 000490 e2e SAP-score gap.** §8 wiring exposed a +3 SAP overshoot. The previous agent claimed "the easy win is to wire `water_heating_from_cert` into `cert_to_inputs`" — that claim was **wrong** (it's already wired, see §A.2 below). The real gap is in upstream precision and is multi-source. Ticket A is to investigate and chip away, with explicit user check-ins before changing pinned expected values. +2. **Ticket B — Implement §8c Space cooling.** xlsx rows 435–466, worksheet line refs (100)..(108). All 6 Elmhurst fixtures = 0 cooling. Should be a small slice. + +Hard rules (same as previous handovers): +- **Tolerance**: don't loosen test tolerances to make them pass. If the orchestrator can't hit the locked tolerance, pause and ask the user. +- **Spec PDFs**: don't scan more than ~50 lines without checking with the user. +- **Fixture `build_epc()`**: don't modify — handover §11 of the original §6 handover is still in force. Local `_build_section_*_epc(fixture)` wrappers if you need to override windows. +- **Commit per slice**, one slice = one commit, AAA test convention (`# Arrange / # Act / # Assert`), Co-Authored-By trailer. + +--- + +## §A — Ticket A: 000490 SAP-score gap diagnostic + +### A.1 Current state +After §8 wiring (commit `f6ab7626`) and `_currently_within_3_points` test update (in `f6ab7626` too), 000490 cert-driven calculator output is: + +| Metric | Calculator | PDF | Delta | +|---|---|---|---| +| SAP integer | 60 | 57 | +3 | +| Continuous SAP | 60.22 | 57.40 | +2.82 | +| Annual space heating kWh | 11467.18 | 11183.275 | +2.5% | +| Annual HW fuel kWh | 3090.47 | 2850.57 | +8.4% | +| Total fuel cost £ | 756.99 | 807.54 | −6.3% | +| ECF | 2.4538 | 3.0539 | −20% | + +### A.2 What's already wired (do NOT redo) +- ✅ `water_heating_from_cert` IS the active path in `cert_to_inputs._hw_kwh_and_gains_from_cert` (file `cert_to_inputs.py`, line ~738). Legacy `predicted_hot_water_kwh` is a fallback only when `epc.total_floor_area_m2 is None`. For Elmhurst fixtures TFA is always present. +- ✅ `internal_gains_from_cert` (§5), `solar_gains_from_cert` (§6), `mean_internal_temperature_monthly` (§7), `space_heating_monthly_kwh` (§8) all wired. +- ✅ Per-fixture orchestrator-level conformance: all 6 Elmhurst fixtures pass §3–§8 at line-ref level (see `test_*.py::test_*_matches_elmhurst_worksheet_all_fixtures` tests). + +### A.3 Where the gap actually is + +The drift is in **scalar derivations the cert→inputs adapter computes from cert codes** — not in the per-month physics. Verified: + +```python +inputs = cert_to_inputs(_w000490.build_epc()) +inputs.main_heating_efficiency # → 0.8000 +# PDF Vaillant Ecotec Pro Manufacturer-declared: 0.882 +``` + +Drivers (ordered by likely SAP impact): + +1. **Water-heating efficiency** (`cert_to_inputs._legacy_water_heating_efficiency`) + - Currently selects ~0.78-0.80 from a static SAP10 efficiency table by `sap_main_heating_code`. + - PDF uses the **Manufacturer-declared** value (Vaillant Ecotec Pro 88.2%) from PCDB / cert efficiency lodgement. + - Look for: cert field carrying the manufacturer efficiency directly, OR the PCDB lookup that's currently stubbed (per ADR-0009 grill: `NoOpPcdbLookup`). + - Fixing this should drop HW fuel from 3090 → ~2750 kWh, closing ~340 kWh on annual fuel. + +2. **Space heating delivered = q_heat / main_heating_efficiency** + - Same efficiency cascade. PDF (210) Jan = 88.2% throughout. Ours = 80%. + - q_heat itself is correct (we just nailed §8 to 1e-1 kWh). + - Fixing efficiency drops `main_heating_fuel_kwh_per_yr` from 14334 → ~13000 kWh. + +3. **§3 transmission HLC precision** (~+2.5% on annual space heating) + - Calculator's `inputs.heat_transmission.total_w_per_k` = 236.6211 (matches PDF LINE_37). + - But cert_to_inputs derives `total_w_per_k` from windows + walls + roof + floor + thermal_bridging. If any of those is off, HLC drifts → §7 MIT drifts → §8 space heating drifts. + - This is much smaller than the efficiency gap. + +### A.4 Recommended approach for Ticket A + +**Do not** attempt to fix the entire efficiency cascade in one go — it touches §4 + §9 + §12 simultaneously and is a multi-day rewrite. + +**Do** spend ~30 minutes investigating and either: + +a) **Find a cert field that carries Manufacturer efficiency directly** that we're not reading (most likely: `epc.sap_heating.main_heating_details[0].efficiency_declared_pct` or similar). If present, wire it through `cert_to_inputs._main_heating_efficiency_pct` as a precedence-over-table override. Single-slice fix. **Surface findings to the user before implementing.** + +b) **If no such cert field exists**, write a one-paragraph finding into `SPEC_COVERAGE.md` "Prioritised gap list" under "Boiler efficiency Manufacturer override" + ship as a known gap. Skip to Ticket B. + +The user explicitly said in the previous turn: *"this is silly"* about the 000490 overshoot — meaning they want progress on it, but understand it might not be a one-line fix. Bias toward (a) if cheap; flag and defer if expensive. + +### A.5 Don't update e2e tolerances during Ticket A +The `_currently_within_3_points` ceiling for 000490 should stay 3 until Ticket A actually moves it. Tightening to 2 / 1 only after physics improves; loosening is a regression. + +--- + +## §B — Ticket B: §8c Space cooling + +### B.1 Mission +Implement §8c (xlsx rows 435–466) — produce `cooling_monthly_kwh` for all 12 months. All 6 Elmhurst fixtures = 0 (no air conditioning lodged). Should be the smallest section yet. + +Worksheet lines you must close: +- (100)m heat loss rate L_m (cooling) — recomputed via §7 chain at cooling Ti (≈ same as heating MIT for our fixtures) +- (101)m utilisation factor for loss η_loss (cooling Table 10a, formula differs from Table 9a) +- (102)m useful loss = η × L +- (103)m gains (excluding pumps/fans Table 5a per spec — see §6 of the SAP10.2 PDF) +- (104)m cooling requirement Q_cool +- ∑(104) over Jun-Aug only (cooling inclusion rule — June to August, disregarding September to May) +- (105) cooled-area fraction f_C (almost always 0 in RdSAP — set 0 for all 6 fixtures) +- (106)m × intermittency factor (Table 10b) +- (107)m space cooling requirement after f_C +- (108) cooling per m² = (107) / TFA + +### B.2 SAP10.2 spec anchors (already extracted) + +From the spec at `/tmp/sap102.txt`: +- Line 10320: `Qcool = 0.024 × (Gm - mLm) × nm × fcool × fintermittent` (note the SIGN — gains − losses, opposite of heating) +- Line 10321: `Set Qcool to zero if negative or less than 1 kWh.` +- Line 10325: `Include the cooling requirements for each month from June to August (disregarding September to May).` + +Table 10a (utilisation factor for cooling) is a separate formula from Table 9a heating η — page 186 of SAP10.2 spec. +Table 10b (intermittency factor) — page 187. +Table 10c (SEER for cooling system fuel calc) — page 187. + +### B.3 Existing state +- No `space_cooling.py` module exists. +- Calculator has no cooling fields. +- All 6 Elmhurst fixtures have `has_fixed_air_conditioning=False` → no cooling system → cooling fuel = 0. + +### B.4 Likely shape (mirror §8 pattern) + +```python +@dataclass(frozen=True) +class SpaceCoolingResult: + heat_loss_rate_monthly_w: tuple[float, ...] # (100) + utilisation_factor_loss_monthly: tuple[float, ...] # (101) — Table 10a + useful_loss_monthly_w: tuple[float, ...] # (102) + cooling_gains_monthly_w: tuple[float, ...] # (103) + cooling_requirement_monthly_kwh: tuple[float, ...] # (104) + intermittency_factor_monthly: tuple[float, ...] # (106) + space_cooling_monthly_kwh: tuple[float, ...] # (107) ← what the calculator consumes + space_cooling_kwh_per_yr: float + space_cooling_per_m2_kwh: float # (108) + + +def space_cooling_monthly_kwh( + *, + monthly_heat_transfer_coefficient_w_per_k: tuple[float, ...], + monthly_internal_temperature_c: tuple[float, ...], + monthly_external_temperature_c: tuple[float, ...], + monthly_total_gains_w: tuple[float, ...], + total_floor_area_m2: float, + cooled_area_fraction: float = 0.0, # f_C — almost always 0 for RdSAP + # Table 10b intermittency factor by control type, default to 0.25 (intermittent) + intermittency_factor: float = 0.25, +) -> SpaceCoolingResult: ... +``` + +### B.5 Conformance budget +**All 6 fixtures = 0 cooling** for every line. So conformance is trivial: assert every per-line tuple is zero. The interesting test is the **synthetic positive case** — pin a non-zero cooled-area fraction + warm summer month + verify Q_cool emerges, then verify with `f_C = 0` it collapses to zero. + +### B.6 Calculator coupling +After orchestrator: add `CalculatorInputs.space_cooling_monthly_kwh: tuple[float, ...]` field (defaults `(0.0,) * 12` for backwards-compat). The calculator's `MonthlyEntry` already has `space_heat_requirement_kwh`; either add `space_cool_requirement_kwh` or just expose the annual total on `SapResult`. Mirror §8 pattern. + +### B.7 Slice plan (suggested) + +``` +Slice 1 — SpaceCoolingResult + space_cooling_monthly_kwh orchestrator + (Table 10a η_loss + Jun-Aug inclusion rule); synthetic positive + test (non-zero f_C, hot July) + synthetic zero test (f_C=0) +Slice 2 — Per-fixture SECTION_8C_COOLED_AREA_FRACTION=0 constants + + ALL_FIXTURES e2e (every line tuple = 0 across all 6 fixtures) +Slice 3 — CalculatorInputs.space_cooling_monthly_kwh + cert_to_inputs + wiring (atomic) + drop legacy if any +Slice 4 — SPEC_COVERAGE §8c row + slice progress table +``` + +Smaller than §8 because there's no upstream-drift surprise to chase (all fixtures = 0). + +--- + +## §C — Codebase pointers + +- §8 orchestrator (close template): [packages/domain/src/domain/sap/worksheet/space_heating.py](../../packages/domain/src/domain/sap/worksheet/space_heating.py) +- §7 orchestrator (per-zone η pattern): [packages/domain/src/domain/sap/worksheet/mean_internal_temperature.py](../../packages/domain/src/domain/sap/worksheet/mean_internal_temperature.py) +- cert→inputs adapter: [packages/domain/src/domain/sap/rdsap/cert_to_inputs.py](../../packages/domain/src/domain/sap/rdsap/cert_to_inputs.py) (look for the chain at lines 870-960 for how §5/§6/§7/§8 stack) +- E2e SAP-score test: [packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py](../../packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py) +- §8 fixture pins (template): `_elmhurst_worksheet_000490.py` `SECTION_7_*` + `LINE_85..LINE_99` constants +- §8 ALL_FIXTURES test: [test_space_heating.py](../../packages/domain/src/domain/sap/worksheet/tests/test_space_heating.py) +- Extract script template: `/tmp/extract_section8.py` (rewrite for §8c — only ~30 lines) + +## §D — Commit chain to inspect + +```bash +git log --oneline --grep '§8 slice' +# 9113f30a §8 slice 1 — orchestrator + summer clamp +# 1f078af7 §8 slice 2 — 6-fixture conformance +# f6ab7626 §8 slice 3 — calculator + cert_to_inputs wiring +# bb827803 docs — SPEC_COVERAGE §8 + slice progress +``` + +## §E — Skills + +The dev container ships `/grill-me`, `/tdd`, `/caveman`. Default flow: + +``` +/grill-me → walk down design tree +/tdd implement §8c Space cooling → one test → one impl → repeat +``` + +User invokes `/grill-me` first when ready. Audit + surface unknowns first; wait for `/grill-me`. + +## §F — Definitely do NOT + +- Do **not** claim "wiring water_heating_from_cert is the easy win" — that ship has sailed (see §A.2). +- Do **not** update the 000490 `_currently_within_3_points` ceiling unless Ticket A actually moves the SAP score. +- Do **not** loosen test tolerances to make tests pass — ask the user. +- Do **not** scan more than ~50 lines of a spec PDF before asking the user for the specific table/page. +- Do **not** modify existing fixture `build_epc()` functions. +- Do **not** invoke `/ultrareview` yourself.