diff --git a/domain/epc/property_overrides/glazing_mix_guard.py b/domain/epc/property_overrides/glazing_mix_guard.py new file mode 100644 index 000000000..83f4c45c0 --- /dev/null +++ b/domain/epc/property_overrides/glazing_mix_guard.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Optional + +from domain.epc.property_overrides.glazing_type import GlazingType + + +def glazing_mix_guard(description: str) -> Optional[GlazingType]: + """Deterministically resolve an aggregate-mix glazing description to ``MIXED``. + + A landlord glazing description is often a whole-dwelling proportion split + ("40% double glazing 2002 or later, 60% single glazing"). Such a mix cannot be + faithfully applied per-window — it says *how much* is each type, not *which* + windows — and the cert already carries the true per-window glazing, so a mix + must resolve to ``GlazingType.MIXED`` (no overlay → the cert is kept), never to + a single dominant type that would flatten every window (ADR-0042). + + Recognises the structured ``"N% , M% "`` split and returns + ``MIXED`` when it is a genuine mix — two or more glazing types present and none + dominant (≥ the uniform threshold). Returns ``None`` for a uniform / near- + uniform assertion or an unparseable description, so the LLM classifier still + resolves it (a uniform reglaze is applied; varied phrasings are the LLM's job). + """ + raise NotImplementedError diff --git a/domain/epc/property_overrides/glazing_type.py b/domain/epc/property_overrides/glazing_type.py index ee3ad267b..8724f5b5e 100644 --- a/domain/epc/property_overrides/glazing_type.py +++ b/domain/epc/property_overrides/glazing_type.py @@ -21,4 +21,11 @@ class GlazingType(Enum): DOUBLE_PRE_2002 = "Double glazing, pre-2002" TRIPLE_PRE_2002 = "Triple glazing, pre-2002" TRIPLE_POST_2002 = "Triple glazing, 2002 or later" + # An aggregate mix of glazing types across the dwelling ("40% double, 60% + # single"). Deliberately absent from the overlay's `_GLAZING_CODES`, so it + # resolves to no overlay and the cert's per-window `sap_windows` are kept + # rather than flattened to one type — a whole-dwelling proportion cannot say + # which windows are which (ADR-0042 amendment, #1376). The FE-owned pgEnum + # gains this member via a Drizzle migration. + MIXED = "Mixed glazing" UNKNOWN = "Unknown" diff --git a/tests/domain/epc/test_glazing_mix_guard.py b/tests/domain/epc/test_glazing_mix_guard.py new file mode 100644 index 000000000..973b96b6a --- /dev/null +++ b/tests/domain/epc/test_glazing_mix_guard.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from domain.epc.property_overrides.glazing_type import GlazingType +from domain.epc.property_overrides.glazing_mix_guard import glazing_mix_guard + + +def test_a_genuine_percentage_mix_classifies_as_mixed() -> None: + # Arrange — the bug: an aggregate mix the LLM would flatten to "Double". + description = "40% double glazing 2002 or later, 60% single glazing" + + # Act + result = glazing_mix_guard(description) + + # Assert — a mix defers to the cert's per-window glazing, not a whole-dwelling + # type. + assert result is GlazingType.MIXED