From a77f1a284dcab9bf9ae6559f0ba4b9c49105be01 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sat, 30 May 2026 20:53:33 +0000 Subject: [PATCH] Slice S0380.119: propagate sap_roof_windows in _build_section_5_epc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../worksheet/tests/test_internal_gains.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/domain/sap10_calculator/worksheet/tests/test_internal_gains.py b/domain/sap10_calculator/worksheet/tests/test_internal_gains.py index 11298313..55bdb058 100644 --- a/domain/sap10_calculator/worksheet/tests/test_internal_gains.py +++ b/domain/sap10_calculator/worksheet/tests/test_internal_gains.py @@ -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 m² × 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, )