feat(overlay): enable gas A/B bands — Elmhurst-validated (winter,summer)

Gas A = (90.6, 81.6) [PCDB 18790 Worcester Greenstar 8000 Life], B = (88.0, 79.4)
[PCDB 15029 Baxi Duo-tec Combi 24 HE], read off accredited Elmhurst RdSAP10
worksheets (206)/(217). Applied to gas regular (102) + combi (104); gas/LPG/biogas
share the codes. A-rated real cert 15017550: +1.78 SAP (the under-credit fix).
Oil A/B still pending (no slot). Corpus unmoved; 797 tests green (ADR-0068).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-29 18:37:10 +00:00
parent c7edb1df0b
commit 063eb17a89
6 changed files with 49 additions and 16 deletions

View file

@ -287,13 +287,25 @@ _BAND_TO_TABLE_4B_CODE: dict[tuple[int, BoilerEfficiencyBand], int] = {
}
# A/B exceed Table 4b's 84 % gas / 8284 % oil ceiling, so they cannot borrow a
# Table 4b code — they take SEDBUK-derived `(winter, summer)` slot values that
# MUST be validated against an accredited Elmhurst build before they are enabled
# (ADR-0068 gated pre-req; ~14.6k A-rated homes move up + ripple into
# Recommendations / eligibility). PENDING VALIDATION: intentionally empty — until
# the pairs are pinned, an A/B boiler takes no slot and keeps its condensing code
# default, so no invented numbers ship.
_BAND_SLOT_EFFICIENCY_PCT: dict[tuple[int, BoilerEfficiencyBand], tuple[float, float]] = {}
# Table 4b code — they take SEDBUK-derived `(winter, summer)` slot values
# validated against an **accredited Elmhurst build** (ADR-0068 gated pre-req).
#
# GAS (validated 2026-07-29 on Elmhurst RdSAP10, dwelling ref 000567):
# A → PCDB 18790 (Worcester Greenstar 8000 Life): worksheet (206)=90.6 / (217)=81.6
# B → PCDB 15029 (Baxi Duo-tec Combi 24 HE class): worksheet (206)=88.0 / (217)=79.4
# Gas/LPG/biogas share codes 101119, so one pair per band covers all three; applied
# to both gas regular (102) and combi (104). (Enabling A moves ~14.6k A-rated homes
# up ~+13 SAP — flagged to stakeholders; some cross a band → Recs/eligibility.)
#
# OIL (124132): still pending — no oil slot yet, so an oil A/B boiler keeps its
# condensing code default (127/130). Oil A/B is a tiny population; a follow-up can
# pin it from an oil PCDB product or an oil Elmhurst build. No invented numbers ship.
_BAND_SLOT_EFFICIENCY_PCT: dict[tuple[int, BoilerEfficiencyBand], tuple[float, float]] = {
(102, BoilerEfficiencyBand.A): (90.6, 81.6),
(104, BoilerEfficiencyBand.A): (90.6, 81.6),
(102, BoilerEfficiencyBand.B): (88.0, 79.4),
(104, BoilerEfficiencyBand.B): (88.0, 79.4),
}
def band_seasonal_efficiency_pct(

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -42,15 +42,33 @@ def test_c_and_below_bands_resolve_to_the_table_4b_pair(
assert band_seasonal_efficiency_pct(base_code, band) == expected
@pytest.mark.parametrize(
("base_code", "band", "expected"),
[
# Gas A/B validated against accredited Elmhurst (ADR-0068): A → PCDB 18790
# (Worcester Greenstar 8000 Life) 90.6/81.6; B → PCDB 15029 (Baxi Duo-tec
# Combi 24 HE) 88.0/79.4. Gas/LPG/biogas share codes — one pair per band on
# both regular (102) and combi (104).
(102, BoilerEfficiencyBand.A, (90.6, 81.6)),
(104, BoilerEfficiencyBand.A, (90.6, 81.6)),
(102, BoilerEfficiencyBand.B, (88.0, 79.4)),
(104, BoilerEfficiencyBand.B, (88.0, 79.4)),
],
)
def test_gas_a_and_b_bands_use_the_elmhurst_validated_slot(
base_code: int, band: BoilerEfficiencyBand, expected: tuple[float, float]
) -> None:
# These exceed Table 4b's 84% ceiling (the A under-credit the fix removes).
assert band_seasonal_efficiency_pct(base_code, band) == expected
@pytest.mark.parametrize("band", [BoilerEfficiencyBand.A, BoilerEfficiencyBand.B])
@pytest.mark.parametrize("base_code", [102, 104, 127, 130])
def test_a_and_b_bands_are_pending_elmhurst_validation(
@pytest.mark.parametrize("base_code", [127, 130])
def test_oil_a_and_b_bands_are_pending_validation(
base_code: int, band: BoilerEfficiencyBand
) -> None:
# A/B exceed Table 4b's 84% ceiling — they are judgement values that MUST be
# pinned against an accredited Elmhurst build before they are enabled
# (ADR-0068 gated pre-req). Until then: no slot, so an A/B boiler keeps its
# condensing code default (no invented numbers ship).
# Oil A/B not yet validated — no slot, so an oil A/B boiler keeps its
# condensing code default (127/130). No invented numbers ship (ADR-0068).
assert band_seasonal_efficiency_pct(base_code, band) is None
@ -77,6 +95,10 @@ def test_non_banded_boilers_never_take_a_slot(
("Gas boiler, combi", BoilerEfficiencyBand.G, (66.0, 57.0)),
("Oil boiler, regular", BoilerEfficiencyBand.E, (71.0, 59.0)),
("Oil boiler, combi", BoilerEfficiencyBand.D, (77.0, 68.0)),
# Elmhurst-validated A/B upside on gas (the A under-credit fix).
("Gas boiler, regular", BoilerEfficiencyBand.A, (90.6, 81.6)),
("Gas boiler, combi", BoilerEfficiencyBand.A, (90.6, 81.6)),
("Gas boiler, combi", BoilerEfficiencyBand.B, (88.0, 79.4)),
],
)
def test_overlay_sets_the_band_efficiency_slot_on_gas_and_oil_boilers(
@ -96,9 +118,8 @@ def test_overlay_sets_the_band_efficiency_slot_on_gas_and_oil_boilers(
[
# No band → no slot (today's behaviour: keep the condensing code default).
("Gas boiler, combi", None),
# A/B pending Elmhurst → no slot yet.
("Gas boiler, combi", BoilerEfficiencyBand.A),
# C on gas regular equals the code default — still set, but a no-op value.
# Oil A/B still pending Elmhurst → no slot yet.
("Oil boiler, combi", BoilerEfficiencyBand.A),
# Non-banded boilers ignore the band letter entirely.
("Electric boiler", BoilerEfficiencyBand.D),
("Solid fuel boiler", BoilerEfficiencyBand.G),