mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-08-03 05:18:22 +00:00
feat(overrides): GREEN — BoilerEfficiencyBand enum + SEDBUK band parser
BoilerEfficiencyBand (A-G + UNKNOWN sentinel) and boiler_efficiency_band_guard, the shared deterministic parser for the fifth Heating Companion (ADR-0068). UNKNOWN is never persisted; absence of a band -> no value -> Table 4b default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4844d030d7
commit
b41c25e756
2 changed files with 74 additions and 0 deletions
35
domain/epc/property_overrides/boiler_efficiency_band.py
Normal file
35
domain/epc/property_overrides/boiler_efficiency_band.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class BoilerEfficiencyBand(Enum):
|
||||
"""The SEDBUK A-G efficiency band a landlord carries in the Boiler Landlord
|
||||
Description (``Boiler: C rated Combi``) — the fifth Heating Companion
|
||||
(ADR-0068).
|
||||
|
||||
An *attribute* of a ``main_heating_system`` override, orthogonal to the
|
||||
``MainHeatingSystemType`` archetype: the archetype names the *system*, the
|
||||
band names *how well it burns*. It is deliberately NOT a per-band archetype
|
||||
— that enum is FE-owned ([[main-heating-system-pgenum-is-fe-owned]]), and 7
|
||||
bands x N boiler types would explode a cross-team contract.
|
||||
|
||||
Each band resolves (via the main-heating overlay) to a ``(winter, summer)``
|
||||
seasonal-efficiency pair the calculator consumes ahead of the Table 4b code
|
||||
default — applied to the SEDBUK-rated wet boilers only (gas/LPG/biogas and
|
||||
oil); solid-fuel / electric / CPSU boilers carry an intrinsic fixed
|
||||
efficiency and take no band.
|
||||
|
||||
``UNKNOWN`` is the classifier's "no band recognised" sentinel (a plain
|
||||
boiler, or a non-boiler heating system). It is never persisted as an
|
||||
override — absence of a band is modelled as *no value*, so the boiler keeps
|
||||
its Table 4b code default — mirroring how the finaliser drops the other
|
||||
``UNKNOWN`` classifier outputs.
|
||||
"""
|
||||
|
||||
A = "A"
|
||||
B = "B"
|
||||
C = "C"
|
||||
D = "D"
|
||||
E = "E"
|
||||
F = "F"
|
||||
G = "G"
|
||||
UNKNOWN = "Unknown"
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
"""Parse the SEDBUK A-G efficiency band out of a Landlord "Heating" description.
|
||||
|
||||
The single source of truth for the band, shared by three call sites so they
|
||||
cannot drift (mirroring how ``main_heating_guard`` backs both the live
|
||||
classifier path and ``reclassify_main_heating``):
|
||||
|
||||
* the modelling read path (``overlays_from`` → the main-heating overlay slot),
|
||||
* the ``boiler_efficiency_band`` backfill,
|
||||
* the live classifier column (as its deterministic guard).
|
||||
|
||||
The Landlord "Heating" column carries the band as ``Boiler: <A-G> rated
|
||||
<Combi|Regular Boiler|NA>``. A cell can name a second system after ``System 2:``;
|
||||
the primary (system 1) band leads the cell, so the first ``Boiler: <letter>
|
||||
rated`` occurrence is the one that scores the dwelling's main system. A
|
||||
description with no band phrasing (a plain boiler, or a non-boiler heating
|
||||
system) returns ``None`` — absence of a band, not a guess.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from domain.epc.property_overrides.boiler_efficiency_band import BoilerEfficiencyBand
|
||||
|
||||
# `Boiler: <A-G> rated` — the SEDBUK band phrasing. Case-insensitive (the
|
||||
# backfill lowercases the stored description). `\b` after the letter rejects a
|
||||
# stray "H rated" or a multi-letter token; the leading "Boiler:" anchors it so a
|
||||
# bare "A rated" elsewhere in free text cannot match.
|
||||
_BAND_RE = re.compile(r"boiler:\s*([a-g])\s+rated\b", re.IGNORECASE)
|
||||
|
||||
|
||||
def boiler_efficiency_band_guard(description: str) -> Optional[BoilerEfficiencyBand]:
|
||||
"""The SEDBUK band of the primary (system 1) boiler in a Landlord "Heating"
|
||||
description, or ``None`` when the description carries no band phrasing."""
|
||||
match = _BAND_RE.search(description)
|
||||
if match is None:
|
||||
return None
|
||||
return BoilerEfficiencyBand(match.group(1).upper())
|
||||
Loading…
Add table
Reference in a new issue