Resolve a historic EPC's stable attributes into cohort code spaces 🟩

property_type/built_form reuse the override code mapping; wall material
prefix -> RdSAP wall_construction; E&W age-band strings -> Table S1
letters; main_fuel display text (stripping " (not community)") -> modern
RdSAP-20/21 codes; TFA -> float. Unresolvable -> None (filter inactive).
Roof construction is deferred: its RdSAP code table isn't pinned in-repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-06 08:22:31 +00:00
parent 992950bd9f
commit bbfcf1385b

View file

@ -17,6 +17,68 @@ from dataclasses import dataclass
from typing import Optional
from datatypes.epc.domain.historic_epc import HistoricEpc
from domain.epc.property_overrides.override_code_mapping import (
built_form_to_code,
property_type_to_code,
)
# RdSAP `wall_construction` codes by material prefix — the same table
# `wall_type_overlay.py` pins (source: domain/sap10_ml/rdsap_uvalues.py).
# The historic description's material is everything before the first comma.
_WALL_MATERIAL_CONSTRUCTION: dict[str, int] = {
"Granite or whin": 1,
"Sandstone": 2,
"Solid brick": 3,
"Cavity wall": 4,
"Timber frame": 5,
"System built": 6,
"Cob": 7,
"Park home wall": 8,
}
# RdSAP Table S1 band letters — the code space cohort building parts carry
# (`sap_building_parts[].construction_age_band` is "A".."M" from the API).
# The old register lodged the full England-and-Wales display strings; a
# pre-2012 cert cannot carry the post-2012 bands, but they cost nothing.
_AGE_BAND_LETTERS: dict[str, str] = {
"England and Wales: before 1900": "A",
"England and Wales: 1900-1929": "B",
"England and Wales: 1930-1949": "C",
"England and Wales: 1950-1966": "D",
"England and Wales: 1967-1975": "E",
"England and Wales: 1976-1982": "F",
"England and Wales: 1983-1990": "G",
"England and Wales: 1991-1995": "H",
"England and Wales: 1996-2002": "I",
"England and Wales: 2003-2006": "J",
"England and Wales: 2007 onwards": "K",
"England and Wales: 2007-2011": "K",
"England and Wales: 2012-2022": "L",
"England and Wales: 2023 onwards": "M",
}
# Modern RdSAP-20/21 `main_fuel` codes (epc_codes.csv), keyed by the base fuel
# description — the same family `main_fuel_overlay.py` pins. The old register
# suffixes private fuels with " (not community)", stripped before lookup.
_FUEL_CODES: dict[str, int] = {
"mains gas": 26,
"mains gas (community)": 20,
"LPG": 27,
"bottled LPG": 3,
"LPG special condition": 17,
"oil": 28,
"electricity": 29,
"electricity (community)": 25,
"house coal": 33,
"smokeless coal": 15,
"dual fuel (mineral and wood)": 10,
"wood logs": 6,
"bulk wood pellets": 7,
"wood chips": 8,
"biomass (community)": 31,
}
_NOT_COMMUNITY_SUFFIX = " (not community)"
@dataclass(frozen=True)
@ -32,12 +94,30 @@ class HistoricConditioning:
total_floor_area_m2: Optional[float]
def _wall_construction(description: str) -> Optional[int]:
material = description.split(",", 1)[0].strip()
return _WALL_MATERIAL_CONSTRUCTION.get(material)
def _main_fuel(description: str) -> Optional[int]:
base = description.strip().removesuffix(_NOT_COMMUNITY_SUFFIX)
return _FUEL_CODES.get(base)
def _floor_area(raw: str) -> Optional[float]:
try:
area = float(raw)
except ValueError:
return None
return area if area > 0 else None
def conditioning_from_historic(record: HistoricEpc) -> HistoricConditioning:
return HistoricConditioning(
property_type=None,
built_form=None,
wall_construction=None,
construction_age_band=None,
main_fuel=None,
total_floor_area_m2=None,
property_type=property_type_to_code(record.property_type),
built_form=built_form_to_code(record.built_form),
wall_construction=_wall_construction(record.walls_description),
construction_age_band=_AGE_BAND_LETTERS.get(record.construction_age_band),
main_fuel=_main_fuel(record.main_fuel),
total_floor_area_m2=_floor_area(record.total_floor_area),
)