mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Working on LHP asset list review
This commit is contained in:
parent
9b869063d1
commit
96fb10390b
7 changed files with 570 additions and 100 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="Fastapi-backend" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="AssetList" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyNamespacePackagesService">
|
||||
|
|
|
|||
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="Fastapi-backend" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="AssetList" project-jdk-type="Python SDK" />
|
||||
<component name="PyCharmProfessionalAdvertiser">
|
||||
<option name="shown" value="true" />
|
||||
</component>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import asset_list.mappings.heating_systems as heating_mappings
|
|||
import asset_list.mappings.exising_pv as existing_pv_mappings
|
||||
import asset_list.mappings.built_form as built_form_mappings
|
||||
import asset_list.mappings.roof as roof_mappings
|
||||
import asset_list.mappings.outcomes as outcomes_mappings
|
||||
|
||||
from recommendations.recommendation_utils import (
|
||||
estimate_perimeter,
|
||||
|
|
@ -1139,21 +1140,29 @@ class AssetList:
|
|||
# We add a SAP category for all work type identification
|
||||
self.standardised_asset_list["SAP Category"] = np.where(
|
||||
(
|
||||
(self.standardised_asset_list[self.EPC_API_DATA_NAMES["current-energy-efficiency"]] <= 68) |
|
||||
(self.standardised_asset_list[self.STANDARD_SAP] <= 68)
|
||||
(self.standardised_asset_list[self.EPC_API_DATA_NAMES["current-energy-efficiency"]] <= 54) |
|
||||
(self.standardised_asset_list[self.STANDARD_SAP] <= 54)
|
||||
),
|
||||
"SAP Rating 68 or less",
|
||||
"SAP Rating 54 or less",
|
||||
np.where(
|
||||
(
|
||||
(
|
||||
self.standardised_asset_list[self.EPC_API_DATA_NAMES["current-energy-efficiency"]] <=
|
||||
self.EMPTY_CAVITY_SAP_THRESHOLD
|
||||
) | (self.standardised_asset_list[self.STANDARD_SAP] <= self.EMPTY_CAVITY_SAP_THRESHOLD)
|
||||
(self.standardised_asset_list[self.EPC_API_DATA_NAMES["current-energy-efficiency"]] <= 68) |
|
||||
(self.standardised_asset_list[self.STANDARD_SAP] <= 68)
|
||||
),
|
||||
"SAP Rating 55-68",
|
||||
np.where(
|
||||
(
|
||||
(
|
||||
self.standardised_asset_list[self.EPC_API_DATA_NAMES["current-energy-efficiency"]] <=
|
||||
self.EMPTY_CAVITY_SAP_THRESHOLD
|
||||
) | (self.standardised_asset_list[self.STANDARD_SAP] <= self.EMPTY_CAVITY_SAP_THRESHOLD)
|
||||
),
|
||||
f"SAP Rating 69-{self.EMPTY_CAVITY_SAP_THRESHOLD}",
|
||||
f"SAP Rating {self.EMPTY_CAVITY_SAP_THRESHOLD + 1} or more"
|
||||
),
|
||||
f"SAP Rating 69-{self.EMPTY_CAVITY_SAP_THRESHOLD}",
|
||||
f"SAP Rating {self.EMPTY_CAVITY_SAP_THRESHOLD + 1} or more"
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
# We add a SAP category for all work type identification
|
||||
# We break into 4 categories (54 or less, 55-68, 69-74, 75 or more)
|
||||
|
|
@ -1724,8 +1733,8 @@ class AssetList:
|
|||
~self.standardised_asset_list["epc_indicates_empty_cavity"] &
|
||||
pd.isnull(self.standardised_asset_list["cavity_reason"])
|
||||
),
|
||||
"Landlord Data Shows Empty Cavity, EPC & Inspections Shows Filled: " + self.standardised_asset_list[
|
||||
"SAP Category"],
|
||||
"Landlord Data Shows Empty Cavity, EPC & Inspections Shows Filled or Non-cavity: " +
|
||||
self.standardised_asset_list["SAP Category"],
|
||||
self.standardised_asset_list["cavity_reason"]
|
||||
)
|
||||
|
||||
|
|
@ -2172,10 +2181,7 @@ class AssetList:
|
|||
# TODO: Fetch from Sharepoint
|
||||
ecosurv_filepath = "/Users/khalimconn-kowlessar/Documents/hestia/Ecosurv/15.04.csv"
|
||||
logger.info("Getting Ecosurv data from %s", ecosurv_filepath)
|
||||
self.ecosurv = pd.read_csv(
|
||||
ecosurv_filepath,
|
||||
encoding="cp437"
|
||||
)
|
||||
self.ecosurv = pd.read_csv(ecosurv_filepath, encoding="cp437")
|
||||
|
||||
landlords = self.ecosurv["Landlord"].value_counts().reset_index(drop=False)
|
||||
landlord_references = landlords[
|
||||
|
|
@ -2260,46 +2266,82 @@ class AssetList:
|
|||
|
||||
def flag_outcomes(
|
||||
self,
|
||||
outcomes_filepath,
|
||||
outcomes_filepaths,
|
||||
outcomes_sheetname,
|
||||
outcomes_address,
|
||||
outcomes_postcode,
|
||||
outcomes_houseno,
|
||||
outcomes_id
|
||||
):
|
||||
if outcomes_filepath is None:
|
||||
if not outcomes_filepaths:
|
||||
return
|
||||
|
||||
self.outcomes = pd.read_excel(outcomes_filepath, sheet_name=outcomes_sheetname)
|
||||
self.outcomes["row_id"] = self.outcomes.index
|
||||
|
||||
if outcomes_houseno is None:
|
||||
outcomes_houseno = "houseno"
|
||||
self.outcomes["houseno"] = self.outcomes[outcomes_address].apply(
|
||||
lambda x: SearchEpc.get_house_number(x, self.outcomes[outcomes_postcode])
|
||||
)
|
||||
|
||||
logger.info("Matching outcomes to asset list")
|
||||
# Merge the outcomes onto the asset list - we check we're able to match sufficiently well
|
||||
self.outcomes = []
|
||||
outcomes_no_match = []
|
||||
lookup = []
|
||||
nomatch = []
|
||||
for _, x in tqdm(self.outcomes.iterrows(), total=len(self.outcomes)):
|
||||
for idx, outcomes_filepath in enumerate(outcomes_filepaths):
|
||||
outcomes = pd.read_excel(outcomes_filepath, sheet_name=outcomes_sheetname[idx])
|
||||
outcomes["row_id"] = outcomes.index
|
||||
|
||||
if pd.isnull(x[outcomes_address]):
|
||||
continue
|
||||
if outcomes_houseno[idx] is None:
|
||||
outcomes_houseno = "houseno"
|
||||
outcomes["houseno"] = outcomes[outcomes_address[idx]].apply(
|
||||
lambda x: SearchEpc.get_house_number(x, outcomes[outcomes_postcode])
|
||||
)
|
||||
|
||||
# Check if we have an id
|
||||
oid = x[outcomes_id] if outcomes_id is not None else None
|
||||
# We handle an edge case that occured for LHP
|
||||
if "Notes / Outcomes" in outcomes.columns and "Outcome" not in outcomes.columns:
|
||||
# We use the re-mapper to handle this:
|
||||
outcomes["Notes / Outcomes"] = outcomes["Notes / Outcomes"].str.strip()
|
||||
values_to_remap = outcomes["Notes / Outcomes"].unique()
|
||||
# We want to map this to our standardised list of property types we're interested in
|
||||
remapper = DataRemapper(
|
||||
standard_values=outcomes_mappings.outcomes_values, standard_map=outcomes_mappings.outcomes_map
|
||||
)
|
||||
remap_dictionary = remapper.standardize_list(values_to_remap=values_to_remap.tolist())
|
||||
# Perform the remap
|
||||
outcomes["Outcome"] = outcomes["Notes / Outcomes"].map(remap_dictionary)
|
||||
|
||||
outcomes["Outcome"] = outcomes["Outcome"].str.lower()
|
||||
|
||||
logger.info("Matching outcomes to asset list")
|
||||
# Merge the outcomes onto the asset list - we check we're able to match sufficiently well
|
||||
lookup_i = []
|
||||
nomatch_i = []
|
||||
for _, x in tqdm(outcomes.iterrows(), total=len(outcomes)):
|
||||
|
||||
if pd.isnull(x[outcomes_address[idx]]):
|
||||
continue
|
||||
|
||||
# Check if we have an id
|
||||
oid = x[outcomes_id[idx]] if outcomes_id[idx] is not None else None
|
||||
|
||||
if oid is not None:
|
||||
matched = self.standardised_asset_list[
|
||||
(self.standardised_asset_list[
|
||||
self.STANDARD_LANDLORD_PROPERTY_ID
|
||||
].str.strip() == oid)
|
||||
]
|
||||
|
||||
if matched.shape[0] == 1:
|
||||
lookup_i.append(
|
||||
{
|
||||
"row_id": x["row_id"],
|
||||
self.DOMNA_PROPERTY_ID: matched[self.DOMNA_PROPERTY_ID].values[0]
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
address_clean = x[outcomes_address[idx]].lower().replace(",", "").replace(" ", " ")
|
||||
|
||||
if oid is not None:
|
||||
matched = self.standardised_asset_list[
|
||||
(self.standardised_asset_list[
|
||||
self.STANDARD_LANDLORD_PROPERTY_ID
|
||||
].str.strip() == oid)
|
||||
self.STANDARD_FULL_ADDRESS
|
||||
].str.lower().str.replace(",", "").str.replace(" ", " ") == address_clean)
|
||||
]
|
||||
|
||||
if matched.shape[0] == 1:
|
||||
lookup.append(
|
||||
lookup_i.append(
|
||||
{
|
||||
"row_id": x["row_id"],
|
||||
self.DOMNA_PROPERTY_ID: matched[self.DOMNA_PROPERTY_ID].values[0]
|
||||
|
|
@ -2307,65 +2349,65 @@ class AssetList:
|
|||
)
|
||||
continue
|
||||
|
||||
address_clean = x[outcomes_address].lower().replace(",", "").replace(" ", " ")
|
||||
|
||||
self.outcomes["Outcome"] = self.outcomes["Outcome"].str.lower()
|
||||
|
||||
matched = self.standardised_asset_list[
|
||||
(self.standardised_asset_list[
|
||||
self.STANDARD_FULL_ADDRESS
|
||||
].str.lower().str.replace(",", "").str.replace(" ", " ") == address_clean)
|
||||
]
|
||||
|
||||
if matched.shape[0] == 1:
|
||||
lookup.append(
|
||||
{
|
||||
"row_id": x["row_id"],
|
||||
self.DOMNA_PROPERTY_ID: matched[self.DOMNA_PROPERTY_ID].values[0]
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
matched = self.standardised_asset_list[
|
||||
(self.standardised_asset_list[self.STANDARD_POSTCODE].str.strip() == x[outcomes_postcode])
|
||||
].copy()
|
||||
if not matched.empty:
|
||||
matched["houseno"] = matched.apply(
|
||||
lambda x: SearchEpc.get_house_number(
|
||||
str(x[self.STANDARD_ADDRESS_1]), str(x[self.STANDARD_POSTCODE])
|
||||
),
|
||||
axis=1
|
||||
)
|
||||
|
||||
matched = matched[
|
||||
matched["houseno"].astype(str) == str(x[outcomes_houseno])
|
||||
]
|
||||
if matched.shape[0] == 1:
|
||||
lookup.append(
|
||||
{
|
||||
"row_id": x["row_id"],
|
||||
self.DOMNA_PROPERTY_ID: matched[self.DOMNA_PROPERTY_ID].values[0]
|
||||
}
|
||||
matched = self.standardised_asset_list[
|
||||
(self.standardised_asset_list[self.STANDARD_POSTCODE].str.strip() == x[outcomes_postcode[idx]])
|
||||
].copy()
|
||||
if not matched.empty:
|
||||
matched["houseno"] = matched.apply(
|
||||
lambda x: SearchEpc.get_house_number(
|
||||
str(x[self.STANDARD_ADDRESS_1]), str(x[self.STANDARD_POSTCODE])
|
||||
),
|
||||
axis=1
|
||||
)
|
||||
continue
|
||||
elif not matched.empty:
|
||||
# Use levenstein distance to match
|
||||
matched["address"] = matched[self.STANDARD_ADDRESS_1] + " " + matched[self.STANDARD_POSTCODE]
|
||||
|
||||
best_match = process.extractOne(x["Address"], matched[self.STANDARD_FULL_ADDRESS].values)[0]
|
||||
matched = matched[matched[self.STANDARD_FULL_ADDRESS] == best_match]
|
||||
lookup.append(
|
||||
{
|
||||
"row_id": x["row_id"],
|
||||
self.DOMNA_PROPERTY_ID: matched[self.DOMNA_PROPERTY_ID].values[0]
|
||||
}
|
||||
)
|
||||
continue
|
||||
if pd.isnull(x[outcomes_houseno[idx]]):
|
||||
house_no_to_match = SearchEpc.get_house_number(
|
||||
str(x[outcomes_address[idx]]), str(x[outcomes_postcode[idx]])
|
||||
)
|
||||
if isinstance(house_no_to_match, str):
|
||||
house_no_to_match = house_no_to_match.lower()
|
||||
else:
|
||||
house_no_to_match = str(x[outcomes_houseno[idx]]).strip()
|
||||
|
||||
nomatch.append(x["row_id"])
|
||||
matched = matched[matched["houseno"].astype(str) == house_no_to_match]
|
||||
if matched.shape[0] == 1:
|
||||
lookup_i.append(
|
||||
{
|
||||
"row_id": x["row_id"],
|
||||
self.DOMNA_PROPERTY_ID: matched[self.DOMNA_PROPERTY_ID].values[0]
|
||||
}
|
||||
)
|
||||
continue
|
||||
elif not matched.empty:
|
||||
# Use levenstein distance to match
|
||||
matched["address"] = (
|
||||
matched[self.STANDARD_ADDRESS_1] + " " + matched[self.STANDARD_POSTCODE]
|
||||
)
|
||||
|
||||
self.outcomes_no_match = self.outcomes[self.outcomes["row_id"].isin(nomatch)]
|
||||
lookup = pd.DataFrame(lookup)
|
||||
best_match = process.extractOne(
|
||||
x[outcomes_address[idx]], matched[self.STANDARD_FULL_ADDRESS].values
|
||||
)[0]
|
||||
matched = matched[matched[self.STANDARD_FULL_ADDRESS] == best_match]
|
||||
lookup_i.append(
|
||||
{
|
||||
"row_id": x["row_id"],
|
||||
self.DOMNA_PROPERTY_ID: matched[self.DOMNA_PROPERTY_ID].values[0]
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
nomatch_i.append(x["row_id"])
|
||||
|
||||
outcomes_no_match_i = outcomes[outcomes["row_id"].isin(nomatch_i)]
|
||||
lookup_i = pd.DataFrame(lookup_i)
|
||||
|
||||
outcomes_no_match.append(outcomes_no_match_i)
|
||||
lookup.append(lookup_i)
|
||||
self.outcomes.append(outcomes)
|
||||
|
||||
lookup = pd.concat(lookup)
|
||||
outcomes_no_match = pd.concat(outcomes_no_match)
|
||||
self.outcomes = pd.concat(self.outcomes)
|
||||
|
||||
if lookup.empty:
|
||||
return
|
||||
|
|
@ -2376,10 +2418,19 @@ class AssetList:
|
|||
# that the surveyor had a detailed explanation as to why they couldn't gain access so if this has
|
||||
# happened multiple times, in this case we judge that the work may not be viable
|
||||
|
||||
date_col = "Week Commencing" if "Week Commencing" in self.outcomes else "Survey Date"
|
||||
if "Week Commencing" in self.outcomes.columns:
|
||||
date_col = "Week Commencing"
|
||||
elif "Survey Date" in self.outcomes.columns:
|
||||
date_col = "Survey Date"
|
||||
elif "Date letters sent" in self.outcomes.columns:
|
||||
date_col = "Date letters sent"
|
||||
else:
|
||||
raise NotImplementedError("Invalid date in outcomes - implement me")
|
||||
|
||||
notes_col = "Notes" if "Notes" in outcomes.columns else "Notes / Outcomes"
|
||||
|
||||
lookup = lookup.merge(
|
||||
self.outcomes[["row_id", "Outcome", "Notes", date_col]], how="left", on="row_id"
|
||||
self.outcomes[["row_id", "Outcome", notes_col, date_col]], how="left", on="row_id"
|
||||
)
|
||||
|
||||
visit_counts = (
|
||||
|
|
@ -2390,11 +2441,33 @@ class AssetList:
|
|||
.sort_values("visit_count", ascending=False)
|
||||
)
|
||||
|
||||
def extract_date(s):
|
||||
if isinstance(s, str):
|
||||
match = re.search(r"(\d{2}\.\d{2}\.\d{4})", s)
|
||||
if match:
|
||||
return pd.to_datetime(match.group(1), format="%d.%m.%Y", errors="coerce")
|
||||
return pd.NaT
|
||||
|
||||
lookup['parsed_date'] = lookup['Date letters sent'].apply(extract_date)
|
||||
|
||||
def get_latest_note(group):
|
||||
surveyed = group[group['Outcome'] == 'surveyed']
|
||||
if not surveyed.empty:
|
||||
return surveyed.sort_values('parsed_date', ascending=False).iloc[0]
|
||||
else:
|
||||
return group.sort_values('parsed_date', ascending=False).iloc[0]
|
||||
|
||||
latest_note = lookup.groupby('domna_property_id', group_keys=False).apply(get_latest_note).reset_index(
|
||||
drop=True)
|
||||
latest_note = latest_note[["domna_property_id", notes_col]]
|
||||
|
||||
pivot_df = lookup.groupby(["domna_property_id", "Outcome"]).size().unstack(fill_value=0).reset_index()
|
||||
pivot_df = pivot_df.merge(
|
||||
visit_counts, how="left", on="domna_property_id"
|
||||
)
|
||||
|
||||
# We want the latest note
|
||||
|
||||
if pivot_df[self.DOMNA_PROPERTY_ID].duplicated().sum():
|
||||
raise Exception("We have duplicated property IDs in the outcomes data")
|
||||
|
||||
|
|
@ -2406,6 +2479,14 @@ class AssetList:
|
|||
self.standardised_asset_list = self.standardised_asset_list.merge(
|
||||
pivot_df, how="left", left_on=self.DOMNA_PROPERTY_ID, right_on="domna_property_id"
|
||||
)
|
||||
# Merge the latest note
|
||||
self.standardised_asset_list = self.standardised_asset_list.merge(
|
||||
latest_note.rename(columns={notes_col: "Latest Route March Note"}),
|
||||
how="left", left_on=self.DOMNA_PROPERTY_ID, right_on="domna_property_id"
|
||||
)
|
||||
|
||||
if self.standardised_asset_list[self.DOMNA_PROPERTY_ID].duplicated().sum():
|
||||
raise ValueError("Duplicates appreared - something went wrong")
|
||||
|
||||
self.outcomes = self.outcomes.sort_values("domna_property_id", ascending=False)
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,103 @@ def app():
|
|||
# - We want: fully insulated property (all wall types), EPC D or below (floors should be solid)
|
||||
# - Or the insulation required is loft/cavity (floors should be solid)
|
||||
|
||||
# LHP:
|
||||
data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/LHP"
|
||||
data_filename = "LHP.xlsx"
|
||||
sheet_name = "Decent Homes Stock"
|
||||
postcode_column = 'Postcode'
|
||||
fulladdress_column = "Address"
|
||||
address1_column = None
|
||||
address1_method = "house_number_extraction"
|
||||
address_cols_to_concat = []
|
||||
missing_postcodes_method = None
|
||||
landlord_year_built = "Build Date"
|
||||
landlord_os_uprn = None
|
||||
landlord_property_type = "Property Type"
|
||||
landlord_built_form = None
|
||||
landlord_wall_construction = None
|
||||
landlord_roof_construction = None
|
||||
landlord_heating_system = "Heating Type"
|
||||
landlord_existing_pv = None
|
||||
landlord_property_id = "Property ID"
|
||||
landlord_sap = None
|
||||
outcomes_filename = [
|
||||
"/Users/khalimconn-kowlessar/Documents/hestia/Customers/LHP/LHP Outcomes.xlsx",
|
||||
"/Users/khalimconn-kowlessar/Documents/hestia/Customers/LHP/Lincolnshire Housing Partnership - Outcomes 20th "
|
||||
"Feb 2024.xlsx",
|
||||
]
|
||||
outcomes_sheetname = ["Sheet1", "LHP"]
|
||||
outcomes_postcode = ["Postcode", "Postcode"]
|
||||
outcomes_houseno = ["No.", "No."]
|
||||
outcomes_id = [None, None]
|
||||
outcomes_address = ["Address", "Address"]
|
||||
master_filepaths = [os.path.join(data_folder, "LHP Rolling Master for analysis.csv")]
|
||||
master_to_asset_list_filepath = None
|
||||
phase = False
|
||||
ecosurv_landlords = "lhp"
|
||||
|
||||
# Soverign
|
||||
data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Sovereign"
|
||||
data_filename = "Warmfront - Quote for CWI.xlsx"
|
||||
sheet_name = "Sheet2"
|
||||
postcode_column = 'Postcode'
|
||||
fulladdress_column = None
|
||||
address1_column = "Address Line 1"
|
||||
address1_method = None
|
||||
address_cols_to_concat = ["Address Line 1", "Address Line 2", "Address Line 3"]
|
||||
missing_postcodes_method = None
|
||||
landlord_year_built = None
|
||||
landlord_os_uprn = None
|
||||
landlord_property_type = None
|
||||
landlord_built_form = None
|
||||
landlord_wall_construction = None
|
||||
landlord_roof_construction = None
|
||||
landlord_heating_system = None
|
||||
landlord_existing_pv = None
|
||||
landlord_property_id = "ID"
|
||||
landlord_sap = None
|
||||
outcomes_filename = None
|
||||
outcomes_sheetname = None
|
||||
outcomes_postcode = None
|
||||
outcomes_houseno = None
|
||||
outcomes_id = None
|
||||
outcomes_address = None
|
||||
master_filepaths = []
|
||||
master_to_asset_list_filepath = None
|
||||
phase = False
|
||||
ecosurv_landlords = None
|
||||
|
||||
# NCHA
|
||||
data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/NCHA"
|
||||
data_filename = "Energy Info Copy.xlsx"
|
||||
sheet_name = "Data"
|
||||
postcode_column = 'Postcode'
|
||||
fulladdress_column = "Address"
|
||||
address1_column = None
|
||||
address1_method = "house_number_extraction"
|
||||
address_cols_to_concat = []
|
||||
missing_postcodes_method = None
|
||||
landlord_year_built = "Build Date (HAR10)"
|
||||
landlord_os_uprn = None
|
||||
landlord_property_type = "Property Type (HAR10)"
|
||||
landlord_built_form = "Build Form (EPC)"
|
||||
landlord_wall_construction = "Wall Description"
|
||||
landlord_roof_construction = None
|
||||
landlord_heating_system = "Heating System"
|
||||
landlord_existing_pv = None
|
||||
landlord_property_id = "Place ref"
|
||||
landlord_sap = "EPC SAP"
|
||||
outcomes_filename = None
|
||||
outcomes_sheetname = None
|
||||
outcomes_postcode = None
|
||||
outcomes_houseno = None
|
||||
outcomes_id = None
|
||||
outcomes_address = None
|
||||
master_filepaths = []
|
||||
master_to_asset_list_filepath = None
|
||||
phase = False
|
||||
ecosurv_landlords = None
|
||||
|
||||
# Torus
|
||||
data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Torus/Phase 1"
|
||||
data_filename = "Torus Property Asset List - Phase 1.xlsx"
|
||||
|
|
@ -482,7 +579,7 @@ def app():
|
|||
|
||||
# We now flag properties that have been treated under existing programmes
|
||||
asset_list.flag_outcomes(
|
||||
outcomes_filepath=os.path.join(data_folder, outcomes_filename) if outcomes_filename else None,
|
||||
outcomes_filepaths=outcomes_filename,
|
||||
outcomes_sheetname=outcomes_sheetname,
|
||||
outcomes_address=outcomes_address,
|
||||
outcomes_postcode=outcomes_postcode,
|
||||
|
|
@ -611,6 +708,12 @@ def app():
|
|||
transformed_data.append(row_data)
|
||||
|
||||
transformed_df = pd.DataFrame(transformed_data)
|
||||
for col in [
|
||||
"Floor insulation (solid floor)",
|
||||
"Floor insulation", "Floor insulation (suspended floor)"
|
||||
]:
|
||||
if col not in transformed_df.columns:
|
||||
transformed_df[col] = False
|
||||
transformed_df = transformed_df[
|
||||
[
|
||||
asset_list.DOMNA_PROPERTY_ID, "Floor insulation (solid floor)",
|
||||
|
|
|
|||
|
|
@ -220,5 +220,48 @@ HEATING_MAPPINGS = {
|
|||
'Boiler/ underfloor': 'electric underfloor',
|
||||
'Storage system': "non-electric underfloor",
|
||||
'BOILER': 'gas combi boiler',
|
||||
'SPACE_HEATER': 'room heaters'
|
||||
'SPACE_HEATER': 'room heaters',
|
||||
'AIR': 'air source heat pump',
|
||||
'FSOL': 'solid fuel',
|
||||
'PDEV': 'unknown',
|
||||
'GASF': 'gas boiler, radiators',
|
||||
'CONO': 'no heating',
|
||||
'FELE HRSH': 'high heat retention storage heaters',
|
||||
'FOIL': 'oil boiler',
|
||||
'FDEV': 'unknown',
|
||||
'FNON': 'non-electric underfloor',
|
||||
'FGAS': 'gas combi boiler',
|
||||
'FELE': 'electric fuel',
|
||||
'GRNE': 'ground source heat pump',
|
||||
|
||||
'High Heat Storage Heaters': 'high heat retention storage heaters',
|
||||
'Electric Radiators': 'electric radiators',
|
||||
'Electric Air Source Heat Pump': 'air source heat pump',
|
||||
'Gas Combi Condensing Boiler': 'gas condensing combi',
|
||||
'Electric Boiler Heating': 'electric boiler',
|
||||
'Solid Fuel Open Back Boiler Heating': 'solid fuel',
|
||||
'Solid Fuel Closed Back Boiler Heating': 'solid fuel',
|
||||
'Oil Boiler': 'oil boiler',
|
||||
'Electric Storage Heaters': 'electric storage heaters',
|
||||
'Gas Combi Boiler Heating': 'gas combi boiler',
|
||||
'Electric NIBE Heating System': 'air source heat pump',
|
||||
'Gas Back Boiler': 'gas boiler, radiators',
|
||||
'Electric Gel/Oil Filled Radiators': 'electric radiators',
|
||||
'No Information': 'unknown',
|
||||
'Oil Combination Boiler Heating': 'oil boiler',
|
||||
'Electric DSR Heat Retention Radiators': 'high heat retention storage heaters',
|
||||
'Communal Heating System': 'communal heating',
|
||||
'Description': 'unknown',
|
||||
'Oil Combi Condensing Boiler Heating': 'oil boiler',
|
||||
'Gas Combi Condensing Boiler Heating': 'gas condensing combi',
|
||||
'Electric Warm Air Heating': 'electric fuel',
|
||||
'Gas System Boiler Heating': 'gas boiler, radiators',
|
||||
'Gas Back Boiler Heating': 'gas boiler, radiators',
|
||||
'Electric Gel/Oil Fllled Radiators': 'electric radiators',
|
||||
'Gas Condensing Boiler Heating': 'gas condensing combi',
|
||||
'Gas Combi Condensing Boiler Heatiner': 'gas condensing combi',
|
||||
'Oil Standard Boiler Heating': 'oil boiler',
|
||||
'Oil Condensing Boiler Heating': 'oil boiler',
|
||||
'Electric ASHP': 'air source heat pump',
|
||||
'Modern Slimline Storage Heaters': 'electric storage heaters'
|
||||
}
|
||||
|
|
|
|||
231
asset_list/mappings/outcomes.py
Normal file
231
asset_list/mappings/outcomes.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
"""
|
||||
This script was produced to handle the non-standard outcomes, observed in the LHP outcomes sheet
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
outcomes_values = [
|
||||
"Access Issues", "No Outcome", "Asked for a later date", "Customer Refusal",
|
||||
"Installer Refusal", "No Answer", "Not Viable", "Surveyed",
|
||||
"Rescheduled", "Not Knocked", "Void"
|
||||
]
|
||||
|
||||
outcomes_map = {
|
||||
'Access issues, shed against rear wall. Sent photos to Matt JJC, declined': 'Access Issues',
|
||||
'NO ANSWER /TICKET LEFT': 'No Answer',
|
||||
'Looks Void - No Answer': 'No Answer',
|
||||
'No Answer - they were in - No response to my drop card': 'No Answer', 'No Answer': 'No Answer',
|
||||
'No Answer - Even they were in - No response to my drop card': 'No Answer', 'no answer': 'No Answer',
|
||||
'NO ANSWER': 'No Answer', 'No answer': 'No Answer',
|
||||
np.nan: 'unknown',
|
||||
'Access Issues Health reasons try another time': 'Access Issues',
|
||||
'LOFT FULL, CUSTOMER WONT REMOVE': 'Access Issues',
|
||||
'Failed Appointment - Ivy': 'Access Issues',
|
||||
'Failed Appointment - Void soon': 'Void',
|
||||
'Hoarding in loft': 'Access Issues',
|
||||
'Non Complained - Extension at rear and side': 'Not Viable',
|
||||
'Said No letter - then texted me I can only do outside but cant come in': 'Customer Refusal',
|
||||
'Hoarding - unwilling to shift from loft': 'Customer Refusal',
|
||||
'Overgrown vegatation - Happy for HA to deal with': 'Access Issues',
|
||||
'No access to side of property': 'Not Viable',
|
||||
'Very rude': 'Customer Refusal',
|
||||
'REFUSED ACCESS': 'Customer Refusal',
|
||||
'SURVEYED': 'Surveyed',
|
||||
'ELECTRIC ROOM HEATERS. Kieran to check re funding and possible PV?': 'Not Viable',
|
||||
'SUBMITTED': 'Surveyed',
|
||||
'2 single storey extensions': 'Not Viable',
|
||||
'Rebook': 'Rescheduled',
|
||||
'surveyed': 'Surveyed',
|
||||
'not intrested': 'Customer Refusal',
|
||||
'Fixed seating area against rear elevation': 'Not Viable',
|
||||
"Matt said can't install": 'Installer Refusal',
|
||||
'Gave excuses to come this and that time and no reponse': 'No Answer',
|
||||
'NOT KNOCKED': 'Not Knocked',
|
||||
'VOID PROPERTY': 'Void',
|
||||
'Glass lean to. JJC declined': 'Installer Refusal',
|
||||
'Left slip Overgrown vegatation': 'No Answer',
|
||||
'covid': 'Rescheduled',
|
||||
'Lean-to on side elevation': 'Not Viable',
|
||||
'Opted out as moving out': 'Customer Refusal',
|
||||
'Surveyed': 'Surveyed',
|
||||
'refused': 'Customer Refusal',
|
||||
'COVID': 'Rescheduled',
|
||||
'Said No letter received and didn’t answer again': 'No Answer',
|
||||
'Survey completed': 'Surveyed',
|
||||
'Loft fully boarded': 'Access Issues',
|
||||
'Not Available during the day': 'No Answer',
|
||||
'Conservatory. JJC declined.': 'Installer Refusal',
|
||||
'Booked for 19.10.23': 'Rescheduled',
|
||||
'LETTER LEFT': 'No Answer',
|
||||
'Knocked/lettered': 'No Answer',
|
||||
'Survey Complete': 'Surveyed',
|
||||
'Refused by calling office': 'Customer Refusal',
|
||||
'Extension on rear elevation': 'No Viable',
|
||||
'Left Slip - Potential access issue with conservatory': 'Access Issues',
|
||||
'Overgrown vegatation': 'Access Issues',
|
||||
'Left slip Overgrown Ivy and Hedge': 'No Answer',
|
||||
'NOT AVAILABLE THIS WEEK': 'No Answer',
|
||||
'Unwilling to clear loft': 'Access Issues',
|
||||
'survey complete': 'Surveyed',
|
||||
'ivy on wall': 'Access Issues',
|
||||
'not in': 'No Answer',
|
||||
'Covid shrub very close to building': 'Rescheduled',
|
||||
'ON HOLIDAY, UNDER 18 IN HOUSE': 'Rescheduled',
|
||||
'wont do as extention': 'Not Viable',
|
||||
'IN, WONT ANSWER': 'Customer Refusal',
|
||||
'Too many plants next to the walls': 'Access Issues',
|
||||
'obstructions': 'Access Issues',
|
||||
'Left slip -Wall plant': 'Access Issues',
|
||||
'On holiday': 'No Answer',
|
||||
'Failed appointment': 'No Answer',
|
||||
'LOFT FULLY BOARDED': 'Access Issues',
|
||||
'ivy and didn’t want people inside the house': 'Customer Refusal',
|
||||
'Partly IWI': 'Not Viable',
|
||||
'Covid': 'Rescheduled',
|
||||
'REFUSE TO REMOVE IVY': 'Access Issues',
|
||||
'Insulated 2 years ago. Carbon bead in walls, 300mm rock wool in loft': 'Not Viable',
|
||||
'INCONVIENIENT TIME': 'No Answer',
|
||||
'EXT TO REAR': 'Not Viable',
|
||||
'Not In': 'No Answer',
|
||||
'Damp issues.Black mould on walls': 'Access Issues',
|
||||
'Lean to. JJC declined': 'Installer Refusal',
|
||||
'DISABLED CHILD / INCONVIENIENT': 'Customer Refusal',
|
||||
'Plants on wall': 'Access Issues',
|
||||
'Left Slip': 'No Answer',
|
||||
'Never answered': 'No Answer',
|
||||
'SOLAR PV CONNECTED TO MAINS': 'Not Viable',
|
||||
'Bungalow': 'unknown',
|
||||
'call back': 'No Answer',
|
||||
'Message from WFT OFFICE; tenant unavailable this week, no telephone number provided': 'Rescheduled',
|
||||
'LEAN TO PRESENT': 'Not Viable',
|
||||
'She said come Tuesday and never answered': 'Rescheduled',
|
||||
'Sold': 'Surveyed',
|
||||
'Too much mould and cluttered house': 'Access Issues',
|
||||
'Overgrown vegatation will call when clear': 'Access Issues',
|
||||
'LOFT DEC 2013': 'Not Viable',
|
||||
'Ivy': 'Access Issues',
|
||||
'Booked for next week': 'Rescheduled',
|
||||
'empty': 'Void',
|
||||
'Been told property is empty as tenant has passed away': 'Void',
|
||||
'Non Complianced - Single Storey Extension to the front and rear': 'Not Viable',
|
||||
'Going back this week': 'Rescheduled',
|
||||
'Loft insulated in last few months. Ongoing damp issues in bathroom, black mould up wall': 'Access Issues',
|
||||
'rear Extension': 'Not Viable',
|
||||
'DECKING AROUND PROPERTY IN BREACH OF DPC BY 300MM': 'Not Viable',
|
||||
'Said no letter received': 'Customer Refusal',
|
||||
'Unwell, not convenient this week': 'Rescheduled',
|
||||
'IVY on Wall': 'Access Issues',
|
||||
'REFUSED EXTRACTOR': 'Customer Refusal',
|
||||
'ON HOLIDAY': 'Rescheduled',
|
||||
'COVID. Not this week.': 'Rescheduled',
|
||||
'COVID POSITIVE': 'Rescheduled',
|
||||
'VOID. Appears to be under refurbishment': 'Void',
|
||||
'Survey Completed': 'Surveyed',
|
||||
'INCONVIENIENT': 'Rescheduled',
|
||||
'Knocked/lettered. 07598 112360': 'No Answer',
|
||||
'Single skin lean to. JJC declined': 'Installer Refusal',
|
||||
'DENIES LETER, REFUSED ACCESS': 'Customer Refusal',
|
||||
'Loft hoard unable to clear': 'Access Issues',
|
||||
'Left Slip - Look Void': 'Void',
|
||||
'EXCESSIVE IVY GROWTH, CUSOMER UNABLE TO REMOVE, ELDERLEY': 'Access Issues',
|
||||
'Refused': 'Customer Refusal',
|
||||
'REFUSED / INCONVENIENT': 'Customer Refusal',
|
||||
'AGGRESSIVE DOGS LOOSE IN FRONT GARDEN': 'Access Issues',
|
||||
'EXCESSIVE IVY': 'Access Issues',
|
||||
"Won't remove plastic roof": 'Access Issues',
|
||||
'SURVEY COMPLETED': 'Surveyed',
|
||||
'VOID. Under refurbishment. Electric storage heating currently removed for refurbishment': 'Void',
|
||||
'Surveyed ECO4': 'Surveyed',
|
||||
'after 5.30': 'Rescheduled',
|
||||
'CUSTOMER IN, WONT ANSWER DOOR': 'No Answer',
|
||||
'IVY': 'Access Issues',
|
||||
'Single storey extension on gable': 'Not Viable',
|
||||
'No answer.': 'No Answer',
|
||||
'Full extension at rear. Not viable.': 'Not Viable',
|
||||
'Access issues': 'Access Issues',
|
||||
'VOID PROPERTY NOW': 'Void',
|
||||
'Not viable': 'Not Viable',
|
||||
'Looks like a VOID property': 'Void',
|
||||
'NOT VIABLE': 'Not Viable',
|
||||
'No Answer.': 'No Answer',
|
||||
'Not viable.': 'Not Viable',
|
||||
'Looks to be void.': 'Void',
|
||||
'Access issues and loft fully boarded/full': 'Access Issues',
|
||||
'Extension on property. Not Viable': 'Not Viable',
|
||||
'No good. Serious Access issues.': 'Access Issues',
|
||||
'Surveyed and Submitted': 'Surveyed',
|
||||
'UNSANITARY CONDITIONS, RUBBISH EVERYWHERE': 'Access Issues',
|
||||
'Will call when rubbish removed.': 'Access Issues',
|
||||
'Covered in Ivy': 'Access Issues',
|
||||
'CUSTOMER REFUSED': 'Customer Refusal',
|
||||
'Still covered in ivy': 'Access Issues',
|
||||
'CUSTOMER SHOUTED OUT OF WINDOW TO COME BACK ANOTHER TIME': 'Customer Refusal',
|
||||
"Extension on property, can't be done.": 'Not Viable',
|
||||
'Will be looking to do Survey WC 19.02': 'Rescheduled',
|
||||
"Tenant was working, couldn't do survey.": 'No Answer',
|
||||
'PROPERTY EMPTY, SPOKE TO EX TENNANT WHO LEFT 3 WEEKS AGO?': 'Void',
|
||||
'Will call back.': 'Rescheduled',
|
||||
"Tenant not interested. Won't empty loft.": 'Customer Refusal',
|
||||
"Won't answer door.": 'Customer Refusal',
|
||||
"Tenant 'Doesn't want anything to do with LHP'": 'Customer Refusal',
|
||||
"Loft full. Tenant won't empty.": 'Access Issues',
|
||||
'Covered in foliage': 'Access Issues',
|
||||
'Customer not home for appointment.': 'No Answer',
|
||||
'Blown in bead': 'Not Viable',
|
||||
'Distance to property to far from road.': 'Access Issues',
|
||||
'LOFT FULL, CUSTOMER UNABLE TO CLEAR': 'Access Issues',
|
||||
'Stuff against rear wall. Will call when removed.': 'Access Issues',
|
||||
'Will call when rubbish is removed': 'Access Issues',
|
||||
'Mid Terrace': 'unknown',
|
||||
'Tile Hung areas.': 'Not Viable',
|
||||
'REFUSED / UNABLE TO CLEAR LOFT': 'Customer Refusal',
|
||||
'Calling back on Monday (19.02)': 'Rescheduled',
|
||||
'Solid Wall': 'Not Viable',
|
||||
'FAULTY PHONE NUMBER, 3 X KNOCK, LETTER LEFT ON FIRST ATTEMPT, NO REPLY OR CALL BACK': 'No Answer',
|
||||
'Not interested': 'Customer Refusal',
|
||||
'ACCESS DENIED': 'Customer Refusal',
|
||||
'Covered in Ivy.': 'Access Issues',
|
||||
'UNABLE TO GENERATE SAP GAIN WITH EXTENSIONS FRONT AND REAR': 'Not Viable',
|
||||
'Extension on the property.': 'Not Viable',
|
||||
"Covered in Ivy. Can't remove it.": 'Access Issues',
|
||||
'Booked in, but not in when called back': 'No Answer',
|
||||
'EXCESSIVE IVY ON WALLS (SEE PICS)': 'Access Issues',
|
||||
'Moved out': 'Void',
|
||||
'Buying the property. Not interested.': 'Customer Refusal',
|
||||
'Not been to yet': 'No Answer',
|
||||
'CUSTOMER STATES LOFT WAS INSULATED A FEW MONTHS AGO BY LHP': 'Customer Refusal',
|
||||
'Will try again.': 'No Answer',
|
||||
'HOUSE MARTINS NESTING IN EAVES OF 3 ADJOINING PROPERTIES': 'Access Issues',
|
||||
'Told me to call back': 'Rescheduled',
|
||||
'CUSTOMER SAYS PROPERTY ALREADY REFUSED AT PREVIOUS SURVEY, NO REASON GIVEN': 'Customer Refusal',
|
||||
"Won't answer the door.": 'Customer Refusal',
|
||||
'Tenant not interested.': 'Customer Refusal',
|
||||
'Keep trying, keeps putting me off.': 'Customer Refusal',
|
||||
'Already insulated.': 'Not Viable',
|
||||
'Works all day.': 'No Answer',
|
||||
'PROPERTY COVER IN FOILAGE AND SHRUBS': 'Access Issues',
|
||||
'ACCESS IVY GROWTH, LEAN TO / CONSERVATORY IN WAY OF REAR': 'Not Viable',
|
||||
"Tenant unwell. Doesn't want survey.": 'No Answer',
|
||||
'Wont empty loft.': 'Access Issues',
|
||||
'LOFT FULLY BOARDED AS PREVIOUSLY DISCUSSED WITH CUSTOMER BY PREVIOUS SURVEYOR': 'Access Issues',
|
||||
"Property can't be done.": 'Not Viable',
|
||||
'Works everyday. Will call.': 'No Answer',
|
||||
'A LOT OF FOLIAGE IN WAY, PROPERTY LOOKS EMPTY FROM OUTSIDE?': 'Void',
|
||||
"Very old tenant. Said they didn't want it.": 'Customer Refusal',
|
||||
'Covered in ivy. Unable to remove.': 'Access Issues',
|
||||
'Climbers on walls': 'Access Issues',
|
||||
'Will not remove foliage': 'Access Issues',
|
||||
'Not Interested.': 'Customer Refusal',
|
||||
'OFF GAS': 'unknown',
|
||||
'Tenant not interested': 'Customer Refusal',
|
||||
'Will call me. Left my number.': 'Rescheduled',
|
||||
'Keep trying but keeps putting me off': 'Customer Refusal',
|
||||
'Moving out.': 'Void',
|
||||
'Booked in': 'Recheduled',
|
||||
'Refused Survey': 'Customr Refusal',
|
||||
'Big dogs running around front garden.': 'Access Issues',
|
||||
'CUSTOMER HAS CLADDED WALL AT REAR IN CONSERVATORY, REFUSED INTERNAL DRILL': 'Customer Refusal',
|
||||
'Booked in.': 'Rescheduled',
|
||||
'WRONG ADDRESS?': 'unknown',
|
||||
'Works everyday. Will call me.': 'No Answer',
|
||||
'Will not remove foliage.': 'Access Issues'
|
||||
}
|
||||
|
|
@ -194,5 +194,17 @@ PROPERTY_MAPPING = {
|
|||
'Maisonette 2 Ext. Wall': 'maisonette',
|
||||
'5 Ext. Wall Flat': 'flat',
|
||||
'Bungalow Semi Detached': 'bungalow',
|
||||
'COMINT': 'unknown'
|
||||
'COMINT': 'unknown',
|
||||
'12 SBEDSIT': 'bedsit',
|
||||
'01 HOUSE': 'house',
|
||||
'05 BEDSIT': 'bedsit',
|
||||
'14 SFLAT': 'flat',
|
||||
'09 PBEDSIT': 'bedsit',
|
||||
'10 PBUNGALOW': 'bungalow',
|
||||
'13 SBUNGALOW': 'bungalow',
|
||||
'11 PFLAT': 'flat',
|
||||
'02 FLAT': 'flat',
|
||||
'04 MAISONETTE': 'maisonette',
|
||||
'01 HOUSE MID': 'house',
|
||||
'03 BUNGALOW': 'bungalow'
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue