From 419e340477e15d37531b74519e63fd43cbe0ab34 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 16 Jun 2026 08:26:05 +0000 Subject: [PATCH] test(worksheet): pin simulated case 43 at 1e-4 (RR + dry-line + mixed roof) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Golden regression fixture for the multi-feature dwelling that surfaced the two Elmhurst-extractor bugs in a33707f8. case 43 is a 2-BP mid-terrace with a DETAILED room-in-roof (two slopes, two flat ceilings, party + exposed gables, two common walls), a MIXED-insulation multi-section roof (Main insulated + Extension uninsulated 2.30), a DRY-LINED extension solid wall, a mains-gas boiler (102 / control 2106) and a House-coal solid-fuel secondary (633). Routes the Summary PDF through the WHOLE extractor + mapper + calculator pipeline (no hand-built EpcPropertyData) and pins the §3 fabric + SAP-rating block at abs=1e-4: (29a) walls 74.5800, (30) roof 38.5008, (33) fabric 172.7844, continuous SAP 73.2332 = (258), CO2 3518.3037 = (272). Guards the detailed-RR slope/common_wall surfaces, the dry-lining R=0.17 adjustment, and the per-part mixed-roof billing together. Summary mirrored to backend/documents_parser/tests/fixtures/Summary_001431_case43.pdf; provider module mirrors the _case6/_case21 pattern, assertion in test_section_cascade_pins. Harness 47/47; regression = the 3 pre-existing fails; pyright net-zero. Co-Authored-By: Claude Opus 4.8 --- .../tests/fixtures/Summary_001431_case43.pdf | Bin 0 -> 79920 bytes .../_elmhurst_worksheet_001431_case43.py | 116 ++++++++++++++++++ .../worksheet/test_section_cascade_pins.py | 42 +++++++ 3 files changed, 158 insertions(+) create mode 100644 backend/documents_parser/tests/fixtures/Summary_001431_case43.pdf create mode 100644 tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case43.py diff --git a/backend/documents_parser/tests/fixtures/Summary_001431_case43.pdf b/backend/documents_parser/tests/fixtures/Summary_001431_case43.pdf new file mode 100644 index 0000000000000000000000000000000000000000..080fe6184738e22fb3313d4c366d455e79c84fca GIT binary patch literal 79920 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 literal 0 HcmV?d00001 diff --git a/tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case43.py b/tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case43.py new file mode 100644 index 00000000..2e4a7ae3 --- /dev/null +++ b/tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case43.py @@ -0,0 +1,116 @@ +"""Mapper-driven cascade pin against the Elmhurst P960-0001-001431 +"simulated case 43" worksheet — a 2-storey mid-terrace deliberately built to +exercise every feature in one dwelling: + + - a DETAILED room-in-roof on the Main BP (two slopes, two flat ceilings, + a party + an exposed gable, two common walls) — exercises the + slope / stud / common_wall detailed-RR surfaces end-to-end; + - a MIXED-insulation multi-section roof (Main insulated 0.16/0.54/0.68/0.11 + + Extension uninsulated 2.30); + - a DRY-LINED extension solid wall (RdSAP 10 §5.8 Table 14 R=0.17: + solid brick 1.70 -> 1.32); + - a mains-gas boiler (SAP 102, control 2106 interlock) with a House-coal + solid-fuel SECONDARY (633, 60%) and a 210 L declared-loss cylinder. + +This case was generated to settle the room-in-roof + mixed-roof + secondary +feature set with a single 1e-4 pin. It exposed two compensating Elmhurst- +extractor bugs (commit `a33707f8`) whose fabric errors nearly cancelled +(walls net -0.76 W/K, hidden behind a +0.05 SAP delta): + 1. the main/extension wall "Dry-lining: Yes" line was read only for + ALTERNATIVE walls -> the dry-lined extension wall billed at the + un-adjusted 1.70 instead of 1.32; + 2. the LAST room-in-roof surface row's per-row token scan over-read into + the next section -> Common Wall 2's default U silently zeroed + (1.90 -> 0.00). +With both fixed the whole §3 fabric and the SAP/CO2 reproduce EXACTLY. + +Like 000565 / the _rr cases / case 20 / 21 / 38 / 39, this fixture does NOT +hand-build the EpcPropertyData: it routes the Summary PDF through +ElmhurstSiteNotesExtractor + from_elmhurst_site_notes so the pin exercises +the WHOLE extractor + mapper + calculator pipeline. + +Source: user-simulated PDFs at `sap worksheets/golden fixture debugging/ +simulated case 43/`. The Summary is mirrored into the tracked +`backend/documents_parser/tests/fixtures/Summary_001431_case43.pdf` so the +test runs without depending on the unstaged workspace. + +Worksheet pin targets (P960-0001-001431, "11a. SAP rating" / "12a. CO2 +emissions" block — the UK-average-climate rating block our cascade +reproduces): +- SAP value (un-rounded, before (258) integer rounding) = 73.2332 (band C) +- (272) Total CO2, kg/year = 3518.30 + +Per [[feedback-zero-error-strict]] + [[feedback-continuous-sap-tolerance]]: +pins are abs <= 1e-3 against the worksheet PDF (printed to 4 dp). +""" + +from __future__ import annotations + +import re +import subprocess +from pathlib import Path +from typing import Final + +from backend.documents_parser.elmhurst_extractor import ElmhurstSiteNotesExtractor +from datatypes.epc.domain.epc_property_data import EpcPropertyData +from datatypes.epc.domain.mapper import EpcPropertyDataMapper + +# parents[0]=worksheet/, [1]=sap10_calculator/, [2]=domain/, [3]=tests/, +# [4]=repo root. +_SUMMARY_PDF: Final[Path] = ( + Path(__file__).resolve().parents[4] + / "backend" / "documents_parser" / "tests" / "fixtures" + / "Summary_001431_case43.pdf" +) + +LINE_29A_WALLS_W_PER_K: Final[float] = 74.5800 +# (30) = ΣA×U: FlatCeil1 4.3200 + FlatCeil2 6.9000 + Slope1 1.0200 + +# Slope2 0.1408 + roof Main 3.1200 + roof Ext1 (uninsulated) 23.0000. +LINE_30_ROOF_W_PER_K: Final[float] = 38.5008 +LINE_33_FABRIC_W_PER_K: Final[float] = 172.7844 +LINE_258_SAP_VALUE_CONTINUOUS: Final[float] = 73.2332 +LINE_272_TOTAL_CO2_KG_PER_YR: Final[float] = 3518.3037 + + +def _summary_pdf_to_textract_style_pages(pdf_path: Path) -> list[str]: + """Convert a Summary PDF into the per-page text format the + ElmhurstSiteNotesExtractor expects (label/value token sequences). + Mirror of the helper in the other `_elmhurst_worksheet_*` fixtures. + """ + info = subprocess.run( + ["pdfinfo", str(pdf_path)], capture_output=True, text=True, check=True, + ).stdout + m = re.search(r"Pages:\s+(\d+)", info) + if m is None: + raise RuntimeError(f"Could not parse page count from {pdf_path}") + page_count = int(m.group(1)) + pages: list[str] = [] + for i in range(1, page_count + 1): + layout = subprocess.run( + [ + "pdftotext", "-layout", "-f", str(i), "-l", str(i), + str(pdf_path), "-", + ], + capture_output=True, text=True, check=True, + ).stdout + tokens: list[str] = [] + for line in layout.splitlines(): + if not line.strip(): + tokens.append("") + continue + parts = [p for p in re.split(r"\s{2,}", line.strip()) if p] + tokens.extend(parts) + pages.append("\n".join(tokens)) + return pages + + +def build_epc() -> EpcPropertyData: + """Route the simulated case-43 Summary through extractor + mapper. + No hand-built EpcPropertyData — the extractor and mapper are part of + the test target. This module is a pin PROVIDER (build_epc + LINE_* + constants, mirroring `_elmhurst_worksheet_001431_case6` / `_case21`); + the collected assertion lives in + `test_section_cascade_pins.test_case43_*`.""" + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_PDF) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + return EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) diff --git a/tests/domain/sap10_calculator/worksheet/test_section_cascade_pins.py b/tests/domain/sap10_calculator/worksheet/test_section_cascade_pins.py index b8f166ab..4e7336e3 100644 --- a/tests/domain/sap10_calculator/worksheet/test_section_cascade_pins.py +++ b/tests/domain/sap10_calculator/worksheet/test_section_cascade_pins.py @@ -44,6 +44,7 @@ from tests.domain.sap10_calculator.worksheet import ( _elmhurst_worksheet_000516 as _w000516, _elmhurst_worksheet_001431_case6 as _w001431_case6, _elmhurst_worksheet_001431_case21 as _w001431_case21, + _elmhurst_worksheet_001431_case43 as _w001431_case43, ) @@ -328,6 +329,47 @@ def test_section_3_wall_u_by_thickness_case21_match_pdf() -> None: ) +def test_case43_detailed_rr_dryline_and_mixed_roof_match_pdf() -> None: + """Full-feature pin for simulated case 43 — a 2-BP mid-terrace with a + DETAILED room-in-roof (slopes + flat ceilings + party/exposed gables + + common walls), a MIXED-insulation multi-section roof (Main insulated + + Extension uninsulated), a DRY-LINED extension solid wall (RdSAP 10 §5.8 + Table 14: 1.70 -> 1.32), a mains-gas boiler (102, control 2106) and a + House-coal solid-fuel secondary (633). Exposed + regression-guards two + compensating Elmhurst-extractor bugs (commit a33707f8): the unread + main-wall dry-lining and the last-RR-row default-U over-read, whose + fabric errors nearly cancelled (walls net -0.76). With both fixed the + §3 fabric and the SAP-rating block reproduce the P960 exactly.""" + # Arrange + from domain.sap10_calculator.calculator import calculate_sap_from_inputs + + epc = _w001431_case43.build_epc() + + # Act + ht = heat_transmission_section_from_cert(epc) + result = calculate_sap_from_inputs(cert_to_inputs(epc)) + + # Assert — §3 fabric (the RR + dry-lining + mixed-roof fixes) and the + # SAP-rating block, each at abs=1e-4. + _pin(ht.walls_w_per_k, _w001431_case43.LINE_29A_WALLS_W_PER_K, "§3 (29a) case43") + _pin(ht.roof_w_per_k, _w001431_case43.LINE_30_ROOF_W_PER_K, "§3 (30) case43") + _pin( + ht.fabric_heat_loss_w_per_k, + _w001431_case43.LINE_33_FABRIC_W_PER_K, + "§3 (33) case43", + ) + _pin( + result.sap_score_continuous, + _w001431_case43.LINE_258_SAP_VALUE_CONTINUOUS, + "(258) case43", + ) + _pin( + result.co2_kg_per_yr, + _w001431_case43.LINE_272_TOTAL_CO2_KG_PER_YR, + "(272) case43", + ) + + def test_case6_main_2_emitter_and_control_extracted() -> None: """Simulated case 6's §14.1 Main Heating2 lodges its OWN emitter ("Underfloor Heating") and control ("SAP code 2110, ...") — the two