Slice 21b: §1 cascade pins (TFA, Volume) — 12/12 at abs=1e-4

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 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-22 23:07:15 +00:00
parent 20424a2dca
commit c147233072

View file

@ -0,0 +1,90 @@
"""Section-by-section cascade pins against U985 PDF line refs.
Each pin walks the actual certinputs 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}",
)