mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
coding up logic to identify work types
This commit is contained in:
parent
8bf6aa5af2
commit
c0ebffb6cb
4 changed files with 270 additions and 172 deletions
|
|
@ -10,6 +10,7 @@ import pandas as pd
|
||||||
from fuzzywuzzy import process
|
from fuzzywuzzy import process
|
||||||
from utils.logger import setup_logger
|
from utils.logger import setup_logger
|
||||||
from backend.SearchEpc import SearchEpc
|
from backend.SearchEpc import SearchEpc
|
||||||
|
from BaseUtility import Definitions
|
||||||
import asset_list.mappings.property_type as property_type_mappings
|
import asset_list.mappings.property_type as property_type_mappings
|
||||||
import asset_list.mappings.walls as walls_mappings
|
import asset_list.mappings.walls as walls_mappings
|
||||||
import asset_list.mappings.heating_systems as heating_mappings
|
import asset_list.mappings.heating_systems as heating_mappings
|
||||||
|
|
@ -282,7 +283,9 @@ class AssetList:
|
||||||
]
|
]
|
||||||
|
|
||||||
# This SAP threshold is a key search criteria for properties that may be eligible for extraction
|
# This SAP threshold is a key search criteria for properties that may be eligible for extraction
|
||||||
SAP_RATING_THRESHOLD = 75
|
FILLED_CAVITY_SAP_THRESHOLD = 75
|
||||||
|
# This SAP the
|
||||||
|
EMPTY_CAVITY_SAP_THRESHOLD = 71
|
||||||
# Any EPC deemed to have been conducted prior to this year is deemed to be unreliable
|
# Any EPC deemed to have been conducted prior to this year is deemed to be unreliable
|
||||||
EPC_YEAR_THRESHOLD = pd.Timestamp.now().year - 5
|
EPC_YEAR_THRESHOLD = pd.Timestamp.now().year - 5
|
||||||
|
|
||||||
|
|
@ -292,9 +295,17 @@ class AssetList:
|
||||||
ATTRIBUTE_ESTIMATED_PERIMETER = "attribute_est_perimter"
|
ATTRIBUTE_ESTIMATED_PERIMETER = "attribute_est_perimter"
|
||||||
ATTRIBUTE_HEAT_LOSS_AREA = "attribute_heat_loss_area"
|
ATTRIBUTE_HEAT_LOSS_AREA = "attribute_heat_loss_area"
|
||||||
ATTRIBUTE_EPC_ROOF_INSULATION_THICKNESS = "attribute_epc_roof_insulation_thickness"
|
ATTRIBUTE_EPC_ROOF_INSULATION_THICKNESS = "attribute_epc_roof_insulation_thickness"
|
||||||
ATTRIBUTE_SAP_THRESHOLD_AND_BELOW = f"sap_rating_{SAP_RATING_THRESHOLD}_and_below"
|
ATTRIBUTE_SAP_THRESHOLD_AND_BELOW = f"sap_rating_{FILLED_CAVITY_SAP_THRESHOLD}_and_below"
|
||||||
ATTRIBUTE_EPC_PRE_YEAR_THRESHOLD = f"EPC is pre {EPC_YEAR_THRESHOLD}"
|
ATTRIBUTE_EPC_PRE_YEAR_THRESHOLD = f"EPC is pre {EPC_YEAR_THRESHOLD}"
|
||||||
|
|
||||||
|
# These are the descriptions that we look for in the EPC data that are indicative of no insulation
|
||||||
|
EPC_NO_WALL_INSULATION_DESCRIPTIONS = [
|
||||||
|
"cavity wall, as built, no insulation (assumed)",
|
||||||
|
"cavity wall, as built, partial insulation (assumed)",
|
||||||
|
"cavity wall, as built, partial insulation",
|
||||||
|
"cavity wall, as built, no insulation",
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
local_filepath,
|
local_filepath,
|
||||||
|
|
@ -728,12 +739,241 @@ class AssetList:
|
||||||
# 1) Is the SAP rating below C75
|
# 1) Is the SAP rating below C75
|
||||||
self.standardised_asset_list[self.ATTRIBUTE_SAP_THRESHOLD_AND_BELOW] = (
|
self.standardised_asset_list[self.ATTRIBUTE_SAP_THRESHOLD_AND_BELOW] = (
|
||||||
self.standardised_asset_list[self.EPC_API_DATA_NAMES["current-energy-efficiency"]] <=
|
self.standardised_asset_list[self.EPC_API_DATA_NAMES["current-energy-efficiency"]] <=
|
||||||
self.SAP_RATING_THRESHOLD
|
self.FILLED_CAVITY_SAP_THRESHOLD
|
||||||
)
|
)
|
||||||
# 2) Flag anything where the EPC is older than 5 years
|
# 2) Flag anything where the EPC is older than 5 years
|
||||||
|
|
||||||
self.standardised_asset_list[self.ATTRIBUTE_EPC_PRE_YEAR_THRESHOLD] = (
|
self.standardised_asset_list[self.ATTRIBUTE_EPC_PRE_YEAR_THRESHOLD] = (
|
||||||
pd.to_datetime(
|
pd.to_datetime(
|
||||||
self.standardised_asset_list[self.EPC_API_DATA_NAMES["lodgement-date"]]
|
self.standardised_asset_list[self.EPC_API_DATA_NAMES["inspection-date"]]
|
||||||
).dt.year < self.EPC_YEAR_THRESHOLD
|
).dt.year < self.EPC_YEAR_THRESHOLD
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.process_age_band()
|
||||||
|
|
||||||
|
def process_age_band(self):
|
||||||
|
processed_age_band = []
|
||||||
|
for _, x in self.standardised_asset_list.iterrows():
|
||||||
|
|
||||||
|
if pd.isnull(x[self.EPC_API_DATA_NAMES["construction-age-band"]]) or (
|
||||||
|
x[self.EPC_API_DATA_NAMES["construction-age-band"]] in Definitions.DATA_ANOMALY_MATCHES
|
||||||
|
):
|
||||||
|
processed_age_band.append(
|
||||||
|
{
|
||||||
|
self.DOMNA_PROPERTY_ID: x[self.DOMNA_PROPERTY_ID],
|
||||||
|
"epc_year_lower_bound": None,
|
||||||
|
"epc_year_upper_bound": None,
|
||||||
|
"Does Age Match EPC Age Band?": "No EPC Age Band"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# We exatract the upper and lower bounds
|
||||||
|
if x[self.EPC_API_DATA_NAMES["construction-age-band"]] in [
|
||||||
|
"England and Wales: 2007 onwards", "England and Wales: 2012 onwards"
|
||||||
|
]:
|
||||||
|
year_lower_bound = 2007 if x[self.EPC_API_DATA_NAMES[
|
||||||
|
"construction-age-band"]] == "England and Wales: 2007 onwards" else 2012
|
||||||
|
|
||||||
|
if pd.isnull(x[self.STANDARD_YEAR_BUILT]):
|
||||||
|
age_band_matches = "No Year Built From Landlord"
|
||||||
|
else:
|
||||||
|
age_band_matches = (
|
||||||
|
"EPC Age Band Matches Year Built" if x[self.STANDARD_YEAR_BUILT] >= year_lower_bound
|
||||||
|
else "EPC Age Band is older than Year Built"
|
||||||
|
)
|
||||||
|
|
||||||
|
processed_age_band.append(
|
||||||
|
{
|
||||||
|
self.DOMNA_PROPERTY_ID: x[self.DOMNA_PROPERTY_ID],
|
||||||
|
"epc_year_lower_bound": year_lower_bound,
|
||||||
|
"epc_year_upper_bound": None,
|
||||||
|
"Does Age Match EPC Age Band?": age_band_matches
|
||||||
|
}
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if x[self.EPC_API_DATA_NAMES["construction-age-band"]] == "England and Wales: before 1900":
|
||||||
|
|
||||||
|
if pd.isnull(x[self.STANDARD_YEAR_BUILT]):
|
||||||
|
age_band_matches = "No Year Built From Landlord"
|
||||||
|
else:
|
||||||
|
age_band_matches = (
|
||||||
|
"EPC Age Band Matches Year Built" if x[self.STANDARD_YEAR_BUILT] < 1900
|
||||||
|
else "EPC Age Band is newer than Year Built"
|
||||||
|
)
|
||||||
|
|
||||||
|
processed_age_band.append(
|
||||||
|
{
|
||||||
|
self.DOMNA_PROPERTY_ID: x[self.DOMNA_PROPERTY_ID],
|
||||||
|
"epc_year_lower_bound": None,
|
||||||
|
"epc_year_upper_bound": 1899,
|
||||||
|
"Does Age Match EPC Age Band?": age_band_matches
|
||||||
|
}
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if x[self.EPC_API_DATA_NAMES["construction-age-band"]].isdigit():
|
||||||
|
|
||||||
|
if pd.isnull(x[self.STANDARD_YEAR_BUILT]):
|
||||||
|
age_band_matches = "No Year Built From Landlord"
|
||||||
|
else:
|
||||||
|
age_band_matches = (
|
||||||
|
"EPC Age Band Matches Year Built" if x[self.STANDARD_YEAR_BUILT] == int(
|
||||||
|
x[self.EPC_API_DATA_NAMES["construction-age-band"]]
|
||||||
|
)
|
||||||
|
else "EPC Age Band is different from Year Built"
|
||||||
|
)
|
||||||
|
|
||||||
|
processed_age_band.append(
|
||||||
|
{
|
||||||
|
self.DOMNA_PROPERTY_ID: x[self.DOMNA_PROPERTY_ID],
|
||||||
|
"epc_year_lower_bound": int(x[self.EPC_API_DATA_NAMES["construction-age-band"]]),
|
||||||
|
"epc_year_upper_bound": int(x[self.EPC_API_DATA_NAMES["construction-age-band"]]),
|
||||||
|
"Does Age Match EPC Age Band?": age_band_matches
|
||||||
|
}
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Oherwise, we extract the upper and lower bounds
|
||||||
|
age_band = x[self.EPC_API_DATA_NAMES["construction-age-band"]].split(": ")[1]
|
||||||
|
lower_date, upper_date = age_band.split("-")
|
||||||
|
|
||||||
|
age_band_matches = (
|
||||||
|
"EPC Age Band Matches Year Built" if (x[self.STANDARD_YEAR_BUILT] >= float(lower_date)) and (
|
||||||
|
x[self.STANDARD_YEAR_BUILT] <= float(upper_date)
|
||||||
|
)
|
||||||
|
else "EPC Age Band is older than Year Built" if x[self.STANDARD_YEAR_BUILT] > float(upper_date)
|
||||||
|
else "EPC Age Band is newer than Year Built"
|
||||||
|
)
|
||||||
|
|
||||||
|
processed_age_band.append(
|
||||||
|
{
|
||||||
|
self.DOMNA_PROPERTY_ID: x[self.DOMNA_PROPERTY_ID],
|
||||||
|
"epc_year_lower_bound": int(lower_date),
|
||||||
|
"epc_year_upper_bound": int(upper_date),
|
||||||
|
"Does Age Match EPC Age Band?": age_band_matches
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
processed_age_band = pd.DataFrame(processed_age_band)
|
||||||
|
|
||||||
|
self.standardised_asset_list = self.standardised_asset_list.merge(
|
||||||
|
processed_age_band, how="left"
|
||||||
|
)
|
||||||
|
|
||||||
|
def identify_worktypes(self):
|
||||||
|
|
||||||
|
# If we have non-intrusives completed, we can use this to identify work types
|
||||||
|
|
||||||
|
if self.non_intrusives_present:
|
||||||
|
######################################################
|
||||||
|
# Empty cavity:
|
||||||
|
######################################################
|
||||||
|
# 1) Has been flagged on the non-intrusives as being a cavity wall, empty or partially filled
|
||||||
|
# 2) The age is before 1995
|
||||||
|
# TODO: 3) Remove anything that likley has access issues
|
||||||
|
self.standardised_asset_list["non_intrusive_indicates_empty_cavity"] = (
|
||||||
|
(~self.standardised_asset_list[self.STANDARD_PROPERTY_TYPE].isin(["bedsit"])) &
|
||||||
|
(self.standardised_asset_list['non-intrusives: Construction'] == "CAVITY") &
|
||||||
|
self.standardised_asset_list['non-intrusives: Insulated'].isin(["EMPTY", "PARTIAL"]) &
|
||||||
|
(self.standardised_asset_list[self.STANDARD_YEAR_BUILT] <= 2000)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.standardised_asset_list["epc_indicates_empty_cavity"] = (
|
||||||
|
self.standardised_asset_list[self.EPC_API_DATA_NAMES["walls-description"]].str.lower().isin(
|
||||||
|
self.EPC_NO_WALL_INSULATION_DESCRIPTIONS
|
||||||
|
) & (
|
||||||
|
self.standardised_asset_list["epc_year_upper_bound"] <= 1995
|
||||||
|
) & (
|
||||||
|
~self.standardised_asset_list[self.ATTRIBUTE_EPC_PRE_YEAR_THRESHOLD]
|
||||||
|
) & (
|
||||||
|
self.standardised_asset_list[
|
||||||
|
self.EPC_API_DATA_NAMES["current-energy-efficiency"]] <= self.EMPTY_CAVITY_SAP_THRESHOLD
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# Extraction
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
# TODO When filterting like this, 627 properties are flagged as not needing a CIGA check and 582 are flagged
|
||||||
|
# as needing a CIGA check. What is the logic we should be applying here?
|
||||||
|
self.standardised_asset_list["non_intrusive_indicates_cavity_extraction"] = (
|
||||||
|
(self.standardised_asset_list["non-intrusives: Construction"] == "CAVITY") &
|
||||||
|
(self.standardised_asset_list["non-intrusives: Insulated"].isin(["RETRO DRILLED", "FILLED AT BUILD"])) &
|
||||||
|
(~self.standardised_asset_list['non-intrusives: Material'].isin(["GREY LOOSE BEAD", "FORMALDEHYDE"])
|
||||||
|
) & (
|
||||||
|
self.standardised_asset_list[self.ATTRIBUTE_SAP_THRESHOLD_AND_BELOW]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# Solar
|
||||||
|
######################################################
|
||||||
|
# Criteria:
|
||||||
|
|
||||||
|
# TODO: Standardise these columns with our cleaned_data object
|
||||||
|
|
||||||
|
# Check 1: Does the property have a valid heating system?
|
||||||
|
self.standardised_asset_list["solar_landlord_data_indicates_correct_heating_system"] = (
|
||||||
|
self.standardised_asset_list[self.STANDARD_HEATING_SYSTEM].isin(
|
||||||
|
["air source heat pump", "ground source heat pump", "high heat retention storage heaters"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.standardised_asset_list["solar_epc_data_indicates_correct_heating_system"] = (
|
||||||
|
(
|
||||||
|
self.standardised_asset_list[self.EPC_API_DATA_NAMES["mainheat-description"]]
|
||||||
|
.str.lower().str.contains("air source heat pump|ground source heat pump")
|
||||||
|
) | (
|
||||||
|
self.standardised_asset_list[
|
||||||
|
self.EPC_API_DATA_NAMES["mainheat-description"]].str.lower().str.contains(
|
||||||
|
"electric storage heaters"
|
||||||
|
) & (
|
||||||
|
self.standardised_asset_list[self.EPC_API_DATA_NAMES[
|
||||||
|
"mainheatcont-description"]] == "Controls for high heat retention storage heaters"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check 2: Does the property have solar already
|
||||||
|
self.standardised_asset_list["property_has_solar"] = (
|
||||||
|
(self.standardised_asset_list[self.STANDARD_EXISTING_PV] == "already has PV") |
|
||||||
|
(self.standardised_asset_list["non-intrusives: PV, ACCESS ISSUE, SEE NOTES"] == "SOLAR PV ON ROOF") |
|
||||||
|
(self.standardised_asset_list[self.ATTRIBUTE_HAS_SOLAR])
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check 3: Does the property meet the fabric condition
|
||||||
|
# Solar PV installs are subject to the minimum insulation requirements which means:
|
||||||
|
# 1) one of the following insulation measures must be installed as part of the same
|
||||||
|
# ECO4 project:
|
||||||
|
# • roof insulation (flat roof, pitched roof, room-in-roof)
|
||||||
|
# • exterior facing wall insulation (cavity wall, solid wall)
|
||||||
|
# • party cavity wall insulation
|
||||||
|
# • floor insulation (solid and underfloor)
|
||||||
|
#
|
||||||
|
# OR
|
||||||
|
#
|
||||||
|
# all measures (except any exempted measure referred to in paragraph 4.28)
|
||||||
|
# listed in paragraph a) must already be installed
|
||||||
|
#
|
||||||
|
# With this in mind, we look for 2 clases
|
||||||
|
# 1) The property is fully insulated apart from the loft (<200mm insulation)
|
||||||
|
# 2) THe property is fully insulated
|
||||||
|
|
||||||
|
self.standardised_asset_list["solar_landlord_walls_insulated"] = (
|
||||||
|
self.standardised_asset_list[self.STANDARD_WALL_CONSTRUCTION].isin(
|
||||||
|
["filled cavity", "insulated solid brick"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
EPC_INSULATED_WALLS_SUBSTRINGS = [
|
||||||
|
", insulated", "with external insulation", "with internal insulation", "filled cavity"
|
||||||
|
]
|
||||||
|
|
||||||
|
self.standardised_asset_list["landlord_wall_construction"].value_counts()
|
||||||
|
|
||||||
|
EPC_INSULATED_ROOF_SUBSTRINGS = [
|
||||||
|
"(another dwelling above)", "limited insulation", "(other premises above)",
|
||||||
|
", no insulation",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ STANDARD_HEATING_SYSTEMS = {
|
||||||
"electric boiler",
|
"electric boiler",
|
||||||
"unknown",
|
"unknown",
|
||||||
"communal gas boiler",
|
"communal gas boiler",
|
||||||
|
"high heat retention storage heaters",
|
||||||
}
|
}
|
||||||
|
|
||||||
HEATING_MAPPINGS = {
|
HEATING_MAPPINGS = {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
from asset_list.AssetList import DataRemapper
|
from asset_list.AssetList import DataRemapper
|
||||||
|
|
||||||
STANDARD_WALL_CONSTRUCTIONS = {
|
STANDARD_WALL_CONSTRUCTIONS = {
|
||||||
"uninsulated cavity", "filled cavity", "partial insulated cavity", "timber frame", "solid brick",
|
"uninsulated cavity", "filled cavity", "partial insulated cavity", "cavity unknown insulation",
|
||||||
"system built", "granite or whinstone", "other", "unknown", "sandstone or limestone", "cob",
|
"timber frame", "uninsulated solid brick",
|
||||||
|
"insulated solid brick", "system built", "granite or whinstone", "other", "unknown", "sandstone or limestone",
|
||||||
|
"cob",
|
||||||
"new build - average thermal transmittance",
|
"new build - average thermal transmittance",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +28,8 @@ WALL_CONSTRUCTION_MAPPINGS = {
|
||||||
'Average thermal transmittance 0.26 W/m-¦K': 'unknown', 'Average thermal transmittance 0.62 W/m?K': 'unknown',
|
'Average thermal transmittance 0.26 W/m-¦K': 'unknown', 'Average thermal transmittance 0.62 W/m?K': 'unknown',
|
||||||
'Average thermal transmittance 0.64 W/m?K': 'unknown', 'Average thermal transmittance 0.61 W/m?K': 'unknown',
|
'Average thermal transmittance 0.64 W/m?K': 'unknown', 'Average thermal transmittance 0.61 W/m?K': 'unknown',
|
||||||
'Sandstone or limestone, as built, no insulation (assumed)': 'sandstone or limestone',
|
'Sandstone or limestone, as built, no insulation (assumed)': 'sandstone or limestone',
|
||||||
'Average thermal transmittance 0.33 W/m?K': 'unknown', 'Cavity wall,': 'unknown',
|
'Average thermal transmittance 0.33 W/m?K': 'unknown',
|
||||||
|
'Cavity wall,': "cavity unknown insulation",
|
||||||
'Cavity wall, as built, partial insulation (assumed)': 'partial insulated cavity',
|
'Cavity wall, as built, partial insulation (assumed)': 'partial insulated cavity',
|
||||||
'Average thermal transmittance 0.29 W/m-¦K': 'unknown', 'Average thermal transmittance 0.32 W/m-¦K': 'unknown',
|
'Average thermal transmittance 0.29 W/m-¦K': 'unknown', 'Average thermal transmittance 0.32 W/m-¦K': 'unknown',
|
||||||
'Average thermal transmittance 0.19 W/m-¦K': 'unknown', 'Average thermal transmittance 0.27 W/m?K': 'unknown',
|
'Average thermal transmittance 0.19 W/m-¦K': 'unknown', 'Average thermal transmittance 0.27 W/m?K': 'unknown',
|
||||||
|
|
@ -55,7 +58,7 @@ WALL_CONSTRUCTION_MAPPINGS = {
|
||||||
'average thermal transmittance 0.26 w/m-¦k': 'unknown', 'average thermal transmittance 0.62 w/m?k': 'unknown',
|
'average thermal transmittance 0.26 w/m-¦k': 'unknown', 'average thermal transmittance 0.62 w/m?k': 'unknown',
|
||||||
'average thermal transmittance 0.64 w/m?k': 'unknown', 'average thermal transmittance 0.61 w/m?k': 'unknown',
|
'average thermal transmittance 0.64 w/m?k': 'unknown', 'average thermal transmittance 0.61 w/m?k': 'unknown',
|
||||||
'sandstone or limestone, as built, no insulation (assumed)': 'sandstone or limestone',
|
'sandstone or limestone, as built, no insulation (assumed)': 'sandstone or limestone',
|
||||||
'average thermal transmittance 0.33 w/m?k': 'unknown', 'cavity wall,': 'unknown',
|
'average thermal transmittance 0.33 w/m?k': 'unknown', 'cavity wall,': "cavity unknown insulation",
|
||||||
'cavity wall, as built, partial insulation (assumed)': 'partial insulated cavity',
|
'cavity wall, as built, partial insulation (assumed)': 'partial insulated cavity',
|
||||||
'average thermal transmittance 0.29 w/m-¦k': 'unknown', 'average thermal transmittance 0.32 w/m-¦k': 'unknown',
|
'average thermal transmittance 0.29 w/m-¦k': 'unknown', 'average thermal transmittance 0.32 w/m-¦k': 'unknown',
|
||||||
'average thermal transmittance 0.19 w/m-¦k': 'unknown', 'average thermal transmittance 0.27 w/m?k': 'unknown',
|
'average thermal transmittance 0.19 w/m-¦k': 'unknown', 'average thermal transmittance 0.27 w/m?k': 'unknown',
|
||||||
|
|
@ -67,4 +70,20 @@ WALL_CONSTRUCTION_MAPPINGS = {
|
||||||
'average thermal transmittance 0.32 w/m?k': 'unknown', 'average thermal transmittance 0.24 w/m-¦k': 'unknown',
|
'average thermal transmittance 0.32 w/m?k': 'unknown', 'average thermal transmittance 0.24 w/m-¦k': 'unknown',
|
||||||
'cavity wall, with internal insulation': 'filled cavity', 'average thermal transmittance 0.17 w/m-¦k': 'unknown',
|
'cavity wall, with internal insulation': 'filled cavity', 'average thermal transmittance 0.17 w/m-¦k': 'unknown',
|
||||||
'average thermal transmittance 0.28 w/m?k': 'unknown',
|
'average thermal transmittance 0.28 w/m?k': 'unknown',
|
||||||
|
'Cavity wall, filled cavity': 'filled cavity',
|
||||||
|
'Cavity wall, filled cavity and external insulation': 'filled cavity',
|
||||||
|
'Granite or whinstone, as built, no insulation (assumed)': 'granite or '
|
||||||
|
'whinstone',
|
||||||
|
'Solid brick, as built, insulated (assumed)': 'insulated solid brick',
|
||||||
|
'Solid brick, as built, no insulation (assumed)': 'uninsulated solid brick',
|
||||||
|
'Solid brick, with external insulation': 'insulated solid brick',
|
||||||
|
'Solid brick, with internal insulation': 'insulated solid brick',
|
||||||
|
'System built, as built, insulated (assumed)': 'system built',
|
||||||
|
'System built, as built, no insulation (assumed)': 'system built',
|
||||||
|
'System built, with external insulation': 'system built',
|
||||||
|
'System built, with internal insulation': 'system built',
|
||||||
|
'Timber frame, as built, insulated (assumed)': 'timber frame',
|
||||||
|
'Timber frame, as built, no insulation (assumed)': 'timber frame',
|
||||||
|
'Timber frame, as built, partial insulation (assumed)': 'timber frame',
|
||||||
|
'Timber frame, with additional insulation': 'timber frame',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import json
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
from BaseUtility import Definitions
|
|
||||||
from asset_list.AssetList import AssetList
|
from asset_list.AssetList import AssetList
|
||||||
from asset_list.mappings.property_type import PROPERTY_MAPPING
|
from asset_list.mappings.property_type import PROPERTY_MAPPING
|
||||||
from asset_list.mappings.walls import WALL_CONSTRUCTION_MAPPINGS
|
from asset_list.mappings.walls import WALL_CONSTRUCTION_MAPPINGS
|
||||||
|
|
@ -14,13 +13,6 @@ from asset_list.mappings.exising_pv import EXISTING_PV_MAPPINGS
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from backend.SearchEpc import SearchEpc
|
from backend.SearchEpc import SearchEpc
|
||||||
from etl.find_my_epc.RetrieveFindMyEpc import RetrieveFindMyEpc
|
from etl.find_my_epc.RetrieveFindMyEpc import RetrieveFindMyEpc
|
||||||
from etl.epc_clean.epc_attributes.RoofAttributes import RoofAttributes
|
|
||||||
|
|
||||||
from recommendations.recommendation_utils import (
|
|
||||||
estimate_perimeter,
|
|
||||||
estimate_external_wall_area,
|
|
||||||
estimate_number_of_floors
|
|
||||||
)
|
|
||||||
|
|
||||||
from etl.epc_clean.epc_attributes.attribute_utils import (
|
from etl.epc_clean.epc_attributes.attribute_utils import (
|
||||||
extract_thermal_transmittance
|
extract_thermal_transmittance
|
||||||
|
|
@ -177,109 +169,6 @@ def extract_address1(asset_list, full_address_col, postcode_col, method="first_t
|
||||||
raise ValueError(f"Method {method} not recognized")
|
raise ValueError(f"Method {method} not recognized")
|
||||||
|
|
||||||
|
|
||||||
def process_age_band(asset_list, year_built_column):
|
|
||||||
processed_age_band = []
|
|
||||||
for _, x in asset_list.iterrows():
|
|
||||||
|
|
||||||
if pd.isnull(x["Property Age Band"]) or (
|
|
||||||
x["Property Age Band"] in Definitions.DATA_ANOMALY_MATCHES
|
|
||||||
):
|
|
||||||
processed_age_band.append({
|
|
||||||
"row_id": x["row_id"],
|
|
||||||
"epc_year_lower_bound": None,
|
|
||||||
"epc_year_upper_bound": None,
|
|
||||||
"Does Age Match EPC Age Band?": "No EPC Age Band"
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
|
|
||||||
# We exatract the upper and lower bounds
|
|
||||||
if x["Property Age Band"] in ["England and Wales: 2007 onwards", "England and Wales: 2012 onwards"]:
|
|
||||||
year_lower_bound = 2007 if x["Property Age Band"] == "England and Wales: 2007 onwards" else 2012
|
|
||||||
|
|
||||||
if pd.isnull(x[year_built_column]):
|
|
||||||
age_band_matches = "No Year Built From Landlord"
|
|
||||||
else:
|
|
||||||
age_band_matches = (
|
|
||||||
"EPC Age Band Matches Year Built" if x[year_built_column] >= year_lower_bound
|
|
||||||
else "EPC Age Band is older than Year Built"
|
|
||||||
)
|
|
||||||
|
|
||||||
processed_age_band.append(
|
|
||||||
{
|
|
||||||
"row_id": x["row_id"],
|
|
||||||
"epc_year_lower_bound": year_lower_bound,
|
|
||||||
"epc_year_upper_bound": None,
|
|
||||||
"Does Age Match EPC Age Band?": age_band_matches
|
|
||||||
}
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if x["Property Age Band"] == "England and Wales: before 1900":
|
|
||||||
|
|
||||||
if pd.isnull(x[year_built_column]):
|
|
||||||
age_band_matches = "No Year Built From Landlord"
|
|
||||||
else:
|
|
||||||
age_band_matches = (
|
|
||||||
"EPC Age Band Matches Year Built" if x[year_built_column] < 1900
|
|
||||||
else "EPC Age Band is newer than Year Built"
|
|
||||||
)
|
|
||||||
|
|
||||||
processed_age_band.append(
|
|
||||||
{
|
|
||||||
"row_id": x["row_id"],
|
|
||||||
"epc_year_lower_bound": None,
|
|
||||||
"epc_year_upper_bound": 1899,
|
|
||||||
"Does Age Match EPC Age Band?": age_band_matches
|
|
||||||
}
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if x["Property Age Band"].isdigit():
|
|
||||||
|
|
||||||
if pd.isnull(x[year_built_column]):
|
|
||||||
age_band_matches = "No Year Built From Landlord"
|
|
||||||
else:
|
|
||||||
age_band_matches = (
|
|
||||||
"EPC Age Band Matches Year Built" if x[year_built_column] == int(x["Property Age Band"])
|
|
||||||
else "EPC Age Band is different from Year Built"
|
|
||||||
)
|
|
||||||
|
|
||||||
processed_age_band.append(
|
|
||||||
{
|
|
||||||
"row_id": x["row_id"],
|
|
||||||
"epc_year_lower_bound": int(x["Property Age Band"]),
|
|
||||||
"epc_year_upper_bound": int(x["Property Age Band"]),
|
|
||||||
"Does Age Match EPC Age Band?": age_band_matches
|
|
||||||
}
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Oherwise, we extract the upper and lower bounds
|
|
||||||
age_band = x["Property Age Band"].split(": ")[1]
|
|
||||||
lower_date, upper_date = age_band.split("-")
|
|
||||||
|
|
||||||
age_band_matches = (
|
|
||||||
"EPC Age Band Matches Year Built" if (x[year_built_column] >= float(lower_date)) and (
|
|
||||||
x[year_built_column] <= float(upper_date)
|
|
||||||
)
|
|
||||||
else "EPC Age Band is older than Year Built" if x[year_built_column] > float(upper_date)
|
|
||||||
else "EPC Age Band is newer than Year Built"
|
|
||||||
)
|
|
||||||
|
|
||||||
processed_age_band.append(
|
|
||||||
{
|
|
||||||
"row_id": x["row_id"],
|
|
||||||
"epc_year_lower_bound": int(lower_date),
|
|
||||||
"epc_year_upper_bound": int(upper_date),
|
|
||||||
"Does Age Match EPC Age Band?": age_band_matches
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
processed_age_band = pd.DataFrame(processed_age_band)
|
|
||||||
|
|
||||||
return processed_age_band
|
|
||||||
|
|
||||||
|
|
||||||
def app():
|
def app():
|
||||||
"""
|
"""
|
||||||
This app is EPC pulling data for some properties owned by Livewest
|
This app is EPC pulling data for some properties owned by Livewest
|
||||||
|
|
@ -531,62 +420,11 @@ def app():
|
||||||
# TODO: TEMP!!!
|
# TODO: TEMP!!!
|
||||||
epc_df["epc_os_uprn"] = epc_df["epc_os_uprn"].astype("Int64").astype(str)
|
epc_df["epc_os_uprn"] = epc_df["epc_os_uprn"].astype("Int64").astype(str)
|
||||||
asset_list.standardised_asset_list = asset_list.standardised_asset_list.merge(
|
asset_list.standardised_asset_list = asset_list.standardised_asset_list.merge(
|
||||||
epc_df, how="left", left_on="ordnance_survey_uprn", right_on="epc_os_uprn"
|
epc_df.drop(columns=["domna_property_id"]), how="left", left_on="ordnance_survey_uprn", right_on="epc_os_uprn"
|
||||||
)
|
)
|
||||||
|
|
||||||
asset_list.extract_attributes()
|
asset_list.extract_attributes()
|
||||||
|
|
||||||
asset_list["Estimated Number of Floors"] = asset_list.apply(
|
|
||||||
lambda x: estimate_number_of_floors(property_type=x["Property Type"]) if not pd.isnull(
|
|
||||||
x["Property Type"]) else None, axis=1
|
|
||||||
)
|
|
||||||
|
|
||||||
asset_list["Property Floor Area"] = asset_list["Property Floor Area"].astype(float)
|
|
||||||
# Replace "" value with None
|
|
||||||
asset_list["Number of Habitable Rooms"] = asset_list["Number of Habitable Rooms"].replace("", None)
|
|
||||||
asset_list["Number of Habitable Rooms"] = asset_list["Number of Habitable Rooms"].astype(float)
|
|
||||||
|
|
||||||
asset_list["Estimated Perimeter (m)"] = asset_list.apply(
|
|
||||||
lambda x: estimate_perimeter(
|
|
||||||
floor_area=x["Property Floor Area"] / x["Estimated Number of Floors"],
|
|
||||||
num_rooms=x["Number of Habitable Rooms"] / x["Estimated Number of Floors"],
|
|
||||||
), axis=1
|
|
||||||
)
|
|
||||||
|
|
||||||
asset_list["Estimated Heat Loss Perimeter (m2)"] = asset_list.apply(
|
|
||||||
lambda x: estimate_external_wall_area(
|
|
||||||
num_floors=x["Estimated Number of Floors"],
|
|
||||||
floor_height=float(x["Property Floor Height"]) if x["Property Floor Height"] else 2.5,
|
|
||||||
perimeter=x["Estimated Perimeter (m)"],
|
|
||||||
built_form=x["Archetype - EPC"]
|
|
||||||
),
|
|
||||||
axis=1
|
|
||||||
)
|
|
||||||
|
|
||||||
asset_list["Roof Insulation Thickness"] = asset_list.apply(
|
|
||||||
lambda x: RoofAttributes(description=x["Roof Construction"]).process()["insulation_thickness"] if not pd.isnull(
|
|
||||||
x["Roof Construction"]) else None,
|
|
||||||
axis=1
|
|
||||||
)
|
|
||||||
|
|
||||||
# We produce some additional fields
|
|
||||||
# 1) Is the SAP rating below C75
|
|
||||||
asset_list["SAP Rating is 75 and below"] = asset_list["SAP score on register"] <= 75
|
|
||||||
# 2) Flag anything where the EPC is older than 5 years
|
|
||||||
cutoff_year = pd.Timestamp.now().year - 5
|
|
||||||
asset_list[f"EPC is pre {cutoff_year}"] = (
|
|
||||||
pd.to_datetime(asset_list["Date of last EPC"]).dt.year < cutoff_year
|
|
||||||
)
|
|
||||||
|
|
||||||
# 3) If we have year in the asset list, we flag entries where the built year is different from the
|
|
||||||
# EPC Age band
|
|
||||||
if PROPERTY_YEAR_BUILT is not None:
|
|
||||||
# We process the age band and merge it on
|
|
||||||
processed_age_band = process_age_band(asset_list, PROPERTY_YEAR_BUILT)
|
|
||||||
asset_list = asset_list.merge(
|
|
||||||
processed_age_band, how="left", on="row_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
if HAS_NON_INTRUSIVES:
|
if HAS_NON_INTRUSIVES:
|
||||||
# Empty cavity:
|
# Empty cavity:
|
||||||
# 1) Has been flagged on the non-intrusives as being empty or partially filled
|
# 1) Has been flagged on the non-intrusives as being empty or partially filled
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue