Addresses PR #1402 review: the guard only claimed dominant-single, leaving
the symmetric bug open — the LLM can flatten a dominant-double/triple split
onto its minority single (e.g. "96% Double glazing 2002 or later, 4% Single").
A dominant double/triple whose era is explicit ("pre-2002" / "2002 or later")
is just as fully determined as era-free single, so the guard now claims it via
_DOMINANT_MEMBER. Only a genuinely ambiguous era ("unknown age", unstated) still
defers to the LLM — the "96% double -> None" contract now holds solely for the
era-unknown case, not the era-stated one.
Backfill script reuses the same guard, so it now corrects any dominant split;
renamed reclassify_dominant_single_glazing.py -> reclassify_dominant_glazing.py
to match. Tests cover double/triple x pre-2002/2002-or-later and the still-
deferred unknown-age case; 14 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A landlord glazing split like "4% Double glazing 2002 or later, 96% Single
Glazing" is a near-uniform *single*-glazing dwelling, but the LLM classifier
latched onto the 4% minority and wrote a "Double glazing…" override — over-
crediting the dwelling.
glazing_mix_guard rescued genuine mixes (no type >= 90%) to MIXED but returned
None for near-uniform splits, trusting the LLM to have applied the dominant type.
It hadn't. Single glazing is era-free, so a dominant-single split is fully
determined: the guard now returns SINGLE for it (dominant double/triple still
carry era ambiguity, so they stay the LLM's job — the existing "96% double" case
still defers).
Adds a portfolio-scoped backfill (reuses the same guard, so it can't drift from
the live path) and applied it to portfolio 796 (Hyde): 115 glazing overrides
corrected from a spurious Double back to Single.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gov-API certs that lodge a gas boiler as main_heating_category=2 + a PCDB
main_heating_index_number but omit sap_main_heating_code were carried with
a null code, so the heating generators (which gated on sap_main_heating_code
alone) never evaluated them for a boiler upgrade or a system tune-up — they
silently got nothing (£0 works, plateau at D). The SAP calculator already
handles this shape (cert_to_inputs._is_wet_boiler_main), so the calculator
modelled these as wet boilers while the recommenders ignored them.
Mirror the calculator's classification in the generator:
- _is_wet_boiler / _is_electric_boiler fall back, when no SAP code is
lodged, to a PCDB Table 105 boiler index or main_heating_category in
{1,2} (heat pumps excluded); electric boilers stay excluded by the code
range when lodged, else by an electricity main fuel.
- _already_condensing reads the boiler's declared PCDB winter efficiency
when there is no Table 4b code, so a modern condensing boiler lodged this
way still isn't offered a pointless like-for-like swap (gets a tune-up).
Verified read-only on portfolio 814 / scenario 1271: pids 742304, 742306,
742322, 742325, 742336 now get a gas_boiler_upgrade (two cross D->C on the
boiler alone) where they had £0 works; predicted properties whose heating
donor is a PCDB-shape boiler (e.g. 742095/742102/742105) also recover a
tune-up. Fixes the A#2 slice of #1388 (and unblocks 742302/742367).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Heat pump drags category 4 + electricity (29). Meter stays single-rate: a heat
pump does not imply an off-peak tariff the way storage/CPSU do (SAP §12 Rule 3
is conditional), so forcing Dual would mis-bill it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>