From c14723307234d0c6cca39a85c9b7315826da3377 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 22 May 2026 23:07:15 +0000 Subject: [PATCH] =?UTF-8?q?Slice=2021b:=20=C2=A71=20cascade=20pins=20(TFA,?= =?UTF-8?q?=20Volume)=20=E2=80=94=2012/12=20at=20abs=3D1e-4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New file `test_section_cascade_pins.py` for per-section line-ref pins against the U985 PDF. Tests walk the actual cert→inputs cascade (not the per-section isolation tests in test_dimensions.py etc.) and assert the produced value matches the PDF line ref to abs=1e-4 for every fixture. §1 pins: (4) total_floor_area_m2 → dimensions_from_cert(epc).total_floor_area_m2 (5) volume_m3 → dimensions_from_cert(epc).volume_m3 12/12 cases pass (6 fixtures × 2 line refs). Section 1 is closed. Bottom-up plan: §1 → §2 → §3 → §4 → §5 → §6 → §7 → §8 → §9a → §10a → §11a → §12. When upstream sections close at <1e-4, downstream residuals shrink mechanically — a failing §3 pin is more legible than a sapResult.total_fuel_cost_gbp failure that could come from anywhere upstream. Co-Authored-By: Claude Opus 4.7 --- .../tests/test_section_cascade_pins.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py diff --git a/packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py b/packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py new file mode 100644 index 00000000..86fef5d5 --- /dev/null +++ b/packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py @@ -0,0 +1,90 @@ +"""Section-by-section cascade pins against U985 PDF line refs. + +Each pin walks the actual cert→inputs cascade (not the per-section +isolation tests) and asserts the produced value matches the worksheet +PDF line ref to abs=1e-4 for every fixture in the cohort. Tests run in +worksheet order (§1, §2, §3, ..., §12) — when a pin fails the residual +localises by section. Bottom-up discipline: a failing §3 pin gets +fixed before §10a is touched. + +Per `[[feedback-e2e-validation-philosophy]]`: tolerances are NOT +widened to mask drift. A failing pin is a named calculator bug. + +Reference: SAP 10.2 specification (14-03-2025). +""" +from typing import Final + +import pytest + +from domain.sap.rdsap.cert_to_inputs import cert_to_inputs +from domain.sap.worksheet.dimensions import dimensions_from_cert +from domain.sap.worksheet.tests import ( + _elmhurst_worksheet_000474 as _w000474, + _elmhurst_worksheet_000477 as _w000477, + _elmhurst_worksheet_000480 as _w000480, + _elmhurst_worksheet_000487 as _w000487, + _elmhurst_worksheet_000490 as _w000490, + _elmhurst_worksheet_000516 as _w000516, +) + + +_FIXTURES: Final[dict[str, object]] = { + "000474": _w000474, + "000477": _w000477, + "000480": _w000480, + "000487": _w000487, + "000490": _w000490, + "000516": _w000516, +} + +_FLOAT_PIN_ABS: Final[float] = 1e-4 + + +def _pin(actual: float, expected: float, tag: str) -> None: + """Assert `actual` matches `expected` to abs=1e-4. The PDF lodges + every worksheet line ref to 4 d.p.; anything looser is drift.""" + diff = abs(actual - expected) + assert diff < _FLOAT_PIN_ABS, ( + f"{tag}: actual={actual}, expected={expected}, diff={diff:.6f}" + ) + + +# ============================================================================ +# §1 Overall dwelling dimensions — LINE_4 TFA, LINE_5 Volume +# ============================================================================ + + +@pytest.mark.parametrize("fixture_name", list(_FIXTURES), ids=lambda x: x) +def test_section_1_line_4_total_floor_area_matches_pdf(fixture_name: str) -> None: + """§1 (4) — total floor area Σ across heated storeys.""" + # Arrange + mod = _FIXTURES[fixture_name] + epc = mod.build_epc() # type: ignore[attr-defined] + + # Act + dim = dimensions_from_cert(epc) + + # Assert + _pin( + dim.total_floor_area_m2, + mod.LINE_4_TFA_M2, # type: ignore[attr-defined] + f"§1 (4) {fixture_name}", + ) + + +@pytest.mark.parametrize("fixture_name", list(_FIXTURES), ids=lambda x: x) +def test_section_1_line_5_volume_matches_pdf(fixture_name: str) -> None: + """§1 (5) — dwelling internal volume Σ across storeys.""" + # Arrange + mod = _FIXTURES[fixture_name] + epc = mod.build_epc() # type: ignore[attr-defined] + + # Act + dim = dimensions_from_cert(epc) + + # Assert + _pin( + dim.volume_m3, + mod.LINE_5_VOLUME_M3, # type: ignore[attr-defined] + f"§1 (5) {fixture_name}", + )