Map to new dataclasses from LBWF objects 🟩

This commit is contained in:
Daniel Roth 2026-01-22 16:28:10 +00:00
parent fa72d16239
commit 212d62e835
6 changed files with 417 additions and 23 deletions

View file

@ -5,6 +5,7 @@ class AspectType(str, Enum):
MATERIAL = "material"
CONDITION = "condition"
TYPE = "type"
AREA = "area"
CONFIGURATION = "configuration"
PRESENCE = "presence"
RISK = "risk"
@ -18,3 +19,11 @@ class AspectType(str, Enum):
CLADDING = "cladding"
CATEGORY = "category"
QUANTITY = "quantity"
ADEQUACY = "adequacy"
RATING = "rating"
STRATEGY = "strategy"
EXTENT = "extent"
DISTRIBUTION = "distribution"
STRUCTURE = "structure"
COVERING = "covering"
FIRE_RATING = "fire_rating"

View file

@ -21,7 +21,6 @@ class AssetCondition:
renewal_year: Optional[int] = None
element_instance: Optional[int] = None
element_location: Optional[str] = None
source_system: Optional[str] = None
comments: Optional[str] = None

View file

@ -6,7 +6,7 @@ class Element(str, Enum):
# ======================
# PROPERTY / GENERAL
# ======================
PROPERTY_TYPE = "property_type"
PROPERTY = "property"
PROPERTY_CONSTRUCTION_TYPE = "property_construction_type"
PROPERTY_CLASSIFICATION = "property_classification"
PROPERTY_AGE_BAND = "property_age_band"
@ -14,18 +14,15 @@ class Element(str, Enum):
FLOOR_LEVEL_FRONT_DOOR = "floor_level_front_door"
ACCESSIBLE_HOUSING_REGISTER = "accessible_housing_register"
ASBESTOS = "asbestos"
QUALITY_STANDARD = "quality_standard"
# ======================
# EXTERNAL ROOF
# ======================
ROOF_COVERING = "roof_covering"
ROOF_STRUCTURE = "roof_structure"
ROOF_CHIMNEY = "roof_chimney"
ROOF_FASCIA = "roof_fascia"
ROOF_SOFFIT = "roof_soffit"
ROOF = "roof"
RAINWATER_GOODS = "rainwater_goods"
ROOF_PORCH_CANOPY = "roof_porch_canopy"
LOFT_INSULATION = "loft_insulation"
PORCH_CANOPY = "porch_canopy"
# ======================
# EXTERNAL WALLS
@ -35,13 +32,14 @@ class Element(str, Enum):
# ======================
# EXTERNAL WINDOWS
# ======================
WINDOWS = "windows"
EXTERNAL_WINDOWS = "external_windows"
COMMUNAL_WINDOWS = "communal_windows"
SECONDARY_GLAZING = "secondary_glazing"
# ======================
# EXTERNAL DOORS
# ======================
EXTERNAL_DOOR = "external_door"
FRONT_DOOR = "front_door"
REAR_DOOR = "rear_door"
STORE_DOOR = "store_door"
@ -84,6 +82,9 @@ class Element(str, Enum):
HOT_WATER_SYSTEM = "hot_water_system"
COLD_WATER_STORAGE = "cold_water_storage"
PROGRAMMABLE_HEATING = "programmable_heating"
HEATING_SYSTEM = "heating_system"
BOILER_FUEL = "boiler_fuel"
WATER_HEATING = "water_heating"
# ======================
# INTERNAL ELECTRICS / FIRE
@ -94,6 +95,7 @@ class Element(str, Enum):
HEAT_DETECTION = "heat_detection"
CARBON_MONOXIDE_DETECTION = "carbon_monoxide_detection"
FIRE_DOOR_RATING = "fire_door_rating"
FIRE_RISK_ASSESSMENT = "fire"
# ======================
# COMMUNAL SYSTEMS

View file

@ -0,0 +1,351 @@
from dataclasses import dataclass
from typing import Optional
from backend.condition.domain.element import Element
from backend.condition.domain.aspect_type import AspectType
@dataclass(frozen=True)
class LbwfElementMapping:
element: Element
aspect_type: AspectType
element_instance: Optional[int] = None
LBWF_ELEMENT_MAP: dict[str, LbwfElementMapping] = {
# ==========================================================
# PROPERTY / GENERAL
# ==========================================================
"AHR_CAT": LbwfElementMapping(
element=Element.ACCESSIBLE_HOUSING_REGISTER,
aspect_type=AspectType.CATEGORY,
),
"ASSETSAREA": LbwfElementMapping(
element=Element.PROPERTY,
aspect_type=AspectType.AREA,
),
# "DECNTHMINC": LbwfElementMapping(
# element=Element.DECENT_HOMES,
# aspect_type=AspectType.INCLUSION,
# ), # Ignore this one
"QUALITYSTD": LbwfElementMapping(
element=Element.QUALITY_STANDARD,
aspect_type=AspectType.TYPE,
),
"EXTSTOREY": LbwfElementMapping(
element=Element.PROPERTY,
aspect_type=AspectType.CONFIGURATION,
),
"FLVL": LbwfElementMapping(
element=Element.FLOOR_LEVEL_FRONT_DOOR,
aspect_type=AspectType.LOCATION,
),
# ==========================================================
# ASBESTOS (NON-HHSRS RECORD)
# ==========================================================
"ASBESTOS": LbwfElementMapping(
element=Element.ASBESTOS,
aspect_type=AspectType.PRESENCE,
),
# ==========================================================
# INTERNAL BATHROOMS & KITCHENS
# ==========================================================
"INTBTHRLOC": LbwfElementMapping(
element=Element.BATHROOM,
aspect_type=AspectType.LOCATION,
),
"INTBTHADEQ": LbwfElementMapping(
element=Element.BATHROOM,
aspect_type=AspectType.ADEQUACY,
),
"INTKITADEQ": LbwfElementMapping(
element=Element.KITCHEN,
aspect_type=AspectType.ADEQUACY,
),
"INTCKRLOC": LbwfElementMapping(
element=Element.KITCHEN,
aspect_type=AspectType.LOCATION,
),
# ==========================================================
# INTERNAL HEATING
# ==========================================================
"INTCHEXTNT": LbwfElementMapping(
element=Element.HEATING_EXTENT,
aspect_type=AspectType.CONFIGURATION,
),
"INTCHDIST": LbwfElementMapping(
element=Element.HEATING_DISTRIBUTION,
aspect_type=AspectType.TYPE,
),
"INTCHBLR": LbwfElementMapping(
element=Element.HEATING_BOILER,
aspect_type=AspectType.TYPE,
),
# ==========================================================
# INTERNAL FIRE
# ==========================================================
"FRARISKRTG": LbwfElementMapping(
element=Element.FIRE_RISK_ASSESSMENT,
aspect_type=AspectType.RATING,
),
"FRATYPE": LbwfElementMapping(
element=Element.FIRE_RISK_ASSESSMENT,
aspect_type=AspectType.TYPE,
),
"FRAEVACSTR": LbwfElementMapping(
element=Element.FIRE_RISK_ASSESSMENT,
aspect_type=AspectType.STRATEGY,
),
"INTSMKDET": LbwfElementMapping(
element=Element.SMOKE_DETECTION,
aspect_type=AspectType.PRESENCE,
),
"INTCHEXTNT": LbwfElementMapping(
element=Element.HEATING_SYSTEM,
aspect_type=AspectType.EXTENT,
),
# ==========================================================
# HEATING & SERVICES
# ==========================================================
"INTBOILERF": LbwfElementMapping(
element=Element.BOILER_FUEL,
aspect_type=AspectType.TYPE,
),
"INTHTDISYS": LbwfElementMapping(
element=Element.HEATING_SYSTEM,
aspect_type=AspectType.DISTRIBUTION,
),
"INTWTRHTNG": LbwfElementMapping(
element=Element.WATER_HEATING,
aspect_type=AspectType.TYPE,
),
# ==========================================================
# EXTERNAL WALLS (INSTANCED)
# ==========================================================
"EXTWALLSTR": LbwfElementMapping(
element=Element.EXTERNAL_WALL,
aspect_type=AspectType.STRUCTURE,
element_instance=1,
),
"EXTWALLFN1": LbwfElementMapping(
element=Element.EXTERNAL_WALL,
aspect_type=AspectType.FINISH,
element_instance=1,
),
"EXTWALLFN2": LbwfElementMapping(
element=Element.EXTERNAL_WALL,
aspect_type=AspectType.FINISH,
element_instance=2,
),
"EXTWALLINS": LbwfElementMapping(
element=Element.EXTERNAL_WALL,
aspect_type=AspectType.INSULATION,
),
"EXTWALLSPL": LbwfElementMapping(
element=Element.EXTERNAL_WALL,
aspect_type=AspectType.CONDITION,
),
# ==========================================================
# EXTERNAL ROOFS (INSTANCED)
# ==========================================================
"EXTRFSTR1": LbwfElementMapping(
element=Element.ROOF,
aspect_type=AspectType.STRUCTURE,
element_instance=1,
),
"EXTRFSTR2": LbwfElementMapping(
element=Element.ROOF,
aspect_type=AspectType.STRUCTURE,
element_instance=2,
),
"EXTRFSTR3": LbwfElementMapping(
element=Element.ROOF,
aspect_type=AspectType.STRUCTURE,
element_instance=3,
),
"EXTROOF1": LbwfElementMapping(
element=Element.ROOF,
aspect_type=AspectType.COVERING,
element_instance=1,
),
"EXTROOF2": LbwfElementMapping(
element=Element.ROOF,
aspect_type=AspectType.COVERING,
element_instance=2,
),
"EXTROOF3": LbwfElementMapping(
element=Element.ROOF,
aspect_type=AspectType.COVERING,
element_instance=3,
),
# ==========================================================
# EXTERNAL DOORS & WINDOWS
# ==========================================================
"INTFRDOOR": LbwfElementMapping(
element=Element.EXTERNAL_DOOR,
aspect_type=AspectType.TYPE,
),
"INTFRDRFRR": LbwfElementMapping(
element=Element.EXTERNAL_DOOR,
aspect_type=AspectType.FIRE_RATING,
),
"EXTBKSDDR1": LbwfElementMapping(
element=Element.EXTERNAL_DOOR,
aspect_type=AspectType.TYPE,
element_instance=1,
),
"EXTBKSDDR2": LbwfElementMapping(
element=Element.EXTERNAL_DOOR,
aspect_type=AspectType.TYPE,
element_instance=2,
),
"INTWDWTYPE": LbwfElementMapping(
element=Element.EXTERNAL_WINDOWS,
aspect_type=AspectType.TYPE,
),
"EXTWNDWS1": LbwfElementMapping(
element=Element.EXTERNAL_WINDOWS,
aspect_type=AspectType.TYPE,
element_instance=1,
),
"EXTWNDWS2": LbwfElementMapping(
element=Element.EXTERNAL_WINDOWS,
aspect_type=AspectType.TYPE,
element_instance=2,
),
# ==========================================================
# HHSRS PHYSIOLOGICAL REQUIREMENTS
# ==========================================================
"HHSRSDAMP": LbwfElementMapping(
element=Element.HHSRS_DAMP_AND_MOULD,
aspect_type=AspectType.RISK,
),
"HHSRSCOLD": LbwfElementMapping(
element=Element.HHSRS_EXCESS_COLD,
aspect_type=AspectType.RISK,
),
"HHSRSHEAT": LbwfElementMapping(
element=Element.HHSRS_EXCESS_HEAT,
aspect_type=AspectType.RISK,
),
"HHSRSASB": LbwfElementMapping(
element=Element.HHSRS_ASBESTOS_AND_MMF,
aspect_type=AspectType.RISK,
),
# ==========================================================
# HHSRS PSYCHOLOGICAL REQUIREMENTS
# ==========================================================
"HHSRSCROWD": LbwfElementMapping(
element=Element.HHSRS_CROWDING_AND_SPACE,
aspect_type=AspectType.RISK,
),
"HHSRSENTRY": LbwfElementMapping(
element=Element.HHSRS_ENTRY_BY_INTRUDERS,
aspect_type=AspectType.RISK,
),
"HHSRSENTRP": LbwfElementMapping( # collision / entrapment
element=Element.HHSRS_COLLISION_AND_ENTRAPMENT,
aspect_type=AspectType.RISK,
),
"HHSRSLIGHT": LbwfElementMapping(
element=Element.HHSRS_LIGHTING,
aspect_type=AspectType.RISK,
),
"HHSRSNOISE": LbwfElementMapping(
element=Element.HHSRS_NOISE,
aspect_type=AspectType.RISK,
),
# ==========================================================
# HHSRS PROTECTION AGAINST INFECTION
# ==========================================================
"HHSRSDOMES": LbwfElementMapping(
element=Element.HHSRS_DOMESTIC_HYGIENE_PESTS_REFUSE,
aspect_type=AspectType.RISK,
),
"HHSRSFOOD": LbwfElementMapping(
element=Element.HHSRS_FOOD_SAFETY,
aspect_type=AspectType.RISK,
),
"HHSRSPERS": LbwfElementMapping(
element=Element.HHSRS_PERSONAL_HYGIENE_SANITATION,
aspect_type=AspectType.RISK,
),
"HHSRSWATER": LbwfElementMapping(
element=Element.HHSRS_WATER_SUPPLY,
aspect_type=AspectType.RISK,
),
"HHSRSFBATH": LbwfElementMapping(
element=Element.HHSRS_FALLS_ASSOCIATED_WITH_BATHS,
aspect_type=AspectType.RISK,
),
"HHSRSPOSI": LbwfElementMapping(
element=Element.HHSRS_SURFACES_MOULD,
aspect_type=AspectType.RISK,
),
# ==========================================================
# HHSRS PROTECTION AGAINST ACCIDENTS
# ==========================================================
"HHSRSFLEVE": LbwfElementMapping(
element=Element.HHSRS_FALLS_ON_LEVEL_SURFACES,
aspect_type=AspectType.RISK,
),
"HHSRSFSTAI": LbwfElementMapping(
element=Element.HHSRS_FALLS_ON_STAIRS,
aspect_type=AspectType.RISK,
),
"HHSRSFBETW": LbwfElementMapping(
element=Element.HHSRS_FALLS_BETWEEN_LEVELS,
aspect_type=AspectType.RISK,
),
"HHSRSELEC": LbwfElementMapping(
element=Element.HHSRS_ELECTRICAL_HAZARDS,
aspect_type=AspectType.RISK,
),
"HHSRSFIRE": LbwfElementMapping(
element=Element.HHSRS_FIRE,
aspect_type=AspectType.RISK,
),
"HHSRSFLAME": LbwfElementMapping(
element=Element.HHSRS_FLAMES_HOT_SURFACES,
aspect_type=AspectType.RISK,
),
"HHSRSEXPLO": LbwfElementMapping(
element=Element.HHSRS_EXPLOSION,
aspect_type=AspectType.RISK,
),
"HHSRSSTRUC": LbwfElementMapping(
element=Element.HHSRS_STRUCTURAL_COLLAPSE,
aspect_type=AspectType.RISK,
),
# ==========================================================
# HHSRS PROTECTION AGAINST POLLUTION
# ==========================================================
"HHSRSCO": LbwfElementMapping(
element=Element.HHSRS_CARBON_MONOXIDE,
aspect_type=AspectType.RISK,
),
"HHSRSFUEL": LbwfElementMapping(
element=Element.HHSRS_UNSAFE_GAS,
aspect_type=AspectType.RISK,
),
"HHSRSNO2": LbwfElementMapping(
element=Element.HHSRS_UNCOMBUSTED_FUEL_GAS,
aspect_type=AspectType.RISK,
),
"HHSRSSO2": LbwfElementMapping(
element=Element.HHSRS_UNCOMBUSTED_FUEL_GAS,
aspect_type=AspectType.RISK,
),
"HHSRSLEAD": LbwfElementMapping(
element=Element.HHSRS_LEAD,
aspect_type=AspectType.RISK,
),
"HHSRSRADIA": LbwfElementMapping(
element=Element.HHSRS_RADIATION,
aspect_type=AspectType.RISK,
),
"HHSRSBIOC": LbwfElementMapping(
element=Element.HHSRS_UNCOMBUSTED_FUEL_GAS,
aspect_type=AspectType.RISK,
),
}

View file

@ -1,7 +1,11 @@
from typing import Any, List, Optional
from backend.condition.domain.asset_condition import AssetCondition
from backend.condition.domain.lbwf_element import LbwfElement
from backend.condition.domain.element import Element
from backend.condition.domain.mapping.lbwf.lbwf_element_map import (
LbwfElementMapping,
LBWF_ELEMENT_MAP,
)
from backend.condition.domain.mapping.mapper import Mapper
from backend.condition.parsing.records.lbwf.lbwf_asset_condition import (
LbwfAssetCondition,
@ -26,7 +30,9 @@ class LbwfMapper(Mapper):
uprn: int = client_data.uprn
for raw_asset in client_data.assets:
try:
element: LbwfElement = LbwfMapper._map_element(raw_asset.element_code)
element_mapping: LbwfElementMapping = LbwfMapper._map_element(
raw_asset.element_code
)
except:
logger.warning(
f"Unrecognised LBWF Asset Element Code: {raw_asset.element_code}. Skipping record"
@ -36,22 +42,25 @@ class LbwfMapper(Mapper):
mapped_assets.append(
AssetCondition(
uprn=uprn,
element=element,
condition_description=raw_asset.attribute_code_description,
element=element_mapping.element,
aspect_type=element_mapping.aspect_type,
value=raw_asset.attribute_code_description,
quantity=raw_asset.quantity,
install_date=raw_asset.install_date,
renewal_year=LbwfMapper._calculate_renewal_year(
raw_asset, survey_year
),
source=raw_asset.element_comments,
install_date=raw_asset.install_date,
element_instance=element_mapping.element_instance,
source_system=None, # Once we know the system name we'll set it here
comments=raw_asset.element_comments,
)
)
return mapped_assets
@staticmethod
def _map_element(lbwf_element_code: LbwfAssetCondition) -> LbwfElement:
return LbwfElement[lbwf_element_code]
def _map_element(lbwf_element_code: str) -> LbwfElementMapping:
return LBWF_ELEMENT_MAP[lbwf_element_code]
@staticmethod
def _calculate_renewal_year(

View file

@ -181,9 +181,30 @@ def test_lbwf_mapper_maps_house():
prop_sub_type="TERRACED",
element_group="ASSETS",
element_code="EXTWALLFN1",
element_code_description="Wall Finish 1 in External Area",
attribute_code="SMTHRENDER",
attribute_code_description="Render or Pebbledash in External Area",
element_date_value=None,
element_numerical_value=None,
element_text_value=None,
quantity=1,
install_date=date(2009, 4, 1),
remaining_life=26,
element_comments="Source of Data = Codeman",
),
LbwfAssetCondition(
prop_ref=100,
domna=100,
address="123 Fake Street, London, A10 1AB",
ownership="LBWF_OWNED",
prop_status="OCCP",
prop_type="HOU",
prop_sub_type="TERRACED",
element_group="ASSETS",
element_code="EXTWALLFN2",
element_code_description="Wall Finish 2 in External Area",
attribute_code="SMTHRENDER",
attribute_code_description="Smooth Render Wall Finish 1 in External Area",
attribute_code_description="Smooth Render Wall Finish 2 in External Area",
element_date_value=None,
element_numerical_value=None,
element_text_value=None,
@ -256,8 +277,8 @@ def test_lbwf_mapper_maps_house():
),
AssetCondition(
uprn=1,
element=Element.HEATING_EXTENT,
aspect_type=AspectType.CONFIGURATION,
element=Element.HEATING_SYSTEM,
aspect_type=AspectType.EXTENT,
element_instance=None,
value="No Central Heating in Property",
quantity=1,
@ -281,7 +302,7 @@ def test_lbwf_mapper_maps_house():
element=Element.EXTERNAL_WALL,
aspect_type=AspectType.FINISH,
element_instance=1,
value="Render or Pebbledash",
value="Render or Pebbledash in External Area",
quantity=1,
renewal_year=2052,
install_date=date(2009, 4, 1),
@ -292,7 +313,7 @@ def test_lbwf_mapper_maps_house():
element=Element.EXTERNAL_WALL,
aspect_type=AspectType.FINISH,
element_instance=2,
value="Smooth Render Wall Finish 1 in External Area",
value="Smooth Render Wall Finish 2 in External Area",
quantity=1,
renewal_year=2052,
install_date=date(2009, 4, 1),
@ -306,4 +327,7 @@ def test_lbwf_mapper_maps_house():
)
# assert
assert actual_assets == expected_assets
assert len(actual_assets) == len(expected_assets)
for i, (actual, expected) in enumerate(zip(actual_assets, expected_assets)):
assert actual == expected, f"Mismatch at index {i}"