mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Roof and wall tests green
This commit is contained in:
parent
90389ec3c2
commit
2631b4aa20
11 changed files with 596 additions and 183 deletions
2
.idea/Model.iml
generated
2
.idea/Model.iml
generated
|
|
@ -7,7 +7,7 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="AssetList" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Fastapi-backend" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
|
@ -3,7 +3,7 @@
|
|||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.10 (backend)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="AssetList" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Fastapi-backend" project-jdk-type="Python SDK" />
|
||||
<component name="PyCharmProfessionalAdvertiser">
|
||||
<option name="shown" value="true" />
|
||||
</component>
|
||||
|
|
|
|||
0
backend/onboarders/__init__.py
Normal file
0
backend/onboarders/__init__.py
Normal file
|
|
@ -1,3 +1,4 @@
|
|||
import pandas as pd
|
||||
import re
|
||||
from collections.abc import Mapping
|
||||
from enum import Enum
|
||||
|
|
@ -211,12 +212,6 @@ class EpcEfficiency(Enum):
|
|||
NA = "N/A"
|
||||
|
||||
|
||||
EfficiencyRule = Union[
|
||||
EpcEfficiency,
|
||||
Callable[[EpcConstructionAgeBand], EpcEfficiency],
|
||||
]
|
||||
|
||||
|
||||
def cavity_filled_efficiency(age_band: EpcConstructionAgeBand) -> EpcEfficiency:
|
||||
""""
|
||||
Maps cavity filled to efficiency based on construction age band.
|
||||
|
|
@ -343,6 +338,16 @@ WALL_DESCRIPTION_EFFICIENCIES: Mapping[EpcWallDescriptions, WallEfficiencyRule]
|
|||
# Cob (special case)
|
||||
EpcWallDescriptions.cob_as_built_average: EpcEfficiency.AVERAGE,
|
||||
EpcWallDescriptions.cob_as_built_good: EpcEfficiency.GOOD,
|
||||
|
||||
# Unknown mappings which are unhandled
|
||||
EpcWallDescriptions.cavity_as_built_unknown: EpcEfficiency.NA,
|
||||
EpcWallDescriptions.solid_brick_as_built_unknown: EpcEfficiency.NA,
|
||||
EpcWallDescriptions.system_as_built_unknown: EpcEfficiency.NA,
|
||||
EpcWallDescriptions.timber_frame_as_built_unknown: EpcEfficiency.NA,
|
||||
EpcWallDescriptions.granite_as_built_unknown: EpcEfficiency.NA,
|
||||
EpcWallDescriptions.sandstone_as_built_unknown: EpcEfficiency.NA,
|
||||
EpcWallDescriptions.cob_as_built_unknown: EpcEfficiency.NA,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -676,3 +681,37 @@ ROOF_DESCRIPTION_EFFICIENCIES: Mapping[EpcRoofDescriptions, RoofEfficiencyRule]
|
|||
EpcRoofDescriptions.sloping_pitched_no_insulation: EpcEfficiency.VERY_POOR,
|
||||
|
||||
}
|
||||
|
||||
|
||||
def resolve_roof_efficiency(
|
||||
description: EpcRoofDescriptions,
|
||||
age_band: EpcConstructionAgeBand | None,
|
||||
insulation_thickness: int | None,
|
||||
) -> EpcEfficiency:
|
||||
"""
|
||||
Resolve roof efficiency from description + age band + insulation thickness.
|
||||
"""
|
||||
|
||||
# Unknown / holding descriptions → efficiency unknown
|
||||
if description in description.unknown_descriptions:
|
||||
return EpcEfficiency.NA
|
||||
|
||||
rule = ROOF_DESCRIPTION_EFFICIENCIES.get(description)
|
||||
|
||||
if rule is None:
|
||||
return EpcEfficiency.NA
|
||||
|
||||
# Fixed efficiency
|
||||
if isinstance(rule, EpcEfficiency):
|
||||
return rule
|
||||
|
||||
# Callable rule
|
||||
if age_band is None or pd.isnull(age_band):
|
||||
return EpcEfficiency.NA
|
||||
|
||||
try:
|
||||
# Try (thickness, age_band)
|
||||
return rule(insulation_thickness, age_band)
|
||||
except TypeError:
|
||||
# Fallback to (age_band)
|
||||
return rule(age_band)
|
||||
|
|
|
|||
55
backend/onboarders/mappings/as_built_roof_classifiers.py
Normal file
55
backend/onboarders/mappings/as_built_roof_classifiers.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
from backend.onboarders.epc_descriptions import EpcConstructionAgeBand, EpcRoofDescriptions
|
||||
|
||||
|
||||
def classify_flat_roof(age_band: EpcConstructionAgeBand) -> EpcRoofDescriptions:
|
||||
"""
|
||||
For a flat, as built roof, these are the breakdowns:
|
||||
|
||||
2023 onwards → Flat, insulated
|
||||
2003–2022 → Flat, insulated
|
||||
1983–2002 → Flat, insulated
|
||||
1976–1982 → Flat, limited insulation
|
||||
1967–1975 → Flat, limited insulation
|
||||
1950–1966 and earlier → Flat, no insulation
|
||||
:param age_band: Input age band
|
||||
:return: EpcRoofDescriptions
|
||||
"""
|
||||
|
||||
year = age_band.start_year()
|
||||
|
||||
if year >= 1983:
|
||||
return EpcRoofDescriptions.flat_insulated
|
||||
|
||||
if year >= 1967:
|
||||
return EpcRoofDescriptions.flat_limited_insulation
|
||||
|
||||
return EpcRoofDescriptions.flat_no_insulation
|
||||
|
||||
|
||||
def classify_sloping_ceiling_roof(age_band: EpcConstructionAgeBand) -> EpcRoofDescriptions:
|
||||
"""
|
||||
For a sloping ceiling, as built roof, these are the breakdowns:
|
||||
2023 onwards → Sloping pitched, insulated
|
||||
2003–2022 → Sloping pitched, insulated
|
||||
1983–2002 → Sloping pitched, insulated
|
||||
1976–1982 → Sloping pitched, limited insulation
|
||||
1967–1975 and earlier → Sloping pitched, no insulation
|
||||
:param age_band: Input age band
|
||||
:return: EpcRoofDescriptions
|
||||
"""
|
||||
year = age_band.start_year()
|
||||
|
||||
if year >= 1983:
|
||||
return EpcRoofDescriptions.sloping_pitched_insulated
|
||||
|
||||
if year >= 1976:
|
||||
return EpcRoofDescriptions.sloping_pitched_limited_insulation
|
||||
|
||||
return EpcRoofDescriptions.sloping_pitched_no_insulation
|
||||
|
||||
|
||||
AS_BUILT_ROOF_CLASSIFIERS = {
|
||||
# Only need to apply this to flat and sloping ceiling roofs
|
||||
"Flat": classify_flat_roof,
|
||||
"PitchedWithSlopingCeiling": classify_sloping_ceiling_roof,
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
from backend.onboarders.epc_descriptions import EpcConstructionAgeBand, EpcWallDescriptions
|
||||
|
||||
|
||||
def map_cavity_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.cavity_no_insulation_assumed
|
||||
|
||||
if age_band == EpcConstructionAgeBand.from_1976_to_1982:
|
||||
return EpcWallDescriptions.cavity_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.cavity_insulated_assumed
|
||||
|
||||
raise NotImplementedError(f"Age band {age_band} not handled for cavity wall as built insulation mapping")
|
||||
|
||||
|
||||
def map_solid_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.solid_brick_no_insulation_assumed
|
||||
|
||||
if age_band == EpcConstructionAgeBand.from_1976_to_1982:
|
||||
return EpcWallDescriptions.solid_brick_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.solid_brick_insulated_assumed
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for solid wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
def map_timber_frame_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1950:
|
||||
return EpcWallDescriptions.timber_frame_no_insulation_assumed
|
||||
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.timber_frame_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1976):
|
||||
return EpcWallDescriptions.timber_frame_insulated_assumed
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for timber frame wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
def map_system_build_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.system_no_insulation_assumed
|
||||
|
||||
if age_band == EpcConstructionAgeBand.from_1976_to_1982:
|
||||
return EpcWallDescriptions.system_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.system_insulated_assumed
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for system build wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
def map_granite_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.granite_whinstone_no_insulation_assumed
|
||||
|
||||
if age_band == EpcConstructionAgeBand.from_1976_to_1982:
|
||||
return EpcWallDescriptions.granite_whinstone_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.granite_whinestone_insulated_assumed
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for granite wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
def map_sandstone_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.sandstone_limestone_no_insulation_assumed
|
||||
|
||||
if age_band == EpcConstructionAgeBand.from_1976_to_1982:
|
||||
return EpcWallDescriptions.sandstone_limestone_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.sandstone_limestone_insulated_assumed
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for sandstone wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
def map_cob_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1983:
|
||||
return EpcWallDescriptions.cob_as_built_average
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.cob_as_built_good
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for cob wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
AS_BUILT_WALL_CLASSIFIERS = {
|
||||
"Cavity": map_cavity_wall_insulation,
|
||||
"Solid Brick": map_solid_wall_insulation,
|
||||
"Timber Frame": map_timber_frame_wall_insulation,
|
||||
"System": map_system_build_wall_insulation,
|
||||
"Granite": map_granite_wall_insulation,
|
||||
"Sandstone": map_sandstone_wall_insulation,
|
||||
"Cob": map_cob_wall_insulation,
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import re
|
||||
from numpy import nan
|
||||
from tqdm import tqdm
|
||||
import pandas as pd
|
||||
|
|
@ -5,8 +6,9 @@ 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
|
||||
from onboarders.epc_descriptions import EpcRoofDescriptions
|
||||
WALL_DESCRIPTION_EFFICIENCIES, EpcRoofDescriptions, resolve_roof_efficiency
|
||||
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
|
||||
|
||||
tqdm.pandas()
|
||||
|
||||
|
|
@ -97,117 +99,6 @@ wall_mapping = {
|
|||
('Cob', 'AsBuilt'): None,
|
||||
}
|
||||
|
||||
|
||||
def map_cavity_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.cavity_no_insulation_assumed
|
||||
|
||||
if age_band == EpcConstructionAgeBand.from_1976_to_1982:
|
||||
return EpcWallDescriptions.cavity_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.cavity_insulated_assumed
|
||||
|
||||
raise NotImplementedError(f"Age band {age_band} not handled for cavity wall as built insulation mapping")
|
||||
|
||||
|
||||
def map_solid_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.solid_brick_no_insulation_assumed
|
||||
|
||||
if age_band == EpcConstructionAgeBand.from_1976_to_1982:
|
||||
return EpcWallDescriptions.solid_brick_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.solid_brick_insulated_assumed
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for solid wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
def map_timber_frame_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1950:
|
||||
return EpcWallDescriptions.timber_frame_no_insulation_assumed
|
||||
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.timber_frame_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1976):
|
||||
return EpcWallDescriptions.timber_frame_insulated_assumed
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for timber frame wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
def map_system_build_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.system_no_insulation_assumed
|
||||
|
||||
if age_band == EpcConstructionAgeBand.from_1976_to_1982:
|
||||
return EpcWallDescriptions.system_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.system_insulated_assumed
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for system build wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
def map_granite_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.granite_whinstone_no_insulation_assumed
|
||||
|
||||
if age_band == EpcConstructionAgeBand.from_1976_to_1982:
|
||||
return EpcWallDescriptions.granite_whinstone_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.granite_whinestone_insulated_assumed
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for granite wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
def map_sandstone_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1976:
|
||||
return EpcWallDescriptions.sandstone_limestone_no_insulation_assumed
|
||||
|
||||
if age_band == EpcConstructionAgeBand.from_1976_to_1982:
|
||||
return EpcWallDescriptions.sandstone_limestone_partial_insulated_assumed
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.sandstone_limestone_insulated_assumed
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for sandstone wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
def map_cob_wall_insulation(age_band: EpcConstructionAgeBand):
|
||||
if age_band.start_year() < 1983:
|
||||
return EpcWallDescriptions.cob_as_built_average
|
||||
|
||||
if age_band in EpcConstructionAgeBand.from_year_onwards(1983):
|
||||
return EpcWallDescriptions.cob_as_built_good
|
||||
|
||||
raise NotImplementedError(
|
||||
f"Age band {age_band.value} not handled for cob wall insulation mapping"
|
||||
)
|
||||
|
||||
|
||||
AS_BUILT_WALL_CLASSIFIERS = {
|
||||
"Cavity": map_cavity_wall_insulation,
|
||||
"Solid Brick": map_solid_wall_insulation,
|
||||
"Timber Frame": map_timber_frame_wall_insulation,
|
||||
"System": map_system_build_wall_insulation,
|
||||
"Granite": map_granite_wall_insulation,
|
||||
"Sandstone": map_sandstone_wall_insulation,
|
||||
"Cob": map_cob_wall_insulation,
|
||||
}
|
||||
|
||||
WALL_UNKNOWN_AGE_FALLBACK = {
|
||||
"Cavity": EpcWallDescriptions.cavity_as_built_unknown,
|
||||
"Solid Brick": EpcWallDescriptions.solid_brick_as_built_unknown,
|
||||
|
|
@ -378,60 +269,6 @@ roof_mapping = {
|
|||
('PitchedWithSlopingCeiling', 'Unknown'): None, #
|
||||
}
|
||||
|
||||
|
||||
def classify_flat_roof(age_band: EpcConstructionAgeBand) -> EpcRoofDescriptions:
|
||||
"""
|
||||
For a flat, as built roof, these are the breakdowns:
|
||||
|
||||
2023 onwards → Flat, insulated
|
||||
2003–2022 → Flat, insulated
|
||||
1983–2002 → Flat, insulated
|
||||
1976–1982 → Flat, limited insulation
|
||||
1967–1975 → Flat, limited insulation
|
||||
1950–1966 and earlier → Flat, no insulation
|
||||
:param age_band: Input age band
|
||||
:return: EpcRoofDescriptions
|
||||
"""
|
||||
|
||||
year = age_band.start_year()
|
||||
|
||||
if year >= 1983:
|
||||
return EpcRoofDescriptions.flat_insulated
|
||||
|
||||
if year >= 1967:
|
||||
return EpcRoofDescriptions.flat_limited_insulation
|
||||
|
||||
return EpcRoofDescriptions.flat_no_insulation
|
||||
|
||||
|
||||
def classify_sloping_ceiling_roof(age_band: EpcConstructionAgeBand) -> EpcRoofDescriptions:
|
||||
"""
|
||||
For a sloping ceiling, as built roof, these are the breakdowns:
|
||||
2023 onwards → Sloping pitched, insulated
|
||||
2003–2022 → Sloping pitched, insulated
|
||||
1983–2002 → Sloping pitched, insulated
|
||||
1976–1982 → Sloping pitched, limited insulation
|
||||
1967–1975 and earlier → Sloping pitched, no insulation
|
||||
:param age_band: Input age band
|
||||
:return: EpcRoofDescriptions
|
||||
"""
|
||||
year = age_band.start_year()
|
||||
|
||||
if year >= 1983:
|
||||
return EpcRoofDescriptions.sloping_pitched_insulated
|
||||
|
||||
if year >= 1976:
|
||||
return EpcRoofDescriptions.sloping_pitched_limited_insulation
|
||||
|
||||
return EpcRoofDescriptions.sloping_pitched_no_insulation
|
||||
|
||||
|
||||
AS_BUILT_ROOF_CLASSIFIERS = {
|
||||
# Only need to apply this to flat and sloping ceiling roofs
|
||||
"Flat": classify_flat_roof,
|
||||
"PitchedWithSlopingCeiling": classify_sloping_ceiling_roof,
|
||||
}
|
||||
|
||||
ROOF_UNKNOWN_AGE_FALLBACK = {
|
||||
"Flat": EpcRoofDescriptions.flat_as_built_unknown,
|
||||
"PitchedWithSlopingCeiling": EpcRoofDescriptions.sloping_pitched_as_built_unknown,
|
||||
|
|
@ -478,13 +315,45 @@ data["landlord_roof_description"] = data.progress_apply(
|
|||
assert data["landlord_roof_description"].isnull().sum() == 0, (
|
||||
"Some roof descriptions could not be resolved"
|
||||
)
|
||||
# TODO: 1) Map energy efficiency
|
||||
# TODO: 2) Flag sloped ceilings
|
||||
|
||||
|
||||
def extract_insulation_thickness(value: str | None) -> int | None:
|
||||
"""
|
||||
Extract insulation thickness in mm from a string like 'mm150'.
|
||||
Returns None if not present or not parseable.
|
||||
"""
|
||||
if value is None or pd.isnull(value):
|
||||
return None
|
||||
|
||||
match = re.search(r"(\d+)", str(value))
|
||||
if not match:
|
||||
return None
|
||||
|
||||
return int(match.group(1))
|
||||
|
||||
|
||||
data["roof_insulation_thickness_mm"] = data["Roof Insulation"].apply(
|
||||
extract_insulation_thickness
|
||||
)
|
||||
|
||||
data["landlord_roof_efficiency"] = data.progress_apply(
|
||||
lambda row: resolve_roof_efficiency(
|
||||
description=row.landlord_roof_description,
|
||||
age_band=row.construction_age_band,
|
||||
insulation_thickness=row.roof_insulation_thickness_mm,
|
||||
),
|
||||
axis=1,
|
||||
)
|
||||
|
||||
assert data["landlord_roof_efficiency"].isnull().sum() == 0
|
||||
|
||||
# Flag sloping ceiling
|
||||
data["has_sloping_ceiling"] = data["Roof Construction"].apply(
|
||||
lambda x: x == "PitchedWithSlopingCeiling"
|
||||
)
|
||||
|
||||
# Variables we want to map
|
||||
# 'Org Ref', 'Address 1', 'Address 2', 'Address 3', 'Postcode', 'Type',
|
||||
# 'Attachment', 'Construction Years',
|
||||
# 'Roof Construction', 'Roof Insulation',
|
||||
# 'Org Ref', 'Address 1', 'Address 2', 'Address 3', 'Postcode',
|
||||
# 'Floor Construction', 'Floor Insulation', 'Glazing', 'Heating',
|
||||
# 'Boiler Efficiency', 'Main Fuel', 'Controls Adequacy', 'UPRN',
|
||||
# 'Total Floor Area (m2)'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,175 @@
|
|||
import pytest
|
||||
|
||||
from backend.onboarders.epc_descriptions import (
|
||||
EpcConstructionAgeBand,
|
||||
EpcRoofDescriptions,
|
||||
EpcEfficiency,
|
||||
resolve_roof_efficiency,
|
||||
)
|
||||
|
||||
from backend.onboarders.mappings.as_built_roof_classifiers import (
|
||||
classify_flat_roof,
|
||||
classify_sloping_ceiling_roof,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# As-built roof description classification
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"age_band, expected",
|
||||
[
|
||||
(EpcConstructionAgeBand.before_1900, EpcRoofDescriptions.flat_no_insulation),
|
||||
(EpcConstructionAgeBand.from_1950_to_1966, EpcRoofDescriptions.flat_no_insulation),
|
||||
(EpcConstructionAgeBand.from_1967_to_1975, EpcRoofDescriptions.flat_limited_insulation),
|
||||
(EpcConstructionAgeBand.from_1976_to_1982, EpcRoofDescriptions.flat_limited_insulation),
|
||||
(EpcConstructionAgeBand.from_1983_to_1990, EpcRoofDescriptions.flat_insulated),
|
||||
(EpcConstructionAgeBand.from_2007_to_2011, EpcRoofDescriptions.flat_insulated),
|
||||
(EpcConstructionAgeBand.from_2023_onwards, EpcRoofDescriptions.flat_insulated),
|
||||
],
|
||||
)
|
||||
def test_classify_flat_roof(age_band, expected):
|
||||
assert classify_flat_roof(age_band) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"age_band, expected",
|
||||
[
|
||||
(EpcConstructionAgeBand.before_1900, EpcRoofDescriptions.sloping_pitched_no_insulation),
|
||||
(EpcConstructionAgeBand.from_1967_to_1975, EpcRoofDescriptions.sloping_pitched_no_insulation),
|
||||
(EpcConstructionAgeBand.from_1976_to_1982, EpcRoofDescriptions.sloping_pitched_limited_insulation),
|
||||
(EpcConstructionAgeBand.from_1983_to_1990, EpcRoofDescriptions.sloping_pitched_insulated),
|
||||
(EpcConstructionAgeBand.from_2012_to_2022, EpcRoofDescriptions.sloping_pitched_insulated),
|
||||
(EpcConstructionAgeBand.from_2023_onwards, EpcRoofDescriptions.sloping_pitched_insulated),
|
||||
],
|
||||
)
|
||||
def test_classify_sloping_ceiling_roof(age_band, expected):
|
||||
assert classify_sloping_ceiling_roof(age_band) == expected
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Roof efficiency — fixed & age-band driven
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"description, age_band, expected",
|
||||
[
|
||||
# Flat roof, no insulation
|
||||
(EpcRoofDescriptions.flat_no_insulation, EpcConstructionAgeBand.before_1900, EpcEfficiency.VERY_POOR),
|
||||
|
||||
# Flat roof, limited insulation (age-band driven)
|
||||
(EpcRoofDescriptions.flat_limited_insulation, EpcConstructionAgeBand.from_1976_to_1982, EpcEfficiency.POOR),
|
||||
(
|
||||
EpcRoofDescriptions.flat_limited_insulation, EpcConstructionAgeBand.from_1967_to_1975,
|
||||
EpcEfficiency.VERY_POOR),
|
||||
|
||||
# Flat roof, insulated (age-band driven)
|
||||
(EpcRoofDescriptions.flat_insulated, EpcConstructionAgeBand.from_1983_to_1990, EpcEfficiency.AVERAGE),
|
||||
(EpcRoofDescriptions.flat_insulated, EpcConstructionAgeBand.from_2003_to_2006, EpcEfficiency.GOOD),
|
||||
(EpcRoofDescriptions.flat_insulated, EpcConstructionAgeBand.from_2023_onwards, EpcEfficiency.VERY_GOOD),
|
||||
|
||||
# Pitched, insulated assumed (loft)
|
||||
(EpcRoofDescriptions.pitched_insulated_assumed, EpcConstructionAgeBand.from_1996_to_2002, EpcEfficiency.GOOD),
|
||||
(EpcRoofDescriptions.pitched_insulated_assumed, EpcConstructionAgeBand.from_2007_to_2011,
|
||||
EpcEfficiency.VERY_GOOD),
|
||||
],
|
||||
)
|
||||
def test_roof_efficiency_age_band_only(description, age_band, expected):
|
||||
assert resolve_roof_efficiency(
|
||||
description=description,
|
||||
age_band=age_band,
|
||||
insulation_thickness=None,
|
||||
) == expected
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Roof efficiency — insulation thickness driven
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"description, thickness, expected",
|
||||
[
|
||||
# Loft insulation
|
||||
(EpcRoofDescriptions.loft_12mm_insulation, 12, EpcEfficiency.VERY_POOR),
|
||||
(EpcRoofDescriptions.loft_25mm_insulation, 25, EpcEfficiency.POOR),
|
||||
(EpcRoofDescriptions.loft_75mm_insulation, 75, EpcEfficiency.AVERAGE),
|
||||
(EpcRoofDescriptions.loft_150mm_insulation, 150, EpcEfficiency.GOOD),
|
||||
(EpcRoofDescriptions.loft_300mm_insulation, 300, EpcEfficiency.VERY_GOOD),
|
||||
|
||||
# Flat insulated — thickness overrides age band
|
||||
(EpcRoofDescriptions.flat_insulated, 50, EpcEfficiency.POOR),
|
||||
(EpcRoofDescriptions.flat_insulated, 100, EpcEfficiency.AVERAGE),
|
||||
(EpcRoofDescriptions.flat_insulated, 200, EpcEfficiency.GOOD),
|
||||
(EpcRoofDescriptions.flat_insulated, 300, EpcEfficiency.VERY_GOOD),
|
||||
|
||||
# Sloping ceiling
|
||||
(EpcRoofDescriptions.sloping_pitched_insulated, 75, EpcEfficiency.AVERAGE),
|
||||
(EpcRoofDescriptions.sloping_pitched_insulated, 150, EpcEfficiency.GOOD),
|
||||
(EpcRoofDescriptions.sloping_pitched_insulated, 350, EpcEfficiency.VERY_GOOD),
|
||||
],
|
||||
)
|
||||
def test_roof_efficiency_thickness_based(description, thickness, expected):
|
||||
assert resolve_roof_efficiency(
|
||||
description=description,
|
||||
age_band=EpcConstructionAgeBand.before_1900, # should be ignored
|
||||
insulation_thickness=thickness,
|
||||
) == expected
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Thatched roofs
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"description, age_band, expected",
|
||||
[
|
||||
(EpcRoofDescriptions.thatched, EpcConstructionAgeBand.before_1900, EpcEfficiency.AVERAGE),
|
||||
(EpcRoofDescriptions.thatched, EpcConstructionAgeBand.from_2003_to_2006, EpcEfficiency.GOOD),
|
||||
(EpcRoofDescriptions.thatched, EpcConstructionAgeBand.from_2023_onwards, EpcEfficiency.VERY_GOOD),
|
||||
],
|
||||
)
|
||||
def test_thatched_efficiency_age_band(description, age_band, expected):
|
||||
assert resolve_roof_efficiency(
|
||||
description=description,
|
||||
age_band=age_band,
|
||||
insulation_thickness=None,
|
||||
) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"thickness, expected",
|
||||
[
|
||||
(12, EpcEfficiency.AVERAGE),
|
||||
(50, EpcEfficiency.GOOD),
|
||||
(150, EpcEfficiency.GOOD),
|
||||
(200, EpcEfficiency.VERY_GOOD),
|
||||
],
|
||||
)
|
||||
def test_thatched_efficiency_thickness(thickness, expected):
|
||||
assert resolve_roof_efficiency(
|
||||
description=EpcRoofDescriptions.thatched_with_additional_insulation,
|
||||
age_band=EpcConstructionAgeBand.before_1900,
|
||||
insulation_thickness=thickness,
|
||||
) == expected
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Unknown / holding descriptions
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"description",
|
||||
[
|
||||
EpcRoofDescriptions.flat_as_built_unknown,
|
||||
EpcRoofDescriptions.loft_as_built_unknown,
|
||||
EpcRoofDescriptions.thatched_as_built_unknown,
|
||||
EpcRoofDescriptions.sloping_pitched_as_built_unknown,
|
||||
],
|
||||
)
|
||||
def test_unknown_roof_descriptions_return_na(description):
|
||||
assert resolve_roof_efficiency(
|
||||
description=description,
|
||||
age_band=None,
|
||||
insulation_thickness=None,
|
||||
) == EpcEfficiency.NA
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
import pytest
|
||||
|
||||
from backend.onboarders.epc_descriptions import (
|
||||
EpcConstructionAgeBand,
|
||||
EpcWallDescriptions,
|
||||
EpcEfficiency,
|
||||
resolve_wall_efficiency,
|
||||
)
|
||||
|
||||
from backend.onboarders.mappings.as_built_wall_classifiers import (
|
||||
map_cavity_wall_insulation,
|
||||
map_solid_wall_insulation,
|
||||
map_timber_frame_wall_insulation,
|
||||
map_system_build_wall_insulation,
|
||||
map_granite_wall_insulation,
|
||||
map_sandstone_wall_insulation,
|
||||
map_cob_wall_insulation,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# As-built wall description classification
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"age_band, expected",
|
||||
[
|
||||
(EpcConstructionAgeBand.before_1900, EpcWallDescriptions.cavity_no_insulation_assumed),
|
||||
(EpcConstructionAgeBand.from_1950_to_1966, EpcWallDescriptions.cavity_no_insulation_assumed),
|
||||
(EpcConstructionAgeBand.from_1976_to_1982, EpcWallDescriptions.cavity_partial_insulated_assumed),
|
||||
(EpcConstructionAgeBand.from_1983_to_1990, EpcWallDescriptions.cavity_insulated_assumed),
|
||||
(EpcConstructionAgeBand.from_2023_onwards, EpcWallDescriptions.cavity_insulated_assumed),
|
||||
],
|
||||
)
|
||||
def test_map_cavity_wall_insulation(age_band, expected):
|
||||
assert map_cavity_wall_insulation(age_band) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"age_band, expected",
|
||||
[
|
||||
(EpcConstructionAgeBand.before_1900, EpcWallDescriptions.solid_brick_no_insulation_assumed),
|
||||
(EpcConstructionAgeBand.from_1976_to_1982, EpcWallDescriptions.solid_brick_partial_insulated_assumed),
|
||||
(EpcConstructionAgeBand.from_1996_to_2002, EpcWallDescriptions.solid_brick_insulated_assumed),
|
||||
],
|
||||
)
|
||||
def test_map_solid_wall_insulation(age_band, expected):
|
||||
assert map_solid_wall_insulation(age_band) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"age_band, expected",
|
||||
[
|
||||
(EpcConstructionAgeBand.before_1900, EpcWallDescriptions.timber_frame_no_insulation_assumed),
|
||||
(EpcConstructionAgeBand.from_1950_to_1966, EpcWallDescriptions.timber_frame_partial_insulated_assumed),
|
||||
(EpcConstructionAgeBand.from_1983_to_1990, EpcWallDescriptions.timber_frame_insulated_assumed),
|
||||
],
|
||||
)
|
||||
def test_map_timber_frame_wall_insulation(age_band, expected):
|
||||
assert map_timber_frame_wall_insulation(age_band) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"age_band, expected",
|
||||
[
|
||||
(EpcConstructionAgeBand.before_1900, EpcWallDescriptions.system_no_insulation_assumed),
|
||||
(EpcConstructionAgeBand.from_1976_to_1982, EpcWallDescriptions.system_partial_insulated_assumed),
|
||||
(EpcConstructionAgeBand.from_2003_to_2006, EpcWallDescriptions.system_insulated_assumed),
|
||||
],
|
||||
)
|
||||
def test_map_system_wall_insulation(age_band, expected):
|
||||
assert map_system_build_wall_insulation(age_band) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"age_band, expected",
|
||||
[
|
||||
(EpcConstructionAgeBand.before_1900, EpcWallDescriptions.granite_whinstone_no_insulation_assumed),
|
||||
(EpcConstructionAgeBand.from_1976_to_1982, EpcWallDescriptions.granite_whinstone_partial_insulated_assumed),
|
||||
(EpcConstructionAgeBand.from_2012_to_2022, EpcWallDescriptions.granite_whinestone_insulated_assumed),
|
||||
],
|
||||
)
|
||||
def test_map_granite_wall_insulation(age_band, expected):
|
||||
assert map_granite_wall_insulation(age_band) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"age_band, expected",
|
||||
[
|
||||
(EpcConstructionAgeBand.before_1900, EpcWallDescriptions.sandstone_limestone_no_insulation_assumed),
|
||||
(EpcConstructionAgeBand.from_1976_to_1982, EpcWallDescriptions.sandstone_limestone_partial_insulated_assumed),
|
||||
(EpcConstructionAgeBand.from_2007_to_2011, EpcWallDescriptions.sandstone_limestone_insulated_assumed),
|
||||
],
|
||||
)
|
||||
def test_map_sandstone_wall_insulation(age_band, expected):
|
||||
assert map_sandstone_wall_insulation(age_band) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"age_band, expected",
|
||||
[
|
||||
(EpcConstructionAgeBand.before_1900, EpcWallDescriptions.cob_as_built_average),
|
||||
(EpcConstructionAgeBand.from_1976_to_1982, EpcWallDescriptions.cob_as_built_average),
|
||||
(EpcConstructionAgeBand.from_1983_to_1990, EpcWallDescriptions.cob_as_built_good),
|
||||
],
|
||||
)
|
||||
def test_map_cob_wall_insulation(age_band, expected):
|
||||
assert map_cob_wall_insulation(age_band) == expected
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Wall efficiency resolution
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"description, age_band, expected",
|
||||
[
|
||||
# Fixed efficiencies
|
||||
(EpcWallDescriptions.cavity_no_insulation_assumed, None, EpcEfficiency.POOR),
|
||||
(EpcWallDescriptions.cavity_partial_insulated_assumed, None, EpcEfficiency.AVERAGE),
|
||||
(EpcWallDescriptions.cavity_insulated_assumed, None, EpcEfficiency.GOOD),
|
||||
|
||||
# Function-based efficiencies
|
||||
(
|
||||
EpcWallDescriptions.cavity_filled_cavity,
|
||||
EpcConstructionAgeBand.from_2023_onwards,
|
||||
EpcEfficiency.VERY_GOOD,
|
||||
),
|
||||
(
|
||||
EpcWallDescriptions.cavity_filled_cavity,
|
||||
EpcConstructionAgeBand.from_1991_to_1995,
|
||||
EpcEfficiency.GOOD,
|
||||
),
|
||||
(
|
||||
EpcWallDescriptions.solid_brick_internal_insulation,
|
||||
EpcConstructionAgeBand.from_2003_to_2006,
|
||||
EpcEfficiency.VERY_GOOD,
|
||||
),
|
||||
(
|
||||
EpcWallDescriptions.solid_brick_internal_insulation,
|
||||
EpcConstructionAgeBand.from_1950_to_1966,
|
||||
EpcEfficiency.GOOD,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_resolve_wall_efficiency(description, age_band, expected):
|
||||
assert resolve_wall_efficiency(description, age_band) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"description",
|
||||
[
|
||||
EpcWallDescriptions.cavity_as_built_unknown,
|
||||
EpcWallDescriptions.solid_brick_as_built_unknown,
|
||||
EpcWallDescriptions.system_as_built_unknown,
|
||||
EpcWallDescriptions.timber_frame_as_built_unknown,
|
||||
EpcWallDescriptions.granite_as_built_unknown,
|
||||
EpcWallDescriptions.sandstone_as_built_unknown,
|
||||
EpcWallDescriptions.cob_as_built_unknown,
|
||||
],
|
||||
)
|
||||
def test_unknown_wall_descriptions_return_na(description):
|
||||
assert resolve_wall_efficiency(description, None) == EpcEfficiency.NA
|
||||
|
|
@ -86,7 +86,7 @@ resource "aws_db_instance" "default" {
|
|||
# Temporary to enfore immediate change
|
||||
apply_immediately = true
|
||||
# Set up storage type to gp3 for better performance
|
||||
storage_type = "gp3"
|
||||
storage_type = "gp3"
|
||||
}
|
||||
|
||||
# Set up the bucket that recieve the csv uploads of epc to be retrofit
|
||||
|
|
@ -244,7 +244,7 @@ module "lambda_heating_cost_prediction_ecr" {
|
|||
}
|
||||
|
||||
module "lambda_hot_water_cost_prediction_ecr" {
|
||||
ecr_name = "hot-water-cost-prediction-${var.stage}"
|
||||
ecr_name = "hot-water-fcost-prediction-${var.stage}"
|
||||
source = "./modules/ecr"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[pytest]
|
||||
pythonpath = .
|
||||
addopts = --cov-report term-missing --cov=etl/epc --cov=recommendations --cov=backend --cov=etl/epc_clean --cov=etl/spatial
|
||||
testpaths = recommendations/tests backend/tests etl/epc/tests etl/epc_clean/tests etl/spatial/tests backend/condition/tests
|
||||
testpaths = recommendations/tests backend/tests etl/epc/tests etl/epc_clean/tests etl/spatial/tests backend/condition/tests backend/onboarders/tests
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue