Model/docs/sap-spec/HANDOVER_SECTION_6.md
Khalim Conn-Kowlessar 29feee7869 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>
2026-05-20 19:29:30 +00:00

12 KiB
Raw Blame History

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 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 — 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_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 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:
    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 §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. §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.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

# §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).