docs: handover for §6 Solar gains agent

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 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-20 19:29:30 +00:00
parent 52a11f5e74
commit 29feee7869

View file

@ -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: <line_ref> <name> — <one-line summary>`.
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 <noreply@anthropic.com>` 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).