test(modelling): ASHP pin from a gas boiler with instantaneous HW + fuel tripwire

Second ASHP before/after (boiler 2): a gas boiler whose hot water is electric/
instantaneous (water-heating SAP code 909, no cylinder). The cascade pin passes
at 1e-4, exercising the overlay's water_heating_code reset 909 -> 901 that the
boiler-1 pin (already 901) did not. (After lodges control 2209 vs the overlay's
2210 — SAP-equivalent zone controls.)

Adds an xfail(strict) tripwire test_gas_boiler_instant_hw_before_baselines: the
raw before is not scorable on its own because the mapper maps the 'BGB' gas-
boiler EES code to an empty main_fuel_type (boiler-1's 'RGE' resolves to 26),
so Sap10Calculator raises MissingMainFuelType. Harmless to the pin (the overlay
overwrites fuel -> 30); flips green when the mapper derives mains gas from the
gas-boiler SAP code (separate mapper-front fix). ADR-0024.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-06 17:40:25 +00:00
parent 9f17a27766
commit ec76acc3d8
3 changed files with 50 additions and 0 deletions

View file

@ -641,3 +641,53 @@ def test_ashp_overlay_reproduces_the_relodged_after_from_a_gas_boiler() -> None:
# Act / Assert
_assert_overlay_reproduces_after(before, after, option.overlay)
def test_ashp_overlay_reproduces_the_relodged_after_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
# 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.
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)
_BOILER_INSTANT_HW_FUEL_REASON: Final[str] = (
"Blocked on the Elmhurst mapper deriving main_fuel_type for a gas boiler "
"lodged with EES code 'BGB' / Main Heating SAP code 102: it currently maps "
"to '' (empty), so Sap10Calculator raises MissingMainFuelType when "
"baselining the raw before. (boiler-1's 'RGE' resolves to mains gas 26; "
"'BGB' has no mapping.) The ASHP overlay still reproduces the after exactly "
"because it overwrites main_fuel_type -> 30, so the cascade pin above passes "
"— only baselining the unmodified before is blocked. Flips green once the "
"mapper derives mains gas (26) from the gas-boiler SAP code. Owner: "
"mapper/extractor front."
)
@pytest.mark.xfail(strict=True, reason=_BOILER_INSTANT_HW_FUEL_REASON)
def test_gas_boiler_instant_hw_before_baselines() -> None:
# The Modelling pipeline baselines the dwelling before modelling it, so the
# before must be scorable on its own. This one is not yet: its main fuel is
# unresolved (see reason). A failing tripwire for the separate mapper fix.
# Arrange
before: EpcPropertyData = parse_recommendation_summary(
"ashp_from_gas_boiler_instant_hw_001431_before.pdf"
)
# Act / Assert — currently raises MissingMainFuelType.
Sap10Calculator().calculate(before)