mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.40: parametrized API-path chain sweep for cohort-2 (34/38 at 1e-4)
Mirror of the cohort-2 Summary-path sweep that closed across
S0380.30..38: for each of the 38 cohort-2 certs whose API JSON was
fetched in S0380.39, drive the full API chain (`from_api_response`
→ `cert_to_inputs` → `calculate_sap_from_inputs`) and assert
`sap_score_continuous` vs the worksheet's lodged SAP at abs <= 1e-4.
Per cross-mapper parity ([[feedback-cross-mapper-parity-via-cascade]]):
the SAP cascade is the load-bearing equivalence check between
EpcPropertyData produced by from_api_response and from_elmhurst_site_notes.
If both paths hit the worksheet at 1e-4, they're cascade-output-
equivalent for load-bearing fields — strictly stronger than a noisy
structural EpcPropertyData diff.
Two parametrized tests, both green at HEAD:
- test_api_cohort_2_full_chain_sap_matches_worksheet_at_1e_minus_4:
34 certs that hit the worksheet at 1e-4 on the API path immediately
(the cascade can't tell which mapper produced the EPC).
- test_api_cohort_2_open_cert_residual_matches_current_pin:
4 certs that don't yet hit 1e-4 — pinned at their current cascade
output as forcing functions per [[project-api-to-sap-residual-test]].
When a follow-up slice closes the underlying mapper/spec gap, the
cascade output moves and the pin fires, forcing the cert to migrate
from _COHORT_2_API_OPEN to _COHORT_2_API_CLOSED.
Open cohort residuals (handover to Slice C+):
- 0300/1536/9380: tight +0.42..+0.44 band — likely a single shared
cascade-spec gap (API-mapper-specific, since Summary path hits 1e-4)
- 2102: -6.30 — Summary test (test_summary_2102_secondary_heating_
routes_house_coal_for_open_fire) shows the cert lodges house-coal
open-fire secondary heating; API mapper likely routes secondary
fuel differently. Probe `secondary_heating` block first.
Test suite: 712 → 750 pass (0 fails). Pyright net-zero on touched file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
22ae6f4d77
commit
ff25746f44
1 changed files with 147 additions and 0 deletions
|
|
@ -1872,6 +1872,153 @@ def test_api_9418_full_chain_sap_within_spec_floor_of_worksheet() -> None:
|
|||
assert abs(result.sap_score_continuous - 84.6305) < _ASHP_COHORT_CHAIN_TOLERANCE
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Cohort-2 API-path chain tests (cross-mapper parity at the cascade)
|
||||
# ============================================================================
|
||||
# Mirror the cohort-2 Summary-path sweep that closed across S0380.30..38.
|
||||
# Per [[feedback-cross-mapper-parity-via-cascade]]: API EPC and Elmhurst EPC
|
||||
# must produce SAP within 1e-4 of each other AND of the worksheet — the
|
||||
# SAP cascade is the load-bearing equivalence check. Each cert in this
|
||||
# cohort has both a Summary PDF (under `sap worksheets/additional with
|
||||
# api 2/<cert>/Summary_*.pdf`) and an API JSON fixture (fetched into
|
||||
# `domain/sap10_calculator/rdsap/tests/fixtures/golden/<cert>.json` in
|
||||
# Slice S0380.39). Worksheet SAP is the source of truth.
|
||||
#
|
||||
# At HEAD of Slice S0380.40: 34/38 certs hit 1e-4 immediately; the
|
||||
# remaining 4 are residual-pinned below as forcing functions for the
|
||||
# next per-cert closure slices (Slice C+).
|
||||
|
||||
_COHORT_2_API_FIXTURE_DIR: Path = (
|
||||
Path(__file__).parents[3]
|
||||
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
|
||||
)
|
||||
|
||||
# (cert_dir, worksheet_unrounded_sap) — 34 cohort-2 certs whose API-path
|
||||
# cascade hits the worksheet's continuous SAP at 1e-4 without any
|
||||
# follow-up mapper work. Identical to the Summary-path sweep at the
|
||||
# same tolerance: cross-mapper parity is achieved via cascade output
|
||||
# equivalence (per [[feedback-cross-mapper-parity-via-cascade]]).
|
||||
_COHORT_2_API_CLOSED: list[tuple[str, float]] = [
|
||||
("0036-6325-1100-0063-1226", 62.7471),
|
||||
("0100-5141-0522-4696-3463", 85.8332),
|
||||
("0200-3155-0122-2602-3563", 80.8674),
|
||||
("0310-2763-5450-2506-3501", 78.3593),
|
||||
("0320-2126-2150-2326-6161", 71.7224),
|
||||
("0320-2756-8640-2296-1101", 89.9458),
|
||||
("0330-2257-3640-2196-3145", 84.6541),
|
||||
("0360-2266-5650-2106-8285", 80.468),
|
||||
("0380-2530-6150-2326-4161", 65.7795),
|
||||
("0390-2066-4250-2026-4555", 65.3253),
|
||||
("0464-3032-0205-4276-3204", 80.4533),
|
||||
("0652-3022-1205-2826-1200", 70.9577),
|
||||
("2007-3011-9205-8136-3204", 68.3914),
|
||||
("2031-3007-0205-1296-3204", 64.1734),
|
||||
("2130-3018-4205-4686-5204", 71.3158),
|
||||
("2336-3124-3600-0517-1292", 83.4955),
|
||||
("2536-2525-0600-0788-2292", 79.7264),
|
||||
("2590-3025-7205-9066-0200", 65.9194),
|
||||
("2699-3025-5205-8066-0200", 68.7535),
|
||||
("2800-7999-0322-4594-3563", 78.1408),
|
||||
("3136-7925-4500-0246-6202", 77.8872),
|
||||
("3336-2825-9400-0512-8292", 78.3739),
|
||||
("4536-5424-8600-0109-1226", 82.4974),
|
||||
("4536-8325-3100-0409-1222", 65.6),
|
||||
("4800-3992-0422-0599-3563", 86.7192),
|
||||
("6835-3920-2509-0933-5226", 80.1977),
|
||||
("7700-3362-0922-7022-3563", 63.4425),
|
||||
("7800-1501-0922-7127-3563", 64.7504),
|
||||
("7836-3125-0600-0526-2202", 80.1792),
|
||||
("9036-0824-3500-0420-8222", 84.2727),
|
||||
("9370-3060-1205-3546-4204", 87.8687),
|
||||
("9421-3045-3205-1646-6200", 87.4495),
|
||||
("9796-3058-6205-0346-9200", 90.1318),
|
||||
("9836-7525-9500-0575-1202", 75.2223),
|
||||
]
|
||||
|
||||
# (cert_dir, worksheet_unrounded_sap, current_cascade_continuous_sap)
|
||||
# — 4 cohort-2 certs whose API-path cascade does NOT yet hit the
|
||||
# worksheet at 1e-4. The third tuple element is the cascade's current
|
||||
# `sap_score_continuous` at HEAD of Slice S0380.40, pinned at abs <=
|
||||
# 1e-4 as a forcing function: when a follow-up slice closes the
|
||||
# residual, the cascade output moves and this assertion fires, forcing
|
||||
# the cert to migrate from `_COHORT_2_API_OPEN` to `_COHORT_2_API_CLOSED`.
|
||||
#
|
||||
# Cluster diagnosis (handover to next agent):
|
||||
# - 0300/1536/9380: ws Δ = +0.42..+0.44, tight 0.02-band cluster
|
||||
# — likely a single shared cascade-spec gap (heating/cooling
|
||||
# dispatch or RdSAP fuel-factor cascade). Summary path hits 1e-4
|
||||
# on all three, so the gap is API-mapper-specific (a field the
|
||||
# Summary mapper surfaces and the API mapper drops or mis-routes).
|
||||
# - 2102: ws Δ = -6.30, two orders of magnitude worse. Summary path
|
||||
# hits 1e-4 (cohort-2 Summary sweep is 38/38). The Summary test
|
||||
# `test_summary_2102_secondary_heating_routes_house_coal_for_open_fire`
|
||||
# covers the cert's open-fire + house-coal secondary heating; the
|
||||
# API mapper likely lodges the secondary fuel differently. Probe
|
||||
# the API JSON's `secondary_heating` block first.
|
||||
_COHORT_2_API_OPEN: list[tuple[str, float, float]] = [
|
||||
("0300-2403-2650-2206-0235", 76.6541, 77.084454),
|
||||
("1536-9325-5100-0433-1226", 65.8928, 66.337334),
|
||||
("9380-2957-7490-2595-3141", 74.5902, 75.010196),
|
||||
("2102-3018-0205-7886-5204", 63.8732, 57.570156),
|
||||
]
|
||||
|
||||
|
||||
def _cascade_continuous_sap_from_api(cert_dir_name: str) -> float:
|
||||
doc = json.loads((_COHORT_2_API_FIXTURE_DIR / f"{cert_dir_name}.json").read_text())
|
||||
epc = EpcPropertyDataMapper.from_api_response(doc)
|
||||
r = calculate_sap_from_inputs(cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES))
|
||||
return r.sap_score_continuous
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cert_dir_name,ws_sap", _COHORT_2_API_CLOSED)
|
||||
def test_api_cohort_2_full_chain_sap_matches_worksheet_at_1e_minus_4(
|
||||
cert_dir_name: str, ws_sap: float
|
||||
) -> None:
|
||||
"""API-path mirror of the cohort-2 Summary-path sweep.
|
||||
|
||||
For each cert: the GOV.UK EPB API JSON → `from_api_response` →
|
||||
`cert_to_inputs` → `calculate_sap_from_inputs` chain must hit the
|
||||
worksheet's continuous SAP at abs <= 1e-4 — the same tolerance
|
||||
the Summary path achieves. Cross-mapper parity at the cascade
|
||||
output ([[feedback-cross-mapper-parity-via-cascade]])."""
|
||||
# Arrange
|
||||
actual = _cascade_continuous_sap_from_api(cert_dir_name)
|
||||
|
||||
# Act (no separate act phase — `actual` IS the cascade output)
|
||||
delta = actual - ws_sap
|
||||
|
||||
# Assert
|
||||
assert abs(delta) <= 1e-4, (
|
||||
f"cert {cert_dir_name}: cascade SAP={actual:.6f} vs worksheet {ws_sap}; Δ={delta:+.6f}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cert_dir_name,ws_sap,pinned_continuous_sap", _COHORT_2_API_OPEN
|
||||
)
|
||||
def test_api_cohort_2_open_cert_residual_matches_current_pin(
|
||||
cert_dir_name: str, ws_sap: float, pinned_continuous_sap: float
|
||||
) -> None:
|
||||
"""Residual pin for the 4 cohort-2 API-path certs that DON'T yet hit
|
||||
1e-4 against the worksheet. The pin asserts the cascade's current
|
||||
`sap_score_continuous` at abs <= 1e-4 — a forcing function: when a
|
||||
follow-up slice closes the underlying mapper or spec gap, the
|
||||
cascade output moves and this test fires, forcing the cert to
|
||||
migrate from `_COHORT_2_API_OPEN` to `_COHORT_2_API_CLOSED`. Per
|
||||
[[project-api-to-sap-residual-test]] this is the established
|
||||
pattern for tracking residuals as forcing functions, not as
|
||||
tolerance widening."""
|
||||
# Arrange
|
||||
actual = _cascade_continuous_sap_from_api(cert_dir_name)
|
||||
|
||||
# Assert — Δ vs PINNED cascade output (worksheet Δ stays surfaced
|
||||
# in the message for diagnostic context).
|
||||
assert abs(actual - pinned_continuous_sap) <= 1e-4, (
|
||||
f"cert {cert_dir_name}: cascade SAP={actual:.6f} moved from pin "
|
||||
f"{pinned_continuous_sap}; worksheet Δ now {actual - ws_sap:+.6f}"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Mapper-vs-hand-built EpcPropertyData diff tests
|
||||
# ============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue