Slice S0380.119: propagate sap_roof_windows in _build_section_5_epc

The §5 test EPC builder threaded sap_windows from the fixture but
discarded `sap_roof_windows` — passing them through `make_minimal_sap10
_epc(...)`. Pre-S0380.110 the `_daylight_factor_from_cert` cascade
read a single aggregate `rooflight_total_area_m2` kwarg + bulk g_L,
so the test EPC builder's omission was masked. Post-S0380.110 the
cascade reads per-rooflight glazing via `epc.sap_roof_windows`
(Appendix L §L2a per-window g_L sum) — Triple / Double / Single
distinctions matter.

For cohort 000516 (the only cohort fixture with a lodged rooflight,
a Double-glazed 1.18 m² × g_L=0.80 × FF=0.70 × Z_L=1.0), the empty
sap_roof_windows on the test EPC undercut the daylight factor →
cascade lighting (67) Jan 33.78 W vs ws 32.68 W (+1.1 W/month) →
lighting_kwh_per_yr 238.65 vs ws 230.88 (+7.77 kWh/yr).

Fix: thread `fixture.build_epc().sap_roof_windows` through the
minimal EPC. Cohorts 000474/477/480/487/490 have no rooflights →
list is None → cascade unchanged for those certs.

Test movement: 000516 (67) Jan 33.78 → 32.68 ✓ EXACT. 000516
lighting_kwh_per_yr 238.65 → 230.88 ✓ EXACT.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-30 20:53:33 +00:00 committed by Jun-te Kim
parent 412525ae6f
commit a548f637e4

View file

@ -580,7 +580,15 @@ def _build_section_5_epc(fixture: ModuleType) -> EpcPropertyData:
doesn't yet carry: sap_windows (DG air-filled / PVC), low-energy bulb
count, and a MainHeatingDetail with the recorded pump age. Kept in
test scope so the legacy fixture build_epc()s stay pinned for §1-§4
conformance + the e2e SAP-score regression."""
conformance + the e2e SAP-score regression.
Per S0380.110 the §5 lighting cascade reads per-rooflight glazing
via `epc.sap_roof_windows` (Appendix L §L2a per-window g_L) rather
than a single aggregate area + bulk g_L. Propagate the fixture's
lodged rooflights so `_daylight_factor_from_cert` sees Triple /
Double / Single distinctions for the cohort (e.g. 000516 lodges a
Double-glazed rooflight at 1.18 × g_L=0.80 × FF=0.70 × Z_L=1.0).
"""
def _window(area: float) -> SapWindow:
side = area ** 0.5
return SapWindow(
@ -610,10 +618,12 @@ def _build_section_5_epc(fixture: ModuleType) -> EpcPropertyData:
],
has_fixed_air_conditioning=False,
)
fixture_epc = fixture.build_epc()
return make_minimal_sap10_epc(
total_floor_area_m2=fixture.LINE_4_TFA_M2,
low_energy_fixed_lighting_bulbs_count=fixture.SECTION_5_BULB_COUNT_LEL,
sap_windows=[_window(a) for a in fixture.SECTION_5_WINDOW_AREAS_M2],
sap_roof_windows=list(fixture_epc.sap_roof_windows) if fixture_epc.sap_roof_windows else None,
sap_heating=sap_heating,
)