From 4fdc23f83d6587b51b76ca91d3b02927659408a4 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 15 Jun 2026 13:31:36 +0000 Subject: [PATCH] =?UTF-8?q?test(worksheet):=20pin=20simulated=20case=2038?= =?UTF-8?q?=20=E2=80=94=20mains-gas=20secondary=20reproduces=20worksheet?= =?UTF-8?q?=20exactly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The realistic re-generation of case 37 (code-117 gas boiler, control 2102, + a MAINS-GAS condensing gas-fire secondary code 611, vs case 37's biogas 605). The full extractor -> mapper -> calculator pipeline reproduces the worksheet's SAP-rating block EXACTLY: continuous SAP 60.9152 (Δ 2e-5) and (272) CO2 5801.0770 (Δ ~0). This confirms the boiler-efficiency / control-2102 −5pp interlock / secondary-fuel handling are all correct, and that case 37's +7 gap was purely the biogas sub-fuel the Summary export cannot carry. Summary mirrored into backend/documents_parser/tests/fixtures so the pin runs without the unstaged workspace. PE not pinned — it is a separate DPER block (different scope) already guarded by the corpus PE gauge. Worksheet harness 47/47 unchanged; pyright net-zero. Co-Authored-By: Claude Opus 4.8 --- .../tests/fixtures/Summary_001431_case38.pdf | Bin 0 -> 84371 bytes .../_elmhurst_worksheet_001431_case38.py | 116 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 backend/documents_parser/tests/fixtures/Summary_001431_case38.pdf create mode 100644 tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case38.py diff --git a/backend/documents_parser/tests/fixtures/Summary_001431_case38.pdf b/backend/documents_parser/tests/fixtures/Summary_001431_case38.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7b4cbcae212a62d2ab0aed4325f980fdeb81e0a7 GIT binary patch literal 84371 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 Qb{GWZ>=UrDNh{0z589=g!~g&Q literal 0 HcmV?d00001 diff --git a/tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case38.py b/tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case38.py new file mode 100644 index 00000000..f96b17ce --- /dev/null +++ b/tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case38.py @@ -0,0 +1,116 @@ +"""Mapper-driven cascade pin against the Elmhurst P960-0001-001431 +"simulated case 38" worksheet — a mains-gas dwelling with a code-117 +regular boiler (1979-1997, winter 66%), control 2102 (programmer, no room +thermostat → −5pp interlock → (206)=61%), and a **mains-gas condensing gas +fire secondary** (SAP code 611). + +This is the realistic re-generation of "simulated case 37": case 37 lodged +the same dwelling's code-605 gas fire on BIOGAS (7.60 p/kWh), which the +Elmhurst Summary export cannot carry (it lodges only the secondary SAP +code, not its sub-fuel — see `_elmhurst_secondary_fuel_from_sap_code`), so +the mains-gas modal default left a +7 SAP gap that was purely the biogas +sub-fuel. With a mains-gas secondary the whole cascade reproduces the +worksheet EXACTLY, confirming the boiler-efficiency / control-2102 / +secondary handling is all correct. + +Like 000565 / the _rr cases / case 20 / 21, 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 38/`. The Summary is mirrored into the tracked +`backend/documents_parser/tests/fixtures/Summary_001431_case38.pdf` so the +test runs without depending on the unstaged workspace. + +Worksheet pin targets (P960-0001-001431, "11a. SAP rating" block): +- SAP value (un-rounded, before (258) integer rounding) = 60.9152 +- (272) Total CO2, kg/year = 5801.0770 + +Per [[feedback-zero-error-strict]] + [[feedback-continuous-sap-tolerance]]: +pins are abs <= 1e-3 against the worksheet PDF (the worksheet prints the +SAP value 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 +from domain.sap10_calculator.calculator import calculate_sap_from_inputs +from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs + +# 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_case38.pdf" +) + +LINE_258_SAP_VALUE_CONTINUOUS: Final[float] = 60.9152 +LINE_272_TOTAL_CO2_KG_PER_YR: Final[float] = 5801.0770 +_PIN_ABS: Final[float] = 1e-3 + + +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-38 Summary through extractor + mapper. + No hand-built EpcPropertyData — the extractor and mapper are part of + the test target.""" + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_PDF) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + return EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) + + +def test_case38_mains_gas_secondary_reproduces_the_worksheet_sap_and_co2() -> None: + # Arrange — the full extractor -> mapper -> calculator pipeline on the + # simulated case-38 Summary (mains-gas boiler 117 + mains-gas + # condensing gas-fire secondary 611). + epc = build_epc() + + # Act + result = calculate_sap_from_inputs(cert_to_inputs(epc)) + + # Assert — the SAP-rating block reproduces the worksheet exactly. + assert ( + abs(result.sap_score_continuous - LINE_258_SAP_VALUE_CONTINUOUS) + <= _PIN_ABS + ) + assert abs(result.co2_kg_per_yr - LINE_272_TOTAL_CO2_KG_PER_YR) <= _PIN_ABS