The flagged "priority" (per-main boiler interlock −5pp) was already implemented (S0380.141 cylinder-thermostat path + S0380.177 room- thermostat path); case 6 already produces (206)=79/(207)=84 exactly and 0240 is a combi with no cylinder. Records that S0380.201 closed the secondary dual-system pump item and the remaining case-6 gaps (space demand +1.28%, HW −1.6%) for full-SapResult promotion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
Handover — post S0380.200 (dual-main split done; boiler-interlock −5pp OPEN)
Point-in-time note. Start from AGENT_GUIDE.md for methodology,
accuracy bar, and pipeline — this records what this session did and what is open.
- Branch:
feature/per-cert-mapper-validation - HEAD:
8ae978a6(S0380.200) - Baseline:
2355 passed, 1 skipped, 0 failed. Verify with the AGENT_GUIDE §4 suite command.
What this session shipped (S0380.196–200)
The through-line: golden certs 6035 and 0240 were both closed to SAP-exact by finding real API-mapper bugs (not "lodged divergence"), each confirmed against a user-generated Elmhurst worksheet ("simulated case 5/6").
| Slice | What | Spec |
|---|---|---|
| .196 | API Simplified Type 1 room-in-roof: room_in_roof_type_1 gables (length-only, no height) weren't deducted from the A_RR shell → whole shell billed as roof at U_RR=2.30 (+52 W/K). Route them through detailed_surfaces (gable area = L × 2.45 default RR storey height). 6035 SAP −2→+0 exact, PE +19.16→+1.84. |
RdSAP 10 §3.9.1(e) p.21; Table 4 p.22 |
| .197 | Promoted "simulated case 5" (detached sandstone RR) to e2e fixture (001431_case5, 11 pins @1e-4). Fixed sandstone wall label "SS"→2 (_ELMHURST_WALL_CODE_TO_SAP10) + _parse_thickness_mm for "400+ mm" roof insulation (trailing + was dropped → u_roof fell to age default). |
— |
| .198 | API window_wall_type=4 → roof window. These are roof-of-room rooflights; the mapper flattened them into sap_windows (vertical, (27), U=2.0) instead of sap_roof_windows ((27a), inclined U=2.30 + 45° solar). The inclined solar dominates → 0240 SAP −1→+0 exact, PE +3.91→+1.95; 6035 PE +1.84→+1.37. Discriminator is wall_type==4 NOT window_type==2 (0390/7536 lodge window_type=2 on main walls). |
SAP 10.2 §3 (27a); Table 6e Note 2 |
| .199 | Site-notes mirror of .198: extractor parses "Roof of Room" window rows (_parse_window_from_anchors); _is_elmhurst_roof_window location branch; _ELMHURST_ROOF_WINDOW_U_BY_GLAZING["Double between 2002 and 2021"]=2.30. Case 6 pinned on §3 windows (test_section_3_roof_windows_case6_match_pdf): (27)=22.7408, (27a)=13.0375 exact. |
RdSAP 10 §3.7 |
| .200 | SAP 10.2 §9a two-main-heating split (203)/(204)/(205)/(207)/(213). The cascade lumped a 2-main dwelling into one system. Now space_heating_fuel_monthly_kwh splits demand (204) to sys1 @ (206) + (205) to sys2 @ (207); _solve_month sums main_1+main_2; _main_heating_detail_efficiency (new, the per-detail core of _main_heating_efficiency) gives each system its own efficiency. Site-notes: _map_elmhurst_main_heating_2 inherits Main 1's fuel when §14.1 omits Fuel Type. Cost/CO2/PE main_2 paths were already wired. 0240 unchanged (identical Eq-D1 systems). |
SAP 10.2 §9a |
Two new e2e fixtures: 001431_case5 (full SapResult, S0380.197) and
001431_case6 (§3 windows only, S0380.199 — see why below). Source PDFs
tracked under sap worksheets/golden fixture debugging/simulated case {5,6}/;
Summaries mirrored to backend/documents_parser/tests/fixtures/Summary_001431_case{5,6}.pdf.
⚠️ CORRECTION (post S0380.201) — the interlock priority was ALREADY DONE
The "priority" below was misdiagnosed. At HEAD the cascade already
produces case 6 (206) sys-1 eff = 79.0 and (207) sys-2 eff = 84.0,
matching the worksheet exactly. The cylinder-thermostat interlock path
(no_stored_hw_interlock = has_cylinder and cylinder_thermostat != "Y")
has existed since S0380.141; the room-thermostat path since S0380.177.
no_interlock = no_room_thermostat OR no_stored_hw_interlock — it does NOT
only catch 2101/2102. Toggling case 6 cylinder_thermostat N→Y flips eff
0.79→0.84, confirming the −5pp fires. Golden 0240 is a combi
(has_hot_water_cylinder=False) → correctly NOT penalised; its predicted
re-pin from the interlock is void. The misread came from
energy_requirements_section_from_cert (a §2.4 debug helper using raw
_main_heating_efficiency, which reports 84 — the real cert_to_inputs
cascade applies the −5pp at ~line 6071). See feedback-verify-handover-claims.
S0380.201 landed the SECONDARY item (dual-system aux pumps): SAP 10.2
Table 4f note c) second main-system circulation pump, gated on a lodged
main_heating_fraction > 0. Case 6 (231) 241 → 356 EXACT (= 41 Main-1
pump + 115 Main-2 pump + 200 oil aux). 0240 re-pinned (pumps 315 → 430,
integer 73 → 72, resid +0 → -1, PE +2.8092, CO2 +0.1385) — anticipated
and authorised below. 000565 protected by the fraction>0 gate (its Main 2
is a DHW-only combi, fraction 0).
Remaining case-6 gaps for full-SapResult promotion (vs P960-0001-001431):
- (98c) space demand cascade 12145.31 vs ws 11991.96 (+1.28%) — living-area MIT (87) ~0.3 °C low in winter; multi-causal (gains/heat-loss).
- (219) hot water cascade 4824.74 vs ws 4902.86 (−1.6%) — §4 walk needed. Once both close, promote case 6 to a full SapResult e2e fixture (pin grid below).
OPEN (was the priority, now DONE) — boiler-interlock −5pp efficiency adjustment, per main system
Goal: a RdSAP-10/SAP-10.2 spec-accurate implementation of the boiler interlock efficiency adjustment, applied per main heating system, done in the established pattern of this domain (per-line walk → cite spec → TDD → re-pin). This is the last gap blocking full closure of simulated case 6, and it will also re-pin golden 0240.
The evidence (simulated case 6)
sap worksheets/golden fixture debugging/simulated case 6/ — detached, dual
oil boiler (both SAP code 127, base seasonal eff 84%), radiators 51%
(control 2106) + underfloor 49% (control 2110). Its P960 worksheet:
| line | worksheet | meaning |
|---|---|---|
| (206) main sys-1 eff | 79.0 | 84 − 5pp |
| (207) main sys-2 eff | 84.0 | base, no penalty |
| (216) water-heater eff | 72.0 | also penalised (DHW leg of the −5pp) |
| "Temperature adjustment" | 0.0000 | flow temp has NO effect — this is NOT a flow-temperature feature |
Summary §14 lodges it explicitly: system 1 "Boiler Interlock: No", system 2 "Boiler Interlock: Yes". The 84→79 is the SAP 10.2 Table 4c(2) "no boiler interlock" −5pp Space + DHW adjustment (same mechanism as the AGENT_GUIDE "oil 6" worked example, S0380.177 — but that one fired off control 2101).
Why control 2106 (which HAS a room thermostat) is "no interlock"
Per RdSAP 10 boiler-interlock rules (find + cite the exact §; the existing
_NO_INTERLOCK_CONTROLS = {2101, 2102} block in cert_to_inputs.py ~line 1238
quotes "RdSAP 10 §3 p.57: boiler interlock is assumed present if there is a room
thermostat and [time control], AND — when there is a hot-water cylinder — a
cylinder thermostat; otherwise not interlocked"): system 1 serves the DHW
cylinder, the cylinder is present (Hot Water Cylinder Present: Yes) but
Cylinder Thermostat: No → interlock not present → −5pp, despite the
room thermostat. System 2 (underfloor, separate part, no cylinder interaction)
keeps interlock via its zone control → no penalty.
So the determination is not "control ∈ {2101,2102}". It is, per system:
interlock present ⇔ (room thermostat present, from the control code) AND
(time/programmer control) AND (cylinder absent OR cylinder thermostat present).
The current cascade only catches the "no room thermostat" path (2101/2102); it
misses the "room thermostat present but no cylinder thermostat" path that 2106
hits here.
This single root cause explains BOTH remaining case-6 deltas
- space heating: sys-1 eff 79 not 84 → main fuel cascade 14925 vs ws 14736.96
- hot water: the −5pp DHW leg → cascade HW 4824 vs ws 4902.86 (lower cascade fuel ⇒ cascade eff too high ⇒ missing the penalty)
0240 will shift — and that is correct (apply the spec uniformly)
Golden 0240 has the SAME controls (sys1 2106 / sys2 2110) AND the same
cylinder_thermostat = "N" with a cylinder present. So the spec-correct rule
applies the −5pp to 0240's system 1 too. 0240 is currently SAP-exact (continuous
72.55) without the penalty — that is an offsetting coincidence (it's API-only,
±0.5 bar, no worksheet). Per feedback-software-no-special-handling +
feedback-spec-floor-skepticism: implement the spec rule, let 0240 shift, and
re-pin it with a documented note. Expect 0240 continuous SAP to drop ~0.3–0.5
(may take the integer 73→72; if so the golden expected_sap_resid moves −1 and
that is the new truth). Measure precisely and re-pin PE/CO2 too.
Where to implement (per-line walk first, then TDD)
- Interlock determination. Add a per-
MainHeatingDetailhelper, e.g._boiler_interlock_present(main, epc) -> bool, encoding the RdSAP 10 rule above (room thermostat from control code + cylinder-thermostat gate when a cylinder is present).epc.sap_heating.cylinder_thermostat("Y"/"N") andcylinder_size/hot_water_cylinder_presentare the cylinder signals. The site-notes path already lodgescylinder_thermostat(mapper.py ~5183, string "Y"/"N"); the API path lodges it onsap_heating.cylinder_thermostat(0240 = "N"). - Apply Table 4c(2) −5pp per system. The existing −5pp lives near the
_NO_INTERLOCK_CONTROLSblock — find how it currently adjusts the seasonal efficiency for 2101/2102 and generalise it to fire onnot _boiler_interlock_present(main, epc), applied inside_main_heating_detail_efficiencyso each main system gets its own adjustment (sys1 −5, sys2 0). Confirm the DHW leg (water-heater efficiency (216)) is penalised too — the §4 water-heating cascade reads_main_heating_efficiency; verify the −5pp flows there (case 6 (216)=72 is the check). - Verify combi vs regular rows of Table 4c(2). The "no interlock" −5pp has a combi row (Space −5 / DHW 0) and a regular-boiler row (Space −5 / DHW −5); the DHW leg is gated on cylinder presence. Case 6 is a regular oil boiler with a cylinder → DHW −5 applies (hence (216)=72). Read the table; don't assume.
Validation target
After the fix, promote case 6 to a full SapResult e2e fixture (it's currently §3-windows-only because the lumped efficiency made (211)/(219)/(231) non- comparable). Case 6 worksheet Block-1 pin grid (P960-0001-001431):
- SAP 72 (258), continuous 71.6597, ECF 2.0316 (257)
- total fuel cost 1162.5374 (255), CO2 5953.6679 (272)
- (211) main sys-1 fuel 7741.6458, (213) main sys-2 fuel 6995.3106 (SapResult.main_heating_fuel_kwh_per_yr should be the sum 14736.9564)
- hot water 4902.8601 (219), lighting 357.6571 (232)
- pumps/fans 356.0 (231) — see the SECOND open item below
SECONDARY open item — dual-system auxiliary pumps (Table 4f)
Case 6 (231) = 356 = (230c) central-heating pump 156 + (230d) oil-boiler pump 200. Cascade gives 241. Two boilers → two pump contributions per Table 4f (note c: "where there are two main heating systems include two figures from this table" — same note already used for the 0240 oil-pump in S0380.148). Needs the per-system pump aggregation. Smaller than the interlock fix; do it after, then case 6's (231) pin closes and the full e2e fixture lands.
Process notes
- One slice = one commit, spec citation (page + line) in the message,
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>trailer. - AAA tests (
# Arrange/# Act/# Assert),abs(x-y) <= tol(notpytest.approx). - New code passes
pyrightstrict, 0 errors. (mapper.py + cert_to_inputs.py each carry 32 pre-existing errors — don't add to them; check with agit stashbaseline comparison.) - The Elmhurst worksheet is ground truth at abs=1e-4. 0240 is API-only (±0.5 fallback) — case 6 is its worksheet-backed proxy for the heating archetype, but differs from 0240 on the boiler SAP code (127 vs 0240's 130 condensing combi), so pin case 6 to ITS OWN worksheet, not 0240's register.
- Suite command + section/e2e harness layout: AGENT_GUIDE §2.6 + §4.