From bbfcf1385b4bfd45895d406620002122b32b66dd Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 6 Jul 2026 08:22:31 +0000 Subject: [PATCH] =?UTF-8?q?Resolve=20a=20historic=20EPC's=20stable=20attri?= =?UTF-8?q?butes=20into=20cohort=20code=20spaces=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../epc_prediction/historic_conditioning.py | 92 +++++++++++++++++-- 1 file changed, 86 insertions(+), 6 deletions(-) diff --git a/domain/epc_prediction/historic_conditioning.py b/domain/epc_prediction/historic_conditioning.py index ca0d318f2..c52f94f91 100644 --- a/domain/epc_prediction/historic_conditioning.py +++ b/domain/epc_prediction/historic_conditioning.py @@ -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), )