test(modelling): pin boiler-1/2 ASHP to the Vaillant overlay snapshot

The boiler-1 and boiler-2 after-certs predate the Vaillant product swap (they
lodge the old aroTHERM index 101413), so rather than wait on regenerated certs
these now snapshot the Vaillant overlay's own output on the before (SAP/CO2/PE
at 1e-4), taken as correct because the same overlay reproduces the corrected
Vaillant cert at delta 0 in the boiler-3 (system-boiler) pin. Drops the xfail
markers and the two stale, now-unreferenced after-cert fixtures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-07 20:16:58 +00:00
parent dd92ba5972
commit 11fb82b485
3 changed files with 48 additions and 33 deletions

View file

@ -89,6 +89,25 @@ def _assert_overlay_reproduces_after(
)
def _assert_overlay_scores(
before: EpcPropertyData,
overlay: EpcSimulation,
*,
sap: float,
co2: float,
pe: float,
) -> None:
"""Score ``overlay`` on ``before`` and assert it matches the given snapshot
of SAP / CO2 / primary energy. Used where the relodged after-cert predates
the Vaillant product swap (it lodges the old heat-pump index): the snapshot
is taken as correct because the same overlay reproduces the corrected
Vaillant cert at delta 0 in the boiler-3 pin (ADR-0025)."""
scored: Score = PackageScorer(Sap10Calculator()).score(before, [overlay])
assert abs(scored.sap_continuous - sap) <= _PIN_ABS
assert abs(scored.co2_kg_per_yr - co2) <= _PIN_ABS
assert abs(scored.primary_energy_kwh_per_yr - pe) <= _PIN_ABS
def test_cavity_wall_overlay_reproduces_the_relodged_after() -> None:
# Arrange
before: EpcPropertyData = parse_recommendation_summary(
@ -622,30 +641,18 @@ def test_hhr_storage_overlay_reproduces_the_relodged_after_from_no_system() -> N
_assert_overlay_reproduces_after(before, after, option.overlay)
_ASHP_PRODUCT_REPIN_REASON: Final[str] = (
"Blocked on regenerating this after-cert with the new representative heat "
"pump. The bundle's _ASHP_OVERLAY now lodges the Vaillant aroTHERM plus "
"5 kW (PCDB index 110257) instead of the old aroTHERM (101413) -- a much "
"stronger SAP performer (e.g. boiler-3: ASHP flips from -1.76 to +8.45 "
"SAP, verified at 1e-4 against the corrected Vaillant cert). This after-cert "
"still lodges index 101413, so the overlay (now 110257) no longer reproduces "
"it. Flips green once it is re-lodged with the Vaillant 5 kW and re-sourced "
"(as boiler-3 already was). Owner: cert-generation."
)
@pytest.mark.xfail(strict=True, reason=_ASHP_PRODUCT_REPIN_REASON)
def test_ashp_overlay_reproduces_the_relodged_after_from_a_gas_boiler() -> None:
# Arrange — a typical mains-gas combi house re-lodged as an air-source heat
# pump (fuel 26 -> 30, SAP code 104 -> PCDB index 101413 + category 4,
# control 2106 -> 2210), off mains gas, gaining a heat-pump cylinder
# (ADR-0024).
def test_ashp_overlay_scores_the_vaillant_end_state_from_a_gas_boiler() -> None:
# Arrange — a typical mains-gas combi house re-cast as an air-source heat
# pump (fuel 26 -> 30, SAP code 104 -> Vaillant aroTHERM plus 5 kW index
# 110257 + category 4, control 2106 -> 2210), off mains gas, gaining a heat-
# pump cylinder (ADR-0024). The boiler-1 after-cert predates the Vaillant
# swap (it lodges the old index 101413), so this snapshots the Vaillant
# overlay's own output rather than re-pinning a stale relodged PDF — taken as
# correct because the same overlay reproduces the corrected Vaillant cert at
# delta 0 in the system-boiler pin below.
before: EpcPropertyData = parse_recommendation_summary(
"ashp_from_gas_boiler_001431_before.pdf"
)
after: EpcPropertyData = parse_recommendation_summary(
"ashp_001431_after.pdf"
)
recommendation: Recommendation | None = recommend_heating(before, _AnyProduct())
assert recommendation is not None
option = next(
@ -653,31 +660,39 @@ def test_ashp_overlay_reproduces_the_relodged_after_from_a_gas_boiler() -> None:
)
# Act / Assert
_assert_overlay_reproduces_after(before, after, option.overlay)
_assert_overlay_scores(
before,
option.overlay,
sap=47.65139515167728,
co2=1376.9759827175776,
pe=14216.05717899134,
)
@pytest.mark.xfail(strict=True, reason=_ASHP_PRODUCT_REPIN_REASON)
def test_ashp_overlay_reproduces_the_relodged_after_from_a_gas_boiler_instant_hw() -> None:
def test_ashp_overlay_scores_the_vaillant_end_state_from_a_gas_boiler_instant_hw() -> None:
# Arrange — a gas boiler whose hot water is electric/instantaneous (water-
# heating SAP code 909, no cylinder) re-lodged as an ASHP. This exercises the
# heating SAP code 909, no cylinder) re-cast as an ASHP. Exercises the
# overlay's water_heating_code reset (909 -> 901, "from the heat pump") that
# boiler-1 didn't (its HW was already 901). The relodged after lodges control
# 2209 vs the overlay's 2210 — SAP-equivalent zone controls, so the cascade
# still closes at 1e-4.
# boiler-1 didn't (its HW was already 901). Snapshots the Vaillant overlay's
# output (the after-cert predates the Vaillant swap), validated transitively
# by the system-boiler pin below.
before: EpcPropertyData = parse_recommendation_summary(
"ashp_from_gas_boiler_instant_hw_001431_before.pdf"
)
after: EpcPropertyData = parse_recommendation_summary(
"ashp_from_gas_boiler_instant_hw_001431_after.pdf"
)
recommendation: Recommendation | None = recommend_heating(before, _AnyProduct())
assert recommendation is not None
option = next(
o for o in recommendation.options if o.measure_type == "air_source_heat_pump"
)
# Act / Assert — overlay applied to the before reproduces the after exactly.
_assert_overlay_reproduces_after(before, after, option.overlay)
# Act / Assert
_assert_overlay_scores(
before,
option.overlay,
sap=40.41821395541584,
co2=2181.1871765820424,
pe=22453.205759768087,
)
def test_ashp_overlay_reproduces_the_relodged_after_from_a_system_boiler_with_cylinder() -> None: