Map LbwfHouse to AssetCondition list 🟥

This commit is contained in:
Daniel Roth 2026-01-20 17:18:51 +00:00
parent 694bb0b569
commit c8abc19e59
5 changed files with 375 additions and 8 deletions

View file

@ -6,6 +6,9 @@ from backend.condition.domain.element import Element
@dataclass
class AssetCondition:
uprn: int
element: Element
condition_description: str
renewal_year: Optional[int] = None
element: Element # TODO: should HHSRS elements be handled differently?
condition_description: str # TODO: this probably needs to be some sort of enum so it's searchable/filterable on the frontend
quantity: int
renewal_year: Optional[int] = None
source: Optional[str] = None
# TODO: add install_date

View file

@ -1,4 +1,123 @@
from enum import Enum
from enum import StrEnum
class Element(Enum):
pass
class Element(StrEnum):
AHR_CAT = "Accessible Housing Register Category"
ASBESTOS = "Asbestos Present"
ASSETSAREA = "Assets Area for Decent Homes and Investment"
DECNTHMINC = "Include for Decent Homes Reporting - LBWF Stock"
EICINSFREQ = "EICR - Elec Install Conditions Report Inspection Frequency"
EXTBALCONY = "Private Balconies in External Area"
EXTBKSDDR1 = "Back and Side Doors 1 in External Area"
EXTBKSDDR2 = "Back and Side Doors 2 in External Area"
EXTBPOINTG = "Brickwork Pointing in External Area"
EXTCHIMNEY = "Chimneys in External Area"
EXTDWNPTYP = "Downpipes in External Area"
EXTDRPKERB = "Drop Kerb in External Area"
EXTEXTDECS = "External Decorations in External Area"
EXTFASOFBR = "Fascia / Soffit / Bargeboard in External Area"
EXTGARDOOR = "Garage Door in External Area"
EXTGARROOF = "Garage Roof in External Area"
EXTGARSTDR = "Garage and Store Doors in External Area"
EXTGARSTRF = "Garage and Store Roofs in External Area"
EXTGARSTWD = "Garage and Store Windows in External Area"
EXTGARWDWS = "Garage Windows in External Area"
EXTGUTRTYP = "Gutters in External Area"
EXTHARDSTD = "Hardstanding in External Area"
EXTINTDWNP = "Internal Downpipes in External Area"
EXTLINTELS = "Lintels in External Area"
EXTOUTBOH = "Overhaul of Outbuilding in External Area"
EXTPARKING = "Parking in External Area"
EXTPCHCNPY = "Porch and / or Canopy in External Area"
EXTPTFRDR1 = "Patio and French Doors 1 in External Area"
EXTROOF1 = "Roof Covering 1 in External Area"
EXTROOF2 = "Roof Covering 2 in External Area"
EXTROOF3 = "Roof Covering 3 in External Area"
EXTRFSTR1 = "Roof Structure 1 in External Area"
EXTRFSTR2 = "Roof Structure 2 in External Area"
EXTRFSTR3 = "Roof Structure 3 in External Area"
EXTSTOREY = "Number of Storeys within the Property or Block"
EXTSTRDOOR = "Store Door in External Area"
EXTSTRINSP = "Structural Defects in External Area"
EXTSTRROOF = "Store Roof in External Area"
EXTSTRWDWS = "Store Windows in External Area"
EXTWALLFN1 = "Wall Finish 1 in External Area"
EXTWALLFN2 = "Wall Finish 2 in External Area"
EXTWALLINS = "Wall Insulation Improvement in External Area"
EXTWALLSPL = "Wall Spalling in External Area"
EXTWALLSTR = "Wall Structure in External Area"
EXTWNDWS1 = "Windows 1 in External Area"
EXTWNDWS2 = "Windows 2 in External Area"
FFHHDAMP = "Fitness for Human Habitation - Serious problem with damp"
FFHHDRNWC = "Fitness for Human Habitation - Problems with the drainage or the lavatories"
FFHHHCWAT = "Fitness for Human Habitation - Problem with the supply of hot and cold water"
FFHHNEGLC = "Fitness for Human Habitation - Building neglected and is in a bad condition"
FFHHNONAT = "Fitness for Human Habitation - Not enough natural light"
FFHHNOVEN = "Fitness for Human Habitation - Not enough ventilation"
FFHHPRPCK = "Fitness for Human Habitation - Difficult to prepare and cook food or wash up"
FFHHUNLAY = "Fitness for Human Habitation - Unsafe layout"
FFHHUNSTA = "Fitness for Human Habitation - Building is unstable"
FRARISKRTG = "Fire Risk Assessment Rating"
FRAEVACSTR = "Fire Risk Assessment Evacuation Strategy"
FRATYPE = "Fire Risk Assessment Type"
FLVL = "Floor Level of Front Door"
HHSRSASB = "Asbestos (and MMF)"
HHSRSBIOC = "Biocides"
HHSRSCO = "Carbon monoxide"
HHSRSCOLD = "Excess cold"
HHSRSCLOW = "Collision hazards and low headroom"
HHSRSCROWD = "Crowding and space"
HHSRSDAMP = "Damp and mould growth"
HHSRSDOMES = "Domestic hygeine, Pests and Refuse"
HHSRSELEC = "Electrical hazards"
HHSRSENTRP = "Collision and entrapment"
HHSRSENTRY = "Entry by intruders"
HHSRSEXPLO = "Explosions"
HHSRSFBATH = "Falls associated with baths etc"
HHSRSFBETW = "Falling between levels"
HHSRSFIRE = "Fire"
HHSRSFLAME = "Flames, hot surfaces etc"
HHSRSFLEVE = "Falling on level surfaces etc"
HHSRSFOOD = "Food safety"
HHSRSFSTAI = "Falling on stairs etc"
HHSRSFUEL = "Uncombusted fuel gas"
HHSRSHEAT = "Excess heat"
HHSRSLEAD = "Lead"
HHSRSLIGHT = "Lighting"
HHSRSNO2 = "Nitrogen dioxide"
HHSRSNOISE = "Noise"
HHSRSORGAN = "Volatile organic compounds"
HHSRSPERS = "Personal hygeine, Sanitation and Drainage"
HHSRSPOSI = "Position and operability of amenities etc"
HHSRSRADIA = "Radiation"
HHSRSSO2 = "Sulphur dioxide and smoke"
HHSRSSTRUC = "Structural collapse and falling elements"
HHSRSWATER = "Water supply"
INTACCRAMP = "Access Ramp 1:12 Gradient to Property"
INTADDWCW = "Additional WCs and / or WHBs in Property"
INTBTHADEQ = "Adequacy of Bathroom Location in Property"
INTBTHREML = "Source of Bathroom Remaining Life in Property"
INTBTHRLOC = "Location of Bathroom in Property"
INTBOILERF = "Boiler Fuel in Property"
INTCHEXTNT = "Extent of Central Heating in Property"
INTCKRLOC = "Adequacy of Cooker Location in Property"
INTCOMHTG = "Community Heating in Property"
INTELECTRC = "Electrics Required in Property"
INTFLRLVL = "Floor Level Location for Property"
INTFRDOOR = "Type and Location of Front Door in Property"
INTFRDRFRR = "Front Door Fire Rating in Property"
INTGASAVAI = "Gas Available in Property"
INTHEATREC = "Heat Recovery Units in Property"
INTHTDISYS = "Heating Distribution System in Property"
INTHTIMP = "Heating Improvement Required in Property"
INTKITADEQ = "Adequacy of Kitchen and Type in Property"
INTKITREML = "Source of Kitchen Remaining Life in Property"
INTLOFTINS = "Size in mm of Loft Insulation Thickness in Property"
INTNSEINSL = "Adequacy of Noise Insulation in Property"
INTPROGHTG = "Programmable Heating in Property"
INTSMKDET = "Smoke Detectors in Property"
INTSTEPSFD = "Number of Steps to Front Door for Property"
INTTNTINST = "Tenant Installed Kitchen in Property"
INTWDWTYPE = "Windows in Property"
INTWTRHTNG = "Type of Water Heating in Property"
QUALITYSTD = "Quality standard"

View file

@ -1,9 +1,15 @@
from typing import Any, List
from typing import Any, List, Optional
from backend.condition.domain.asset_condition import AssetCondition
from backend.condition.domain.element import Element
from backend.condition.domain.mapping.mapper import Mapper
from backend.condition.parsing.records.lbwf.lbwf_asset_condition import LbwfAssetCondition
class LbwfMapper(Mapper):
def map_asset_conditions(self, client_data: List[Any]) -> List[AssetCondition]:
raise NotImplementedError
@staticmethod
def _map_element(lbwf_asset: LbwfAssetCondition) -> Optional[Element]:
raise NotImplementedError

View file

@ -0,0 +1,239 @@
from typing import List
import pytest
from datetime import date
from backend.condition.domain.mapping.lbwf_mapper import LbwfMapper
from backend.condition.parsing.records.lbwf.lbwf_house import LbwfHouse
from backend.condition.parsing.records.lbwf.lbwf_asset_condition import LbwfAssetCondition
from backend.condition.domain.element import Element
from backend.condition.domain.asset_condition import AssetCondition
def test_lbwf_mapper_maps_house():
# arrange
lbwf_house = LbwfHouse(
uprn=1,
reference=100,
address="123 Fake Street, London, A10 1AB",
epc="F",
shdf="NO",
house="HOUSE",
fail_decency=2025,
assets=[
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="AHR_CAT",
element_code_description="Accessible Housing Register Category",
attribute_code="F",
attribute_code_description="General Needs",
element_date_value=None,
element_numerical_value=None,
element_text_value=None,
quantity=1,
install_date=None,
remaining_life=None,
element_comments=None,
),
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="FLVL",
element_code_description="Floor Level of Front Door",
attribute_code="0G",
attribute_code_description="Ground Floor",
element_date_value=None,
element_numerical_value=None,
element_text_value=None,
quantity=1,
install_date=None,
remaining_life=None,
element_comments=None,
),
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="ASBESTOS",
element_code_description="Asbestos Present",
attribute_code="YES",
attribute_code_description="Yes",
element_date_value=None,
element_numerical_value=None,
element_text_value=None,
quantity=None,
install_date=None,
remaining_life=None,
element_comments="Source of Data = ACT",
),
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="INTBTHRLOC",
element_code_description="Location of Bathroom in Property",
attribute_code="ENTRANCE",
attribute_code_description="Bathroom on Entrance Level in Property",
element_date_value=None,
element_numerical_value=None,
element_text_value=None,
quantity=1,
install_date=None,
remaining_life=None,
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="INTCHEXTNT",
element_code_description="Extent of Central Heating in Property",
attribute_code="NONE",
attribute_code_description="No Central Heating in Property",
element_date_value=None,
element_numerical_value=None,
element_text_value=None,
quantity=1,
install_date=None,
remaining_life=None,
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="HHSRSFIRE",
element_code_description="Fire",
attribute_code="TYPRISK",
attribute_code_description="Category 4 - Typical Risk",
element_date_value=None,
element_numerical_value=None,
element_text_value=None,
quantity=1,
install_date=None,
remaining_life=None,
element_comments="Source of Data = Morgan Sindall",
),
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="EXTWALLFN1",
element_code_description="Wall Finish 1 in External Area",
attribute_code="RENDERPBBL",
attribute_code_description="Render or Pebbledash Wall Finish 1 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",
),
]
)
mapper = LbwfMapper()
current_year = 2026
expected_assets: List[AssetCondition] = [
AssetCondition(
uprn=1,
element=Element.AHR_CAT,
condition_description="General Needs",
quantity=1,
renewal_year=None,
source=None
),
AssetCondition(
uprn=1,
element=Element.FLVL,
condition_description="Ground Floor",
quantity=1,
renewal_year=None,
source=None
),
AssetCondition(
uprn=1,
element=Element.ASBESTOS,
condition_description="Yes",
quantity=None,
renewal_year=None,
source="Source of Data = ACT"
),
AssetCondition(
uprn=1,
element=Element.INTBTHRLOC,
condition_description="Bathroom on Entrance Level in Property",
quantity=1,
renewal_year=None,
source="Source of Data = Codeman"
),
AssetCondition(
uprn=1,
element=Element.INTCHEXTNT,
condition_description="No Central Heating in Property",
quantity=1,
renewal_year=None,
source="Source of Data = Codeman"
),
AssetCondition(
uprn=1,
element=Element.HHSRSFIRE,
condition_description="Category 4 - Typical Risk",
quantity=1,
renewal_year=None,
source="Source of Data = Morgan Sindall"
),
AssetCondition(
uprn=1,
element=Element.EXTWALLFN1,
condition_description="Render or Pebbledash Wall Finish 1 in External Area",
quantity=1,
renewal_year=2052,
source="Source of Data = Codeman"
),
]
# act
actual_assets: List[AssetCondition] = mapper.map_asset_conditions(lbwf_house)
# assert
assert actual_assets == expected_assets

View file

@ -112,7 +112,7 @@ def lbwf_homes_xlsx_bytes() -> BytesIO:
return stream
def test_lbwf_parser_passes_houses(lbwf_homes_xlsx_bytes):
def test_lbwf_parser_parses_houses(lbwf_homes_xlsx_bytes):
# arrange
parser = LbwfParser()