From 29feee7869f9c9616251067ef1c57fb13bfd8a9d Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 20 May 2026 19:29:30 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20handover=20for=20=C2=A76=20Solar=20gain?= =?UTF-8?q?s=20agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captures the §5 implementation pattern (slice-per-test/impl/commit, ALL_FIXTURES e2e conformance, frozen Result dataclass, calculator.py wiring) and the SAP10.2 / Table 6d gotchas that cost time during §5 (Z_solar vs Z_L columns, rooflight Z=1.0, existing modules untrusted). Hard constraints documented for the next agent: - 6-fixture conformance ≤5e-3 W on every line (do not loosen tests). - Stop and ask the user after ~15 min of unsuccessful reconciliation or before scanning more than ~50 lines of spec PDF. - Don't touch the untracked `sap worksheets/` folder. Surfaces the pre-grilling unknowns the §6 agent should propose recommended answers for during `/grill-me`. Co-Authored-By: Claude Opus 4.7 --- docs/sap-spec/HANDOVER_SECTION_6.md | 148 ++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 docs/sap-spec/HANDOVER_SECTION_6.md diff --git a/docs/sap-spec/HANDOVER_SECTION_6.md b/docs/sap-spec/HANDOVER_SECTION_6.md new file mode 100644 index 00000000..c4132861 --- /dev/null +++ b/docs/sap-spec/HANDOVER_SECTION_6.md @@ -0,0 +1,148 @@ +# Handover — SAP 10.2 §6 Solar gains + +**For the agent picking up §6.** Read this BEFORE invoking `/grill-me`. Read all of it. + +Owner: `khalim@domna.homes`. Branch: `ara-backend-design-prd`. + +## 1. Mission + +Implement §6 Solar gains (xlsx rows ~332-371) to **6-fixture worksheet conformance** with the same shape and tolerances we landed for §4 and §5. + +Worksheet lines you must close: +- **(74)** Z access factor (Table 6d, FIRST column — solar heating; NOT the lighting Z_L from §5) +- **(75)-(82)** per-orientation solar gain per month (N, NE, E, SE, S, SW, W, NW) +- **(83)** Total solar gains per month = Σ (75)..(82) +- **(84)** Total gains = (73) internal + (83) solar + +**Success criterion (non-negotiable):** all 6 Elmhurst U985 fixtures conform to **≤5e-3 W on every line**, every month. Tolerances in the parametrised tests must be tight — `abs=5e-3` or tighter. **Do not loosen tests to make them pass.** If a fixture won't close to that tolerance, pause and ask the user. + +The user expects "0 error". Anything looser means we missed a Table/formula. + +## 2. Codebase state (read first) + +- **Existing §6 file:** [packages/domain/src/domain/sap/worksheet/solar_gains.py](../../packages/domain/src/domain/sap/worksheet/solar_gains.py) — already has `Orientation` enum + `surface_solar_flux_w_per_m2()` leaf + `window_solar_gain_w()` leaf. **DO NOT assume these are correct or complete.** §5 had the same "looks done" appearance and was a 4-of-8 stub. Audit before reusing. +- **Existing §6 tests:** [test_solar_gains.py](../../packages/domain/src/domain/sap/worksheet/tests/test_solar_gains.py) — 7 leaf tests, **no per-fixture conformance**. Likely needs rewrite. +- **Existing cert→inputs window mapping:** [cert_to_inputs._window_inputs](../../packages/domain/src/domain/sap/rdsap/cert_to_inputs.py) at L379 already aggregates windows with `pitch_deg=90`, `g_perpendicular` from Table 6b, `frame_factor` from Table 6c, `overshading_factor=0.77` hardcoded (AVERAGE solar Z). Reuse the table lookups. +- **6 Elmhurst fixtures:** `_elmhurst_worksheet_NNNNNN.py` — each carries `LINE_4_TFA_M2`, `LINE_5_VOLUME_M3`, and worksheet outputs through §5. You will append: + - `SECTION_6_WINDOW_AREAS_BY_ORIENTATION_M2: dict[Orientation, tuple[float, ...]]` + - `SECTION_6_ROOFLIGHT_AREAS_M2: tuple[float, ...]` (or merge into the dict at `Orientation.HORIZONTAL`) + - `LINE_74_Z_ACCESS_FACTOR: float = 0.77` (default AVERAGE) + - `LINE_75_M_*` through `LINE_84_M_TOTAL_GAINS_W` 12-tuples +- **Calculator wiring:** [calculator.py:_solar_gains_w](../../packages/domain/src/domain/sap/calculator.py) currently calls the leaf `_solar_gains_w` per-month inside `_solve_month`. After §6 rebuild, the orchestrator should produce a 12-tuple that `CalculatorInputs.solar_gains_monthly_w` carries forward (mirror the §5 wiring pattern). + +## 3. Implementation pattern — mirror §5 EXACTLY + +Follow the §4/§5 slice precedent. **One slice = one test → one impl → one commit.** Slice commit message format: `§6 slice N: `. + +Suggested slice ordering (build in this order, tracer first): + +1. **Tracer:** simplest line. Probably `(74) z_access_factor` lookup from `OvershadingCategory` (mirror `_Z_L_BY_OVERSHADING` from internal_gains). +2. **Per-orientation flux leaves:** revalidate existing `surface_solar_flux_w_per_m2` against one worksheet's (75)..(82) column values. If wrong, fix; if right, keep. +3. **Per-window gain leaf:** `window_solar_gain_w` — formula `(75)m = A_w × S × g_⊥ × FF × Z`. Validate against worksheet to 4 d.p. +4. **Sum per orientation per month:** Σ over windows of the same orientation. +5. **(83) total solar gains:** Σ across orientations for each month. +6. **`SolarGainsResult` frozen dataclass** holding 9 line tuples ((75)..(83)). +7. **`solar_gains_from_cert` orchestrator** — signature mirrors `internal_gains_from_cert`: + ```python + def solar_gains_from_cert( + *, + epc: EpcPropertyData, + region: int, + overshading: OvershadingCategory = OvershadingCategory.AVERAGE, + rooflight_windows: tuple[RooflightInput, ...] = (), # if needed + ) -> SolarGainsResult: ... + ``` +8. **Extract LINE_74_*..LINE_84_* from all 6 U985 PDFs** via a `/tmp/extract_section6.py` script. Mirror the `/tmp/extract_section4.py` / `extract_section5.py` precedent. +9. **Populate fixtures + add ALL_FIXTURES-parametrized e2e test** with abs=5e-3 tolerance. +10. **Wire calculator.py** — add `solar_gains_monthly_w: tuple[float, ...]` to `CalculatorInputs`, replace per-month `_solar_gains_w` call with index lookup, update `cert_to_inputs` to call `solar_gains_from_cert`. Update synthetic test constructors. +11. **Delete legacy `_solar_gains_w` from calculator.py** + any superseded leaf fns. +12. **Update [SPEC_COVERAGE.md](SPEC_COVERAGE.md)** §6 row + add `## §6 — slice progress` table. + +## 4. Lessons from §5 that the §6 agent must absorb + +These cost time during §5. Don't repeat. + +1. **Table 6d has THREE columns.** First = solar heating Z (0.77 AVERAGE), second = solar cooling Z (0.9 AVERAGE), third = lighting Z_L (0.83 AVERAGE). For §6 you use the FIRST column. (§5 used the third — different value.) +2. **Table 6d note 2: rooflights use Z = 1.0** regardless of overshading bucket. Handle this in your orchestrator; do not assume a single Z applies to every window. 000516 has a 1.18 m² rooflight. +3. **Worksheet rounds intermediate values at 4 d.p.** When your formula gives `52.0001` and the worksheet shows `52.0000`, that's worksheet display rounding — your code is right. +4. **Existing modules are not trusted.** §5's was labelled "Full" in SPEC_COVERAGE but was 4-of-8 lines + a broken lighting fallback. Audit the existing `solar_gains.py` against worksheet (75)..(83) for 000490 before reusing it. +5. **`cert_to_inputs._window_inputs` hardcodes Z_solar=0.77 (AVERAGE).** Replace with the overshading enum in the orchestrator. The hardcode is a known shortcut. + +## 5. **CRITICAL: stop and ask, do not scan** + +§5 wasted ~30-60 min scanning the SAP10.2 spec PDF looking for the missing reconciliation factor on lighting. The factor was a single table column the agent kept missing. + +**Hard rule for §6:** if a formula or table lookup doesn't reconcile within **15 minutes** of normal debugging, **STOP** and ask the user (`AskUserQuestion`) for a worked example or pointer to the right table/page. The user has the worksheets, the spec PDFs, and domain knowledge. Asking is cheap; rabbit-holing is expensive. + +**Specific triggers to ask the user, not scan:** +- A formula gives the wrong value and you've checked the obvious inputs. +- You're not sure which Table or column applies (e.g. "Z for solar heating vs cooling vs lighting"). +- A cert field's value doesn't map cleanly to a SAP code. +- You're considering reading more than ~50 lines of spec text. + +Format the question with the data: "I'm getting X, worksheet says Y, for fixture 000NNN. The formula I'm using is Z. Which table/factor am I missing?" + +## 6. Pre-grilling — unknowns the agent must surface + +The user will run `/grill-me` before implementation. The grilling session should tease out at least these branches; the agent should propose recommended answers for each: + +1. **Scope of rebuild.** Audit existing `solar_gains.py` against 000490 worksheet (75)..(83). If it's exact, extend with orchestrator + fixture wiring. If it's wrong, rebuild like §5. (Likely rebuild; see lesson 4.) +2. **Orchestrator API shape.** Mirror §5 — takes `epc`, `region`, `overshading`, optional rooflight metadata. Returns `SolarGainsResult` (frozen dataclass with 9 line tuples). +3. **Multi-orientation handling.** Worksheet shows (75)..(82) per cardinal+inter-cardinal direction. How does the orchestrator group/aggregate windows? Likely via `Orientation` enum already in solar_gains.py. +4. **Region / Table U3 solar flux.** Each fixture has a region code (000490 = East Pennines). Confirm `region` field exists on EPC and feeds into surface_solar_flux_w_per_m2. +5. **Rooflight handling.** 000516 has a rooflight. Per Table 6d note 2: Z=1.0 + horizontal pitch → Table U3 horizontal flux. Decide: separate `rooflight_windows` arg or `pitch_deg=0` in the SapWindow. +6. **`cert_to_inputs._window_inputs` reconciliation.** That helper already maps SapWindow → WindowInput at pitch_deg=90 with Z=0.77. Decide: reuse + extend, or replace. +7. **Calculator.py wiring scope.** Same as §5: add `solar_gains_monthly_w` 12-tuple to `CalculatorInputs`, replace inline `_solar_gains_w` call. +8. **E2e SAP-score test impact.** Wiring §6 may shift 000490 / 000474 SAP scores. Decide whether to update expected values or pin to current. +9. **Column A vs Column B (Table 5 analogue).** §6 uses the solar HEATING column always for rating + DPER/TPER. The COOLING column (second) only applies to cooling-load calcs, which we're not doing. Document explicitly. +10. **Tolerance.** 5e-3 W on every line. Do not propose looser. + +## 7. References — bounded, do not browse + +Use these. **Do not scan more than ~50 lines per reference without checking with the user first.** + +- **SAP 10.2 spec PDF**: [docs/sap-spec/sap-10-2-full-specification-2025-03-14.pdf](sap-10-2-full-specification-2025-03-14.pdf). §6 prose is around page 12-15; Table 6b/6c/6d around page 178-181; Appendix U (solar flux Tables U3-U5) is the climate-data section. +- **RdSAP 10 spec PDF**: [rdsap-10-specification-2025-06-10.pdf](rdsap-10-specification-2025-06-10.pdf). Window data lodging conventions. +- **Canonical worked example xlsx**: [`2026-05-19-17-18 RdSap10Worksheet.xlsx`](../../2026-05-19-17-18%20RdSap10Worksheet.xlsx) at repo root. Cells in sheet `NonRegionalWeather` cover (74)..(84). +- **6 Elmhurst U985 worksheets**: `/workspaces/model/sap worksheets/U985-0001-NNNNNN.pdf`. These are the ground truth for conformance. +- **6 Elmhurst Summary PDFs**: `/workspaces/model/sap worksheets/Summary_NNNNNN.pdf`. Cert-input source. +- **§5 implementation as exemplar**: `packages/domain/src/domain/sap/worksheet/internal_gains.py` + `test_internal_gains.py` + `_elmhurst_worksheet_*.py` (search for `SECTION_5_*` and `LINE_66_M`..`LINE_73_M`). **This is the pattern to clone.** +- **§5 commit chain** (clone the shape): `git log --oneline --grep '§5 slice'` → 13 commits from `3ec56216` through `380115e2`. + +## 8. Test commands + +```bash +# §6 alone +python -m pytest packages/domain/src/domain/sap/worksheet/tests/test_solar_gains.py --no-header --no-cov + +# Full SAP suite (regression check) +python -m pytest packages/domain/src/domain/sap/ --no-header --no-cov -q + +# Per-fixture error diagnostic (write a temp script like the §5 one I used) +# Expect every line ≤5e-3 W on every fixture. +``` + +## 9. Commit conventions + +- Stage by name, never `git add -A` (user has untracked `sap worksheets/` PDFs that must not be committed). +- AAA test convention (`# Arrange / # Act / # Assert` literal headers). +- `Co-Authored-By: Claude Opus 4.7 ` trailer on every slice commit. +- One slice = one commit. Commit messages: `§6 slice N: ...`. + +## 10. When you finish + +- 6 fixtures conforming end-to-end on §6 to ≤5e-3 W. +- `solar_gains_from_cert` orchestrator wired into `cert_to_inputs` + `calculator.py`. +- Legacy per-month `_solar_gains_w` deleted. +- SPEC_COVERAGE.md §6 row flipped + slice progress table added. +- E2e SAP-score tests still passing (may need to update expected values; flag this to the user before changing them). +- A handover doc for §7 if you stop mid-flight. + +## 11. Definitely do NOT + +- **Do not** delete or modify the user's untracked `sap worksheets/` folder. +- **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 unless asked — they're pinned for §1-§5 conformance and the e2e SAP-score regression. +- **Do not** assume the existing `solar_gains.py` is correct. Verify against the 000490 worksheet first. +- **Do not** invoke `/ultrareview` yourself (user-triggered, billed).