mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 42: golden-cohort PE pin uses demand cascade via calculate_sap_from_inputs
Slice 37's per-cert pin refactor pinned PE residuals against `result.primary_energy_kwh_per_m2` from the rating cascade (UK-avg climate). But per SAP10.2 Appendix U + the codebase's own SAP_CALCULATOR.md docs, the EPC's published `energy_consumption_current` is a postcode-climate value — same as CO2. The CO2 pin was already correct; PE was an oversight. Fix: use the public `calculate_sap_from_inputs` entry point twice — once with `cert_to_inputs` (rating cascade) for SAP, once with `cert_to_demand_inputs` (demand cascade) for PE + CO2. This drops the four section-helper imports and reads everything off SapResult, keeping the test surface minimal. PE residuals shift on every fixture (sometimes toward zero, sometimes away — the rating cascade was masking the real gap): cert old PE new PE Δ 0240-0200-5706-2365-8010 +0.74 +5.58 worse — known RR gap 0300-2747-7640-2526-2135 +17.34 +4.45 tighter 0390-2254-6420-2126-5561 (LN12) -3.14 +0.18 tighter ← bread-and-butter cert now within 0.2 kWh/m² 0390-2954-3640-2196-4175 -27.64 -26.68 ~same 2130-1033-4050-5007-8395 (DE22) -61.25 -65.89 worse — PV PE-offset now correctly accounted 6035-7729-2309-0879-2296 +34.62 +45.05 worse — known wall-insulation + RR gap 7536-3827-0600-0600-0276 -27.45 -17.98 tighter 8135-1728-8500-0511-3296 -14.37 -9.50 tighter The "worse" certs (0240, 6035, DE22) were never close — the rating cascade had been coincidentally masking the real PE gap on the certs with documented mapper gaps. Demand cascade now exposes the real residual for each; the documented gaps' fixes will close them. LN12 (bread-and-butter, gas combi, no PV) now reads: SAP resid +0 (exact match) PE resid +0.18 (within 0.2 kWh/m² of lodged 241) CO2 resid +0.04 (within 0.05 t/yr of lodged 3.5) First cert in the cohort within target ±0.5 on SAP and ±1 on PE/CO2. 930/930 Elmhurst cascade unchanged. 14/14 golden cohort + PCDB chain green. Pyright net-zero (2 errors before and after). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
81392208c4
commit
6836aed004
1 changed files with 26 additions and 23 deletions
|
|
@ -39,9 +39,8 @@ from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
|||
from domain.sap.calculator import calculate_sap_from_inputs
|
||||
from domain.sap.rdsap.cert_to_inputs import (
|
||||
SAP_10_2_SPEC_PRICES,
|
||||
cert_to_demand_inputs,
|
||||
cert_to_inputs,
|
||||
environmental_section_from_cert,
|
||||
local_climate_for_cert,
|
||||
)
|
||||
|
||||
_FIXTURES_DIR = Path(__file__).parent / "fixtures" / "golden"
|
||||
|
|
@ -76,7 +75,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="0240-0200-5706-2365-8010",
|
||||
actual_sap=73,
|
||||
expected_sap_resid=-12,
|
||||
expected_pe_resid_kwh_per_m2=+0.74,
|
||||
expected_pe_resid_kwh_per_m2=+5.5809,
|
||||
expected_co2_resid_tonnes_per_yr=+0.3436,
|
||||
notes=(
|
||||
"Detached house, TFA 202, age J, oil boiler, Table 4b code 130. "
|
||||
|
|
@ -96,7 +95,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="0300-2747-7640-2526-2135",
|
||||
actual_sap=78,
|
||||
expected_sap_resid=-9,
|
||||
expected_pe_resid_kwh_per_m2=+17.3417,
|
||||
expected_pe_resid_kwh_per_m2=+4.4546,
|
||||
expected_co2_resid_tonnes_per_yr=-0.5359,
|
||||
notes=(
|
||||
"Large semi-detached, TFA 526, age D, gas boiler PCDB-listed "
|
||||
|
|
@ -108,7 +107,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="0390-2954-3640-2196-4175",
|
||||
actual_sap=60,
|
||||
expected_sap_resid=-7,
|
||||
expected_pe_resid_kwh_per_m2=-27.6371,
|
||||
expected_pe_resid_kwh_per_m2=-26.6760,
|
||||
expected_co2_resid_tonnes_per_yr=-2.5816,
|
||||
notes="Large detached, TFA 360, age F, oil PCDB-listed. Cert lodges has_draught_lobby=true.",
|
||||
),
|
||||
|
|
@ -116,7 +115,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="6035-7729-2309-0879-2296",
|
||||
actual_sap=70,
|
||||
expected_sap_resid=-6,
|
||||
expected_pe_resid_kwh_per_m2=+34.62,
|
||||
expected_pe_resid_kwh_per_m2=+45.0454,
|
||||
expected_co2_resid_tonnes_per_yr=+1.0245,
|
||||
notes="Mid-terrace, TFA 128, age A, gas combi Table 4b code 104.",
|
||||
),
|
||||
|
|
@ -124,7 +123,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="7536-3827-0600-0600-0276",
|
||||
actual_sap=68,
|
||||
expected_sap_resid=+3,
|
||||
expected_pe_resid_kwh_per_m2=-27.45,
|
||||
expected_pe_resid_kwh_per_m2=-17.9833,
|
||||
expected_co2_resid_tonnes_per_yr=-0.4781,
|
||||
notes="Detached + 2 extensions, TFA 152, age D, gas PCDB.",
|
||||
),
|
||||
|
|
@ -132,7 +131,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="8135-1728-8500-0511-3296",
|
||||
actual_sap=72,
|
||||
expected_sap_resid=+1,
|
||||
expected_pe_resid_kwh_per_m2=-14.3709,
|
||||
expected_pe_resid_kwh_per_m2=-9.5017,
|
||||
expected_co2_resid_tonnes_per_yr=-0.1537,
|
||||
notes="Semi-detached, TFA 102, age C, gas PCDB-listed. Cert lodges blocked_chimneys_count=1.",
|
||||
),
|
||||
|
|
@ -140,7 +139,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="2130-1033-4050-5007-8395",
|
||||
actual_sap=82,
|
||||
expected_sap_resid=+8,
|
||||
expected_pe_resid_kwh_per_m2=-61.2533,
|
||||
expected_pe_resid_kwh_per_m2=-65.8945,
|
||||
expected_co2_resid_tonnes_per_yr=+0.1856,
|
||||
notes=(
|
||||
"End-terrace + 1 extension, TFA 64, gas combi PCDB index 17505, "
|
||||
|
|
@ -160,7 +159,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="0390-2254-6420-2126-5561",
|
||||
actual_sap=65,
|
||||
expected_sap_resid=0,
|
||||
expected_pe_resid_kwh_per_m2=-3.1420,
|
||||
expected_pe_resid_kwh_per_m2=+0.1797,
|
||||
expected_co2_resid_tonnes_per_yr=+0.0413,
|
||||
notes=(
|
||||
"End-terrace + 1 extension, TFA 80, gas combi PCDB index 18119, "
|
||||
|
|
@ -198,24 +197,28 @@ def _load_cert(cert_number: str) -> dict[str, Any]:
|
|||
)
|
||||
def test_golden_cert_residual_matches_pin(expectation: _GoldenExpectation) -> None:
|
||||
# Arrange — load the frozen cert JSON, map to EpcPropertyData, run
|
||||
# the rating cascade (UK-avg climate, SAP 10.2 spec prices per
|
||||
# ADR-0010) for SAP + PE; run the demand-cascade environmental
|
||||
# section (postcode climate via PCDB Table 172) for CO2 — that's
|
||||
# what the EPC publishes as `co2_emissions_current`.
|
||||
# the calculator end-to-end via two cascades:
|
||||
# - cert_to_inputs → UK-average climate → SAP rating (per SAP10.2
|
||||
# Appendix U: only SAP + EI use UK-avg);
|
||||
# - cert_to_demand_inputs → postcode climate (PCDB Table 172) →
|
||||
# PE + CO2 (per the same Appendix U: everything the EPC publishes
|
||||
# as "Current X" uses postcode-specific weather).
|
||||
# The single public interface `calculate_sap_from_inputs` surfaces
|
||||
# all three outputs on SapResult; no section helpers required.
|
||||
doc = _load_cert(expectation.cert_number)
|
||||
epc = EpcPropertyDataMapper.from_api_response(doc)
|
||||
inputs = cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
|
||||
|
||||
# Act
|
||||
result = calculate_sap_from_inputs(inputs)
|
||||
pc = local_climate_for_cert(epc)
|
||||
env = environmental_section_from_cert(epc, postcode_climate=pc)
|
||||
assert env is not None, "demand-cascade environmental section must compute"
|
||||
co2_calc_tonnes = env.total_co2_kg_per_yr / 1000
|
||||
rating = calculate_sap_from_inputs(
|
||||
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
|
||||
)
|
||||
demand = calculate_sap_from_inputs(
|
||||
cert_to_demand_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
|
||||
)
|
||||
|
||||
sap_resid = result.sap_score - expectation.actual_sap
|
||||
pe_resid = result.primary_energy_kwh_per_m2 - doc["energy_consumption_current"]
|
||||
co2_resid = co2_calc_tonnes - doc["co2_emissions_current"]
|
||||
sap_resid = rating.sap_score - expectation.actual_sap
|
||||
pe_resid = demand.primary_energy_kwh_per_m2 - doc["energy_consumption_current"]
|
||||
co2_resid = demand.co2_kg_per_yr / 1000 - doc["co2_emissions_current"]
|
||||
|
||||
# Assert — each residual sits within an absolute tolerance of the
|
||||
# recorded pin. Shifts beyond tolerance fire loudly: tighten the pin
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue