Slice 102f: Layer 4 chain tests for 7-cert ASHP cohort at spec-precision floor

Pins the full API → cert_to_inputs → calculate_sap_from_inputs cascade
for each of the 7 ASHP cohort certs against the Elmhurst dr87
worksheet's continuous SAP. Tolerance is 0.07 (NOT 1e-4 like the
boiler cohort) — see HANDOVER_CERT_0380_MIT_CASCADE.md:

  - BRE web confirmed max_output_kw matches cascade (4.39 for
    Mitsubishi PCDB 104568, 3.933 for Daikin PCDB 102421).
  - Cascade (39) annual HLC matches worksheet at 4 dp exact for
    certs 0380, 2225.
  - Back-solving worksheet η_space implies ~0.15% drift in
    Elmhurst's internal η_space interpolation precision (likely
    a vendor rounding convention not in public SAP 10.2 spec).

The 7-cert cohort clusters within +0.030..+0.060 SAP — this is the
spec-precision floor for the publicly-documented cascade.

At rounded (integer SAP) precision, all 7 cascade integers match
the lodged values exactly (residual = 0, pinned in
`_GOLDEN_EXPECTATIONS` per slice 102f-prep.11).

Cohort summary:
  0380  88.5698 vs 88.5104 Δ=+0.059  Mitsubishi PUZ-WM50VHA
  0350  84.1825 vs 84.1367 Δ=+0.046  Mitsubishi PUZ-WM50VHA
  2225  88.8362 vs 88.7921 Δ=+0.044  Mitsubishi PUZ-WM50VHA + PV
  2636  86.2964 vs 86.2641 Δ=+0.032  Mitsubishi PUZ-WM50VHA + cantilever
  3800  86.1900 vs 86.1458 Δ=+0.044  Mitsubishi PUZ-WM50VHA
  9285  84.1871 vs 84.1369 Δ=+0.050  Mitsubishi PUZ-WM50VHA
  9418  84.6601 vs 84.6305 Δ=+0.030  Daikin Altherma EDLQ05CAV3 ("24" duration)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-27 17:08:16 +00:00
parent 38a5f906bc
commit c00866607b

View file

@ -925,6 +925,118 @@ def test_api_001479_full_chain_sap_matches_worksheet_pdf_exactly() -> None:
assert abs(result.sap_score_continuous - worksheet_unrounded_sap) < 1e-4
# ============================================================================
# Layer 4 chain tests — 7-cert ASHP cohort
# ============================================================================
# These pin the API → from_api_response → cert_to_inputs →
# calculate_sap_from_inputs cascade against each cert's Elmhurst dr87
# worksheet unrounded SAP. Tolerance is 0.07 (NOT 1e-4 like the boiler
# cohort above) — see HANDOVER_CERT_0380_MIT_CASCADE.md for the
# investigation: BRE web confirmed max_output_kw matches cascade
# exactly (4.39 / 3.933), cascade (39) annual HLC matches worksheet
# at 4 dp, but back-solving worksheet η_space implies ~0.15% drift
# in Elmhurst's internal interpolation precision (likely a vendor
# rounding convention not in the public SAP 10.2 spec). The 7 certs
# cluster within +0.030..+0.060 SAP — this is the spec-precision
# floor for the publicly-documented cascade.
#
# At rounded (integer SAP) precision, all 7 cascade integers match
# the lodged values exactly (residual = 0, pinned in
# `_GOLDEN_EXPECTATIONS`).
_API_0350_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "0350-2968-2650-2796-5255.json"
)
_API_3800_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "3800-8515-0922-3398-3563.json"
)
_API_9285_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "9285-3062-0205-7766-7200.json"
)
_ASHP_COHORT_CHAIN_TOLERANCE: float = 0.07
"""SAP-precision floor for the 7-cert ASHP cohort — see handover."""
def test_api_0380_full_chain_sap_within_spec_floor_of_worksheet() -> None:
# Mitsubishi PUZ-WM50VHA PCDB 104568, semi-detached bungalow age D.
doc = json.loads(_API_0380_JSON.read_text())
epc = EpcPropertyDataMapper.from_api_response(doc)
result = calculate_sap_from_inputs(
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
)
assert abs(result.sap_score_continuous - 88.5104) < _ASHP_COHORT_CHAIN_TOLERANCE
def test_api_0350_full_chain_sap_within_spec_floor_of_worksheet() -> None:
# Mitsubishi PUZ-WM50VHA PCDB 104568.
doc = json.loads(_API_0350_JSON.read_text())
epc = EpcPropertyDataMapper.from_api_response(doc)
result = calculate_sap_from_inputs(
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
)
assert abs(result.sap_score_continuous - 84.1367) < _ASHP_COHORT_CHAIN_TOLERANCE
def test_api_2225_full_chain_sap_within_spec_floor_of_worksheet() -> None:
# Mitsubishi PUZ-WM50VHA PCDB 104568, with PV. Slice 102f-prep.8
# closed the shower_outlets=None default.
doc = json.loads(_API_2225_JSON.read_text())
epc = EpcPropertyDataMapper.from_api_response(doc)
result = calculate_sap_from_inputs(
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
)
assert abs(result.sap_score_continuous - 88.7921) < _ASHP_COHORT_CHAIN_TOLERANCE
def test_api_2636_full_chain_sap_within_spec_floor_of_worksheet() -> None:
# Mitsubishi PUZ-WM50VHA PCDB 104568, with cantilever + alt wall.
# Slice 102f-prep.9 (cantilever) + 102f-prep.10 (alt-wall openings).
doc = json.loads(_API_2636_JSON.read_text())
epc = EpcPropertyDataMapper.from_api_response(doc)
result = calculate_sap_from_inputs(
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
)
assert abs(result.sap_score_continuous - 86.2641) < _ASHP_COHORT_CHAIN_TOLERANCE
def test_api_3800_full_chain_sap_within_spec_floor_of_worksheet() -> None:
# Mitsubishi PUZ-WM50VHA PCDB 104568.
doc = json.loads(_API_3800_JSON.read_text())
epc = EpcPropertyDataMapper.from_api_response(doc)
result = calculate_sap_from_inputs(
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
)
assert abs(result.sap_score_continuous - 86.1458) < _ASHP_COHORT_CHAIN_TOLERANCE
def test_api_9285_full_chain_sap_within_spec_floor_of_worksheet() -> None:
# Mitsubishi PUZ-WM50VHA PCDB 104568.
doc = json.loads(_API_9285_JSON.read_text())
epc = EpcPropertyDataMapper.from_api_response(doc)
result = calculate_sap_from_inputs(
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
)
assert abs(result.sap_score_continuous - 84.1369) < _ASHP_COHORT_CHAIN_TOLERANCE
def test_api_9418_full_chain_sap_within_spec_floor_of_worksheet() -> None:
# Daikin Altherma EDLQ05CAV3 PCDB 102421, heating_duration_code='24'
# (continuous, all days at Th). Slice 102f-prep.7 closed Table N4.
doc = json.loads(_API_9418_JSON.read_text())
epc = EpcPropertyDataMapper.from_api_response(doc)
result = calculate_sap_from_inputs(
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
)
assert abs(result.sap_score_continuous - 84.6305) < _ASHP_COHORT_CHAIN_TOLERANCE
# ============================================================================
# Mapper-vs-hand-built EpcPropertyData diff tests
# ============================================================================