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>
12 KiB
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 — already has
Orientationenum +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 — 7 leaf tests, no per-fixture conformance. Likely needs rewrite.
- Existing cert→inputs window mapping: cert_to_inputs._window_inputs at L379 already aggregates windows with
pitch_deg=90,g_perpendicularfrom Table 6b,frame_factorfrom Table 6c,overshading_factor=0.77hardcoded (AVERAGE solar Z). Reuse the table lookups. - 6 Elmhurst fixtures:
_elmhurst_worksheet_NNNNNN.py— each carriesLINE_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 atOrientation.HORIZONTAL)LINE_74_Z_ACCESS_FACTOR: float = 0.77(default AVERAGE)LINE_75_M_*throughLINE_84_M_TOTAL_GAINS_W12-tuples
- Calculator wiring: calculator.py:_solar_gains_w currently calls the leaf
_solar_gains_wper-month inside_solve_month. After §6 rebuild, the orchestrator should produce a 12-tuple thatCalculatorInputs.solar_gains_monthly_wcarries 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):
- Tracer: simplest line. Probably
(74) z_access_factorlookup fromOvershadingCategory(mirror_Z_L_BY_OVERSHADINGfrom internal_gains). - Per-orientation flux leaves: revalidate existing
surface_solar_flux_w_per_m2against one worksheet's (75)..(82) column values. If wrong, fix; if right, keep. - Per-window gain leaf:
window_solar_gain_w— formula(75)m = A_w × S × g_⊥ × FF × Z. Validate against worksheet to 4 d.p. - Sum per orientation per month: Σ over windows of the same orientation.
- (83) total solar gains: Σ across orientations for each month.
SolarGainsResultfrozen dataclass holding 9 line tuples ((75)..(83)).solar_gains_from_certorchestrator — signature mirrorsinternal_gains_from_cert:def solar_gains_from_cert( *, epc: EpcPropertyData, region: int, overshading: OvershadingCategory = OvershadingCategory.AVERAGE, rooflight_windows: tuple[RooflightInput, ...] = (), # if needed ) -> SolarGainsResult: ...- Extract LINE_74_..LINE_84_ from all 6 U985 PDFs via a
/tmp/extract_section6.pyscript. Mirror the/tmp/extract_section4.py/extract_section5.pyprecedent. - Populate fixtures + add ALL_FIXTURES-parametrized e2e test with abs=5e-3 tolerance.
- Wire calculator.py — add
solar_gains_monthly_w: tuple[float, ...]toCalculatorInputs, replace per-month_solar_gains_wcall with index lookup, updatecert_to_inputsto callsolar_gains_from_cert. Update synthetic test constructors. - Delete legacy
_solar_gains_wfrom calculator.py + any superseded leaf fns. - Update SPEC_COVERAGE.md §6 row + add
## §6 — slice progresstable.
4. Lessons from §5 that the §6 agent must absorb
These cost time during §5. Don't repeat.
- 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.)
- 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.
- Worksheet rounds intermediate values at 4 d.p. When your formula gives
52.0001and the worksheet shows52.0000, that's worksheet display rounding — your code is right. - 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.pyagainst worksheet (75)..(83) for 000490 before reusing it. cert_to_inputs._window_inputshardcodes 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:
- Scope of rebuild. Audit existing
solar_gains.pyagainst 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.) - Orchestrator API shape. Mirror §5 — takes
epc,region,overshading, optional rooflight metadata. ReturnsSolarGainsResult(frozen dataclass with 9 line tuples). - Multi-orientation handling. Worksheet shows (75)..(82) per cardinal+inter-cardinal direction. How does the orchestrator group/aggregate windows? Likely via
Orientationenum already in solar_gains.py. - Region / Table U3 solar flux. Each fixture has a region code (000490 = East Pennines). Confirm
regionfield exists on EPC and feeds into surface_solar_flux_w_per_m2. - Rooflight handling. 000516 has a rooflight. Per Table 6d note 2: Z=1.0 + horizontal pitch → Table U3 horizontal flux. Decide: separate
rooflight_windowsarg orpitch_deg=0in the SapWindow. cert_to_inputs._window_inputsreconciliation. That helper already maps SapWindow → WindowInput at pitch_deg=90 with Z=0.77. Decide: reuse + extend, or replace.- Calculator.py wiring scope. Same as §5: add
solar_gains_monthly_w12-tuple toCalculatorInputs, replace inline_solar_gains_wcall. - E2e SAP-score test impact. Wiring §6 may shift 000490 / 000474 SAP scores. Decide whether to update expected values or pin to current.
- 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.
- 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. §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. Window data lodging conventions.
- Canonical worked example xlsx:
2026-05-19-17-18 RdSap10Worksheet.xlsxat repo root. Cells in sheetNonRegionalWeathercover (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 forSECTION_5_*andLINE_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 from3ec56216through380115e2.
8. Test commands
# §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 untrackedsap worksheets/PDFs that must not be committed). - AAA test convention (
# Arrange / # Act / # Assertliteral 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_certorchestrator wired intocert_to_inputs+calculator.py.- Legacy per-month
_solar_gains_wdeleted. - 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.pyis correct. Verify against the 000490 worksheet first. - Do not invoke
/ultrareviewyourself (user-triggered, billed).