applying floor transformations

This commit is contained in:
Khalim Conn-Kowlessar 2026-02-02 19:45:08 +00:00
parent 2631b4aa20
commit 918e5fd8ce
2 changed files with 143 additions and 1 deletions

View file

@ -715,3 +715,19 @@ def resolve_roof_efficiency(
except TypeError:
# Fallback to (age_band)
return rule(age_band)
class EpcFloorDescriptions(Enum):
# Solid floor
solid_insulated = "Solid, insulated"
solid_insulated_assumed = "Solid, insulated (assumed)"
solid_no_insulation_assumed = "Solid, no insulation (assumed)"
solid_limited_insulation_assumed = "Solid, limited insulation (assumed)"
# Suspended floor
suspended_insulated = "Suspended, insulated"
suspended_insulated_assumed = "Suspended, insulated (assumed)"
suspended_no_insulation_assumed = "Suspended, no insulation (assumed)"
suspended_limited_insulation_assumed = "Suspended, limited insulation (assumed)"
unknown = None # We don't resolve anything

View file

@ -6,7 +6,7 @@ from backend.onboarders.mappings.property_type import parity_map as property_map
from backend.onboarders.mappings.age_band import parity_map as age_band_map
from backend.onboarders.mappings.built_form import parity_map as built_form_map
from backend.onboarders.epc_descriptions import EpcWallDescriptions, EpcConstructionAgeBand, EpcEfficiency, \
WALL_DESCRIPTION_EFFICIENCIES, EpcRoofDescriptions, resolve_roof_efficiency
WALL_DESCRIPTION_EFFICIENCIES, EpcRoofDescriptions, resolve_roof_efficiency, EpcFloorDescriptions
from backend.onboarders.mappings.as_built_wall_classifiers import AS_BUILT_WALL_CLASSIFIERS
from backend.onboarders.mappings.as_built_roof_classifiers import AS_BUILT_ROOF_CLASSIFIERS
@ -352,6 +352,132 @@ data["has_sloping_ceiling"] = data["Roof Construction"].apply(
lambda x: x == "PitchedWithSlopingCeiling"
)
# ------------ Floor Construction ------------
floor_mapping = {
# Solid floor
('Solid', 'AsBuilt'): None, # Mapped
('Solid', 'Unknown'): None, # Mapped
('Solid', nan): None, # Mapped
('Solid', 'RetroFitted'): EpcFloorDescriptions.solid_insulated,
# Suspended floor
('SuspendedTimber', nan): None, # Mapped suspended_floor_as_built
('SuspendedTimber', 'AsBuilt'): None, # Mapped suspended_floor_as_built
('SuspendedTimber', 'RetroFitted'): EpcFloorDescriptions.suspended_insulated,
('SuspendedTimber', 'Unknown'): None, # Mapped suspended_floor_as_built
('SuspendedNotTimber', 'RetroFitted'): EpcFloorDescriptions.suspended_insulated,
('SuspendedNotTimber', nan): None, # Mapped suspended_floor_as_built
('SuspendedNotTimber', 'Unknown'): None, # Mapped suspended_floor_as_built
('SuspendedNotTimber', 'AsBuilt'): None, # Mapped suspended_floor_as_built
# Unknown type - mapped on age
('Unknown', 'Unknown'): None, # Mapped unknown_floor_as_built
('Unknown', 'RetroFitted'): None, # Mapped unknown_floor_retrofitted
(nan, nan): None, # No actual information!
('Unknown', 'AsBuilt'): None, # Mapped unknown_floor_as_built
}
# Unknown floor, as built
# Before 1900, 1900 - 1929 -> Suspended, no insulation (assumed)
# 1930-1949, 1950 - 1966, 1967 - 1975, 1976-1982, 1983-1990, 1991-1995, -> Solid, no insulation (assumed)
# 1996 - 2002, Solid, limited insulation (assumed)
# 2003 onwards -> Solid, insulated (assumed)
def unknown_floor_as_built(age_band: EpcConstructionAgeBand) -> EpcFloorDescriptions:
year = age_band.start_year()
if year >= 2003:
return EpcFloorDescriptions.solid_insulated_assumed
if year >= 1930:
return EpcFloorDescriptions.solid_no_insulation_assumed
return EpcFloorDescriptions.suspended_no_insulation_assumed
# before 1900, 1900-1929 -> Suspended, insulated
# Thereafter, 1930 onwards -> Solid, insulated
def unknown_floor_retrofitted(age_band: EpcConstructionAgeBand) -> EpcFloorDescriptions:
year = age_band.start_year()
if year >= 1930:
return EpcFloorDescriptions.solid_insulated
return EpcFloorDescriptions.suspended_insulated
# 2003 - 2006, 2023 onwards -> Solid, insulated (assumed)
# 1996 - 2022 -> Solid, limited insulation (assumed)
# 1983 - 1990, 1991 - 1995 -> Solid, no insulation (assumed)
def solid_floor_as_built(age_band: EpcConstructionAgeBand) -> EpcFloorDescriptions:
year = age_band.start_year()
if year >= 2003:
return EpcFloorDescriptions.solid_insulated_assumed
if year >= 1996:
return EpcFloorDescriptions.solid_limited_insulation_assumed
return EpcFloorDescriptions.solid_no_insulation_assumed
# 2003 -> 2006 -> Suspended, insulated (assumed)
# 1996 - 2022 -> Suspended, limited insulation (assumed)
# 1983 - 1995 -> Suspended, no insulation (assumed)
def suspended_floor_as_built(age_band: EpcConstructionAgeBand) -> EpcFloorDescriptions:
year = age_band.start_year()
if year >= 2003:
return EpcFloorDescriptions.suspended_insulated_assumed
if year >= 1996:
return EpcFloorDescriptions.suspended_limited_insulation_assumed
return EpcFloorDescriptions.suspended_no_insulation_assumed
data["landlord_floor_description"] = (
data[["Floor Construction", "Floor Insulation"]]
.progress_apply(tuple, axis=1)
.map(floor_mapping)
)
def fill_floor_as_built(row):
# 1. Already resolved
if row.landlord_floor_description is not None:
return row.landlord_floor_description
age_band = row.construction_age_band
floor_type = row["Floor Construction"]
insulation = row["Floor Insulation"]
# 2. Missing age band → conservative fallback
if pd.isnull(age_band):
return EpcFloorDescriptions.unknown
# 3. Known floor types
if floor_type == "Solid":
return solid_floor_as_built(age_band)
if floor_type in {"SuspendedTimber", "SuspendedNotTimber"}:
return suspended_floor_as_built(age_band)
# 4. Unknown floor type
if floor_type == "Unknown":
if insulation == "RetroFitted":
return unknown_floor_retrofitted(age_band)
return unknown_floor_as_built(age_band)
# 5. Truly missing / garbage input
return EpcFloorDescriptions.unknown
data["landlord_floor_description"] = data.progress_apply(
fill_floor_as_built,
axis=1,
)
# Variables we want to map
# 'Org Ref', 'Address 1', 'Address 2', 'Address 3', 'Postcode',
# 'Floor Construction', 'Floor Insulation', 'Glazing', 'Heating',