From 5c11fd35c8cc645ab5d8b38b3bd43ac30da1f847 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 15 Jun 2026 15:26:11 +0000 Subject: [PATCH] Validate SAP calculator vs Elmhurst; fix reduced-field window U; add accuracy harness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reduced-field window U: heat_transmission derived the synthesised-window raw U from u_window(all None) -> the 2.5 placeholder regardless of glazing. Now routes the (uniform) glazing_type code through u_window (RdSAP Table 24) so e.g. double pre-2002 reads 2.8, not 2.5. Only the pre-SAP10 reduced-field path is affected (21.0.1 certs carry per-window U upstream) — the RdSAP-21.0.1 corpus gauge is unchanged at 66.9% within-0.5. test_real_cert_sap_accuracy: pin uprn_10002468137 (RdSAP-17.1, all-electric storage heaters) at SAP 61, validated against Elmhurst on identical inputs (dual off-peak immersion, 110 L cylinder, 2 baths). Our engine reproduces Elmhurst's fuel cost to the penny; lodged 55 is the old SAP-2012 schema. Tooling to grow the accuracy corpus: - scripts/fetch_real_life_epc_sample.py — capture a cert by UPRN into the corpus. - scripts/compare_epc_paths.py — diff gov-API vs Elmhurst-summary EpcPropertyData and run both through the engine, localising mapper vs calculator differences. - skill validate-cert-sap-accuracy — the end-to-end loop (capture -> Elmhurst inputs -> human builds -> compare -> reconcile -> pin in the test). - skill epc-to-elmhurst-rdsap-inputs reference: corrected immersion (code 1=dual), cylinder size (code 2 = Normal/110 L), and bath-count (WWHRS sub-tab) mappings. Co-Authored-By: Claude Opus 4.8 --- .../reference/mapping.md | 5 +- .../validate-cert-sap-accuracy/SKILL.md | 89 ++++++++++++ .../uprn_10002468137/elmhurst_summary.pdf | Bin 0 -> 81755 bytes .../uprn_10002468137/elmhurst_worksheet.pdf | Bin 0 -> 47243 bytes .../worksheet/heat_transmission.py | 50 ++++++- scripts/compare_epc_paths.py | 135 ++++++++++++++++++ scripts/fetch_real_life_epc_sample.py | 130 +++++++++++++++++ .../test_real_cert_sap_accuracy.py | 26 ++-- 8 files changed, 415 insertions(+), 20 deletions(-) create mode 100644 .claude/skills/validate-cert-sap-accuracy/SKILL.md create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-17.1/uprn_10002468137/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-17.1/uprn_10002468137/elmhurst_worksheet.pdf create mode 100644 scripts/compare_epc_paths.py create mode 100644 scripts/fetch_real_life_epc_sample.py diff --git a/.claude/skills/epc-to-elmhurst-rdsap-inputs/reference/mapping.md b/.claude/skills/epc-to-elmhurst-rdsap-inputs/reference/mapping.md index 0a52c7d3..d3741489 100644 --- a/.claude/skills/epc-to-elmhurst-rdsap-inputs/reference/mapping.md +++ b/.claude/skills/epc-to-elmhurst-rdsap-inputs/reference/mapping.md @@ -179,13 +179,14 @@ UPRN 10002468137 — lodged 55, engine 62. | `water_heating_code` | 901 = From main heating system (Elmhurst "Boiler Circulator"); **903 = Electric immersion, off-peak → Elmhurst "Water Heater" category** (NOT Boiler Circulator) | | `water_heating_fuel` | as Fuel codes above (29 = off-peak) | | `has_hot_water_cylinder` | → "Hot Water Cylinder Present" | -| `cylinder_size` | band: 1=Small, 2=Medium, 3=Large | +| `cylinder_size` | **code 2 = Normal / 110 L, code 3 = Medium / 160 L, code 4 = Large / 210 L** (RdSAP 10 §10.5 Table 28; source: `cert_to_inputs.py` `_CYLINDER_SIZE_CODE_TO_LITRES`). In Elmhurst pick the **litre value**, NOT the label — "Normal" = 110 L. | | `cylinder_insulation_type` | **1 = factory Foam, 2 = loose Jacket** (source: `cert_to_inputs.py` `_CYLINDER_INSULATION_TYPE_LOOSE_JACKET = 2`) | | `cylinder_insulation_thickness` | mm (38 mm ≈ factory foam; jackets 80 mm+) | -| `immersion_heating_type` | 1 = single | +| `immersion_heating_type` | **code 1 = DUAL, code 2 = SINGLE** (source: `cert_to_inputs.py` ~L5288, per RdSAP 10 §10.5 "assume dual on a dual/off-peak meter" + the API cohort). ⚠️ Do NOT read 1 as "single" — single vs dual flips the Table 13 high-rate fraction and can swing the SAP score several points (e.g. cert 10002468137: dual 0.131 → SAP 61, single 0.571 → SAP 57). Storage-heater / off-peak certs are almost always code 1 = dual. | - **Community Hot Water**: 0 unless lodged. - **Solar Water Heating**: `solar_water_heating` Y/N. +- **Number of baths** (Elmhurst tab: **Water Heating → WWHRS sub-tab → "Total no. of Baths"**, NOT the main Water Heating sub-tab): the gov-API derives it from `sap_heating.instantaneous_wwhrs` ROOM counts — `number_baths = rooms_with_bath_and_or_shower + rooms_with_bath_and_mixer_shower`. ⚠️ Elmhurst defaults this to 0; set it to the derived count or the gov-API and Elmhurst hot-water demand diverge (e.g. cert 10002468137: 2 baths = +165 kWh HW ≈ +£11 ≈ +0.7 SAP). Keep WWHRS itself **No**. - **WWHRS**: ⚠️ `sap_heating.instantaneous_wwhrs` holds **bath/shower ROOM counts** (ADR-0028: `rooms_with_bath_and_or_shower`, `rooms_with_mixer_shower_no_bath`, `rooms_with_bath_and_mixer_shower`) — it is **NOT** a heat-recovery device. diff --git a/.claude/skills/validate-cert-sap-accuracy/SKILL.md b/.claude/skills/validate-cert-sap-accuracy/SKILL.md new file mode 100644 index 00000000..be3a6283 --- /dev/null +++ b/.claude/skills/validate-cert-sap-accuracy/SKILL.md @@ -0,0 +1,89 @@ +--- +name: validate-cert-sap-accuracy +description: Run the end-to-end loop that validates this repo's SAP calculator against accredited Elmhurst Energy for one real EPC certificate, then locks the result into the regression test corpus. Capture a cert by UPRN → generate Elmhurst inputs → (human builds it in Elmhurst) → diff the gov-API vs Elmhurst EpcPropertyData and run both through our engine → reconcile to convergence → pin the agreed SAP score in the accuracy test. Use when validating/expanding SAP-calculator accuracy against Elmhurst, adding a cert to the accuracy corpus, or when the user wants to "check a cert against Elmhurst" / "add another accuracy test". +--- + +# Validate cert SAP accuracy (gov-API ↔ Elmhurst) + +Separates **calculator** correctness from **mapper** fidelity by computing the +same property two ways and reconciling them, then freezes the agreed score as +a regression pin. Files land in the corpus location so the suite grows. + +Sample home for every cert: `backend/epc_api/json_samples/real_life_examples//uprn_/` +(`epc.json`, `elmhurst_inputs.md`, `elmhurst_summary.pdf`, `elmhurst_worksheet.pdf`). + +## Workflow + +1. **Capture the cert** (gov-EPC API → saved json + our engine's score): + ``` + PYTHONPATH=/workspaces/model python scripts/fetch_real_life_epc_sample.py + ``` + Writes `real_life_examples//uprn_/epc.json` and prints schema, + lodged rating, and our engine's SAP + per-end-use kWh. Note the schema: + only RdSAP schemas map today (full SAP `SAP-Schema-*` is partial). + +2. **Generate the Elmhurst input sheet** — invoke the **`epc-to-elmhurst-rdsap-inputs`** + skill on the UPRN. It writes `elmhurst_inputs.md` next to the json, page by + page, with the code→value mappings (cylinder, immersion, baths, glazing, …). + +3. **Human builds it in Elmhurst** from `elmhurst_inputs.md`, then exports the + **Summary PDF** and the **SAP-10.2 worksheet PDF**, saving them in the sample + dir as **`elmhurst_summary.pdf`** and **`elmhurst_worksheet.pdf`**. (This is + the only manual step — Elmhurst is the accredited ground truth.) + +4. **Compare the two paths**: + ``` + PYTHONPATH=/workspaces/model python scripts/compare_epc_paths.py + ``` + Builds `EpcPropertyData` from the gov-API json AND from the Elmhurst summary + (`parse_site_notes_pdf`), deep-diffs them, runs BOTH through `Sap10Calculator`, + and prints Elmhurst's own worksheet SAP (258). Reading it: + - **Our engine on Elmhurst inputs ≈ Elmhurst's worksheet SAP** → calculator is + correct (it reproduces accredited Elmhurst on identical inputs). + - **gov-API SAP vs Elmhurst-PDF SAP gap** → input differences only. The field + diff localises them. + +5. **Reconcile to convergence.** Triage each field diff (use the + `epc-to-elmhurst-rdsap-inputs` skill's `reference/mapping.md` for code + semantics — cylinder code 2=110 L, immersion code 1=dual, baths on the WWHRS + sub-tab, etc.): + - **Elmhurst data-entry error** (e.g. swapped floor dims, wrong cylinder/ + immersion, missing baths, wrong postcode/region) → fix in Elmhurst, re-export, + re-run step 4. + - **gov-API mapper gap** (e.g. lodged alt-wall dropped) → a real per-cert-mapper + fix; flag it (Khalim's domain) — don't tune to mask it. + - **Genuine ground-truth question** (what the property *actually* is) → the + assessor/user settles it; align both sides to the lodged data. + Target: gov-API and a correctly-built Elmhurst within ~0.5 SAP. Cosmetic / + representation diffs (codes vs strings, empty `EnergyElement` lists) are noise. + +6. **Lock it in.** Once converged on a value you trust, add a case to + `tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py`: + ```python + RealCertExpectation( + schema="", sample="uprn_", + cert_num="", sap_score=, + ) + ``` + with a comment recording the ground truth + what reconciled it. If a known + engine bug still blocks it, use `known_bug_xfail="…"` (strict xfail) instead + of widening. Run `pytest tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py` + — it must pass (or xfail with the documented reason). + +## Notes + +- The sample dir IS the corpus entry — capturing + saving the PDFs there is all + the "expand the tests" bookkeeping needed; step 6 is what activates it. +- `sap_score` pins the gov-API engine's integer SAP (the production path). Add + per-end-use kWh pins to the same `RealCertExpectation` later (worksheet- + validated) to tighten coverage. +- Don't tune the mapper to a single cert — pin the observed value and fix mapper + gaps generically, guarded by the RdSAP-21.0.1 corpus gauge + (`tests/infrastructure/epc_client/test_sap_accuracy_corpus.py`). + +## Worked example + +UPRN **10002468137** (`RdSAP-Schema-17.1`): gov-API 60.92, Elmhurst 61 — converged +after aligning dual immersion, 110 L cylinder, and 2 baths. Pinned `sap_score=61`. +The journey closed an off-peak-water-heating bug (Table 13) and a reduced-field +window-U bug; the calculator matched Elmhurst's cost to the penny throughout. diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-17.1/uprn_10002468137/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-17.1/uprn_10002468137/elmhurst_summary.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5fad6ad27f3d56bf56907ec26d3c966654c999b0 GIT binary patch literal 81755 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 literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-17.1/uprn_10002468137/elmhurst_worksheet.pdf b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-17.1/uprn_10002468137/elmhurst_worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fc63d5c85795935e66e755cb310f13d1e0d0c7e6 GIT binary patch literal 47243 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 literal 0 HcmV?d00001 diff --git a/domain/sap10_calculator/worksheet/heat_transmission.py b/domain/sap10_calculator/worksheet/heat_transmission.py index ebdb2b52..9a34309b 100644 --- a/domain/sap10_calculator/worksheet/heat_transmission.py +++ b/domain/sap10_calculator/worksheet/heat_transmission.py @@ -41,7 +41,7 @@ from __future__ import annotations from dataclasses import dataclass from decimal import ROUND_HALF_UP, Decimal -from typing import Any, Final, Optional +from typing import Any, Final, Optional, Sequence, Tuple from datatypes.epc.domain.epc_property_data import ( EpcPropertyData, @@ -126,6 +126,48 @@ _DEFAULT_STOREY_HEIGHT_M: Final[float] = 2.5 # SAP10.2 §3.2 curtain/blind thermal resistance applied to windows (and # roof windows) — turns raw window U into the worksheet's (27) effective U. _WINDOW_CURTAIN_RESISTANCE_M2K_PER_W: Final[float] = 0.04 + +# SAP10 glazing-type code (the cascade enum used on `SapWindow.glazing_type`, +# see solar_gains `_G_PERPENDICULAR_BY_GLAZING_TYPE`) → the `u_window` glazing +# category + the install-year band the code implies. Used to derive the raw +# window U for SYNTHESISED (reduced-field) windows that carry no per-window +# U lodgement — previously these all fell to `u_window`'s all-None placeholder +# (2.5), regardless of glazing, under-counting window heat loss vs RdSAP Table +# 24 (e.g. double pre-2002 should be 2.8, not 2.5). +_GLAZING_CODE_TO_UWINDOW: Final[dict[int, Tuple[str, Optional[int]]]] = { + 1: ("single", None), + 2: ("double", 2002), # double 2002-2022 + 3: ("double", None), # double pre-2002 (None → pre-2002 row) + 4: ("double", None), # double low-E soft-coat + 5: ("secondary", None), + 6: ("triple", None), # triple pre-2002 default + 7: ("double", None), # double, known data + 8: ("triple", None), # triple, known data + 9: ("triple", 2002), # triple 2002-2022 + 10: ("triple", None), # triple pre-2002 + 11: ("secondary", None), + 12: ("secondary", None), + 13: ("double", 2022), # double 2022+ + 14: ("triple", 2022), # triple 2022+ + 15: ("single", None), +} + + +def _synthesised_window_u_raw(windows: Optional[Sequence[SapWindow]]) -> float: + """Raw (pre-curtain) window U for reduced-field windows with no per-window + U lodgement. Derives glazing category + install-year band from the + (uniform) synthesised `glazing_type` code and routes through `u_window` + (RdSAP Table 24), rather than the all-None 2.5 placeholder.""" + if not windows: + return u_window(installed_year=None, glazing_type=None, frame_type=None) + w = windows[0] + code = w.glazing_type + glaze, year = ( + _GLAZING_CODE_TO_UWINDOW.get(code, ("double", None)) + if isinstance(code, int) + else ("double", None) + ) + return u_window(installed_year=year, glazing_type=glaze, frame_type=w.frame_material) # RdSAP10 §15 "Rounding of data" (p.66): "All element areas (gross) # including window areas and conservatory wall area: 2 d.p." plus # "U-values: 2 d.p.". This is the data-passed-to-SAP-calculator @@ -632,8 +674,10 @@ def heat_transmission_from_cert( ) windows_w_per_k_total += a_w * u_eff_w else: - window_u_raw = window_avg_u_value if (window_avg_u_value or 0) > 0 else u_window( - installed_year=None, glazing_type=None, frame_type=None + window_u_raw = ( + window_avg_u_value + if (window_avg_u_value or 0) > 0 + else _synthesised_window_u_raw(epc.sap_windows) ) window_u = ( 1.0 / (1.0 / window_u_raw + _WINDOW_CURTAIN_RESISTANCE_M2K_PER_W) diff --git a/scripts/compare_epc_paths.py b/scripts/compare_epc_paths.py new file mode 100644 index 00000000..7610ca82 --- /dev/null +++ b/scripts/compare_epc_paths.py @@ -0,0 +1,135 @@ +"""Compare the two EpcPropertyData source paths for one real cert, to +separate MAPPER fidelity from CALCULATOR correctness. + +For a cert captured under +``backend/epc_api/json_samples/real_life_examples//uprn_/`` +this: + 1. builds `EpcPropertyData` from the gov-EPC API json (`epc.json`), and + 2. builds `EpcPropertyData` from the Elmhurst summary PDF + (`elmhurst_summary.pdf`) via `parse_site_notes_pdf`, +then deep-diffs the two and runs BOTH through `Sap10Calculator`. Where the +two objects match, any SAP gap is the calculator; where they differ, it's +input mapping / data entry. If `elmhurst_worksheet.pdf` is present its +printed SAP rating (258) is shown as the ground truth. + +USAGE +----- + PYTHONPATH=/workspaces/model python scripts/compare_epc_paths.py + +Part of the `validate-cert-sap-accuracy` workflow — see that skill. +""" + +from __future__ import annotations + +import dataclasses +import json +import re +import sys +from pathlib import Path +from typing import Any, Optional +from unittest.mock import patch + +import httpx + +from backend.documents_parser.parser import parse_site_notes_pdf +from datatypes.epc.domain.epc_property_data import EpcPropertyData +from datatypes.epc.domain.mapper import EpcPropertyDataMapper +from domain.sap10_calculator.calculator import Sap10Calculator + +_ROOT = Path("backend/epc_api/json_samples/real_life_examples") + + +def _find_sample_dir(uprn: str) -> Path: + matches = list(_ROOT.glob(f"*/uprn_{uprn}")) + if not matches: + raise SystemExit( + f"no sample dir for UPRN {uprn} under {_ROOT} — capture it first " + f"with scripts/fetch_real_life_epc_sample.py {uprn}" + ) + return matches[0] + + +def _gov_api_epc(epc_json: Path) -> EpcPropertyData: + data = json.loads(epc_json.read_text()) + + def _mock(*_a: object, **_k: object) -> httpx.Response: + return httpx.Response( + 200, json={"data": data}, request=httpx.Request("GET", "x") + ) + + # Route the raw payload through the real mapper (httpx mocked, no network). + with patch("httpx.get", side_effect=_mock): + from infrastructure.epc_client.epc_client_service import EpcClientService + + return EpcClientService(auth_token="t").get_by_certificate_number("x") + + +def _elmhurst_printed_sap(worksheet_pdf: Path) -> Optional[int]: + if not worksheet_pdf.exists(): + return None + import fitz # pymupdf + + text = "\n".join(p.get_text() for p in fitz.open(str(worksheet_pdf))) + for line in text.splitlines(): + if "SAP rating" in line and "(258)" in line: + # value sits immediately before the "(258)" line ref + match = re.search(r"(\d+)\s*\(258\)", line) + if match: + return int(match.group(1)) + return None + + +def _deep_diff(a: Any, b: Any, prefix: str, out: list[str]) -> None: + if dataclasses.is_dataclass(a) and dataclasses.is_dataclass(b): + for f in dataclasses.fields(a): + _deep_diff(getattr(a, f.name), getattr(b, f.name), f"{prefix}.{f.name}", out) + elif isinstance(a, list) and isinstance(b, list): + if len(a) != len(b): + out.append(f" {prefix}: LEN {len(a)} vs {len(b)}") + for i, (x, y) in enumerate(zip(a, b)): + _deep_diff(x, y, f"{prefix}[{i}]", out) + elif a != b: + out.append(f" {prefix}: API={a!r} ELM={b!r}") + + +def compare(uprn: str) -> None: + sample = _find_sample_dir(uprn) + print(f"=== {sample} ===") + gov = _gov_api_epc(sample / "epc.json") + + summary = sample / "elmhurst_summary.pdf" + elm: Optional[EpcPropertyData] = None + if summary.exists(): + elm = parse_site_notes_pdf(str(summary)) + else: + print(" (no elmhurst_summary.pdf yet — gov-API side only)") + + rg = Sap10Calculator().calculate(gov) + print("\nOUR ENGINE:") + print( + f" gov-API inputs → SAP {rg.sap_score} ({rg.sap_score_continuous:.2f})" + f" HW {rg.hot_water_kwh_per_yr:.0f} kWh cost £{rg.total_fuel_cost_gbp:.2f}" + ) + if elm is not None: + re_ = Sap10Calculator().calculate(elm) + print( + f" Elmhurst-PDF inputs → SAP {re_.sap_score} ({re_.sap_score_continuous:.2f})" + f" HW {re_.hot_water_kwh_per_yr:.0f} kWh cost £{re_.total_fuel_cost_gbp:.2f}" + ) + printed = _elmhurst_printed_sap(sample / "elmhurst_worksheet.pdf") + if printed is not None: + print(f" Elmhurst's OWN engine (worksheet 258): {printed}") + diffs: list[str] = [] + _deep_diff(gov, elm, "epc", diffs) + print(f"\nFIELD DIFFS gov-API vs Elmhurst ({len(diffs)}):") + print("\n".join(diffs) if diffs else " (none — paths identical)") + + +def main() -> None: + if len(sys.argv) != 2: + raise SystemExit(__doc__) + compare(sys.argv[1]) + + +if __name__ == "__main__": + main() diff --git a/scripts/fetch_real_life_epc_sample.py b/scripts/fetch_real_life_epc_sample.py new file mode 100644 index 00000000..c33cd766 --- /dev/null +++ b/scripts/fetch_real_life_epc_sample.py @@ -0,0 +1,130 @@ +"""Capture a real EPC certificate by UPRN for the SAP accuracy test suite. + +Resolves a UPRN to its latest lodged certificate via the GOV.UK EPB +register, downloads the full ``data`` payload (the exact shape +``EpcPropertyDataMapper.from_api_response`` consumes), and freezes it +under the schema-bucketed sample tree the accuracy test reads: + + backend/epc_api/json_samples/real_life_examples//uprn_/epc.json + +It also prints the lodged SAP rating and what ``Sap10Calculator`` +currently produces, so a new case can be added to +``tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py`` with +the right ``schema`` / ``sap_score`` straight away. + +USAGE +----- + PYTHONPATH=/workspaces/model python scripts/fetch_real_life_epc_sample.py [ ...] + +Token is read from ``backend/.env`` (``OPEN_EPC_API_TOKEN``, falling +back to ``EPC_AUTH_TOKEN``). Re-running overwrites the sample. +""" + +from __future__ import annotations + +import json +import os +import pathlib +import sys +from typing import Any + +import httpx +from dotenv import load_dotenv + +_BASE = "https://api.get-energy-performance-data.communities.gov.uk" +_SAMPLES_ROOT = pathlib.Path( + "backend/epc_api/json_samples/real_life_examples" +) + + +def _headers() -> dict[str, str]: + load_dotenv("backend/.env") + token = os.environ.get("OPEN_EPC_API_TOKEN") or os.environ["EPC_AUTH_TOKEN"] + return {"Authorization": f"Bearer {token}", "Accept": "application/json"} + + +def _latest_cert_number(uprn: int, headers: dict[str, str]) -> str: + resp = httpx.get( + f"{_BASE}/api/domestic/search", + params={"uprn": uprn}, + headers=headers, + timeout=30.0, + ) + resp.raise_for_status() + rows: list[dict[str, Any]] = resp.json().get("data", []) + if not rows: + raise SystemExit(f"UPRN {uprn}: no certificates found") + latest = max(rows, key=lambda r: r["registrationDate"]) + return str(latest["certificateNumber"]) + + +def _fetch_cert_data(cert_num: str, headers: dict[str, str]) -> dict[str, Any]: + resp = httpx.get( + f"{_BASE}/api/certificate", + params={"certificate_number": cert_num}, + headers=headers, + timeout=30.0, + ) + resp.raise_for_status() + data: dict[str, Any] = resp.json()["data"] + return data + + +def _report(uprn: int, cert_num: str, data: dict[str, Any]) -> None: + """Print lodged rating + current calculator output for the captured cert.""" + from infrastructure.epc_client.epc_client_service import EpcClientService + from domain.sap10_calculator.calculator import Sap10Calculator + from unittest.mock import patch + + def _mock(*_a: object, **_k: object) -> httpx.Response: + return httpx.Response( + 200, json={"data": data}, request=httpx.Request("GET", "x") + ) + + print(f" schema_type : {data.get('schema_type')}") + print(f" lodged rating : {data.get('energy_rating_current')}") + + service = EpcClientService(auth_token="test-token") + try: + with patch("httpx.get", side_effect=_mock): + epc = service.get_by_certificate_number(cert_num) + except ValueError as exc: + # Full-SAP (vs RdSAP) certs aren't supported by the mapper, so the + # calculator front-end can't consume them. Captured for reference + # but NOT addable to the RdSAP accuracy suite. + print(f" NOT MAPPABLE : {exc}") + return + result = Sap10Calculator().calculate(epc) + + print(f" calc sap_score : {result.sap_score}") + print(f" space_heating_kwh : {result.space_heating_kwh_per_yr:.4f}") + print(f" main_heating_kwh : {result.main_heating_fuel_kwh_per_yr:.4f}") + print(f" hot_water_kwh : {result.hot_water_kwh_per_yr:.4f}") + print(f" co2_kg_per_yr : {result.co2_kg_per_yr:.4f}") + + +def capture(uprn: int) -> None: + headers = _headers() + cert_num = _latest_cert_number(uprn, headers) + data = _fetch_cert_data(cert_num, headers) + + schema_type = str(data.get("schema_type") or "unknown-schema") + out_dir = _SAMPLES_ROOT / schema_type / f"uprn_{uprn}" + out_dir.mkdir(parents=True, exist_ok=True) + out = out_dir / "epc.json" + out.write_text(json.dumps(data, indent=2)) + + print(f"UPRN {uprn} -> cert {cert_num}") + print(f" wrote : {out}") + _report(uprn, cert_num, data) + + +def main() -> None: + if len(sys.argv) < 2: + raise SystemExit(__doc__) + for arg in sys.argv[1:]: + capture(int(arg)) + + +if __name__ == "__main__": + main() diff --git a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py index 6d8b89ed..202ef725 100644 --- a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py +++ b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py @@ -107,25 +107,21 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( ), # UPRN 10002468137 → cert 0215-2818-7357-9703-2145. RdSAP-Schema-17.1, # all-electric high-heat-retention storage heaters on Economy 7, solid- - # brick uninsulated end-terrace. Ground truth is Elmhurst RdSAP10 = 60, - # reproduced on identical inputs (summary + full SAP 10.2 worksheet saved - # alongside: elmhurst_summary.pdf / elmhurst_worksheet.pdf). The engine - # produces 62 — a +2 over-rating localised to OFF-PEAK WATER HEATING: - # the worksheet (lines 243-246) prices the 7-hour off-peak immersion at a - # Table 13 split (19.36% @ 15.29p high + 80.64% @ 5.5p low), but the engine - # prices 100% at the 5.5p low rate, under-costing the bill (£595.68 vs - # £629.67) → lower ECF (2.69 vs 2.84) → SAP 62 not 60. (Space heating 100% - # off-peak IS correct for storage heaters — the worksheet agrees.) Strict - # xfail until the off-peak water-heating rate split is implemented. + # brick uninsulated end-terrace. Validated against Elmhurst RdSAP10 on + # identical (lodged) inputs: dual off-peak immersion, 110 L Normal cylinder, + # 2 baths → Elmhurst 61, our engine 60.92 (cost £620.38 vs Elmhurst £619.37 + # — within £1; the residual is the 3.4 m² alt-wall the gov-API mapper drops). + # Evidence saved alongside: elmhurst_summary.pdf / elmhurst_worksheet.pdf. + # The +2 over-rating first seen (62) was closed by main's Table 13 off-peak + # water-heating fix (PR #1217) plus the reduced-field window-U fix (u_window + # all-None fallback → glazing-aware raw U, heat_transmission.py). Calculator + # confirmed exact: fed Elmhurst's own inputs it reproduces Elmhurst's cost + # to the penny. (lodged 55 is the old SAP-2012 schema — not comparable.) RealCertExpectation( schema="RdSAP-Schema-17.1", sample="uprn_10002468137", cert_num="0215-2818-7357-9703-2145", - sap_score=60, - known_bug_xfail=( - "off-peak (7-hour) water-heating high/low rate split not applied — " - "engine prices 100% at the low rate; see elmhurst_worksheet.pdf (243-246)" - ), + sap_score=61, ), )