diff --git a/.idea/Model.iml b/.idea/Model.iml
index 96ad7a95..762580d9 100644
--- a/.idea/Model.iml
+++ b/.idea/Model.iml
@@ -7,7 +7,7 @@
-
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index fb10c6b0..c916a158 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/asset_list/AssetList.py b/asset_list/AssetList.py
index 31b11c66..306edd99 100644
--- a/asset_list/AssetList.py
+++ b/asset_list/AssetList.py
@@ -344,6 +344,7 @@ class AssetList:
self.standardised_asset_list = self.raw_asset_list.copy()
# Will be used to store aggregated figures against the various work types
self.work_type_figures = {}
+ self.work_type_breakdowns = {}
self.flat_data = None
self.duplicated_addresses = None
@@ -577,7 +578,7 @@ class AssetList:
self.standardised_asset_list[self.landlord_wall_construction] = np.where(
self.standardised_asset_list[self.landlord_wall_construction].str.lower().str.contains(
"average thermal transmittance"
- ),
+ ) == True,
"new build - average thermal transmittance",
self.standardised_asset_list[self.landlord_wall_construction]
)
@@ -1019,6 +1020,23 @@ class AssetList:
)
)
+ # Also include work without the SAP filter as optimistic
+ self.standardised_asset_list["non_intrusive_indicates_cavity_extraction_no_sap_filter"] = (
+ (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", "COMPACTED BEAD", "FIBRE BATT NO CAVITY", "EMPTY NARROW BELOW 30mm"]
+ )
+ )
+ )
+
+ # Adjust
+ self.standardised_asset_list["non_intrusive_indicates_cavity_extraction_no_sap_filter"] = np.where(
+ self.standardised_asset_list["non_intrusive_indicates_cavity_extraction"],
+ False,
+ self.standardised_asset_list["non_intrusive_indicates_cavity_extraction_no_sap_filter"]
+ )
+
######################################################
# Solar
######################################################
@@ -1109,8 +1127,7 @@ class AssetList:
) | (
self.standardised_asset_list[
"walls_u_value"].apply(
- lambda x: x <= 0.7 if not pd.isnull(
- x) else False
+ lambda x: x <= 0.7 if not pd.isnull(x) else False
)
)
)
@@ -1322,26 +1339,58 @@ class AssetList:
# ~self.standardised_asset_list["solar_eligible_other_floor_needs_loft"]
)
+ blocks_of_flats = self.standardised_asset_list[
+ self.standardised_asset_list[self.STANDARD_PROPERTY_TYPE] == "block of flats"
+ ]
+
+ non_blocks_of_flats = self.standardised_asset_list[
+ self.standardised_asset_list[self.STANDARD_PROPERTY_TYPE] != "block of flats"
+ ]
+
# Produce some aggregate figures
self.work_type_figures = {
# Empty cavity from non-intrusives
- "Empty Cavity (non-intrusives)": (
- self.standardised_asset_list["non_intrusive_indicates_empty_cavity"].sum()
+ "Empty Cavity (non-intrusives)": non_blocks_of_flats["non_intrusive_indicates_empty_cavity"].sum(),
+ "Empty Cavity (non-intrusives, blocks of flats)": (
+ blocks_of_flats["non_intrusive_indicates_empty_cavity"].sum()
),
"Empty Cavity (non-intrusives, no SAP filter)": (
- self.standardised_asset_list["non_intrusive_indicates_empty_cavity_no_sap_filter"].sum()
+ non_blocks_of_flats["non_intrusive_indicates_empty_cavity_no_sap_filter"].sum()
+ ),
+ "Empty Cavity (non-intrusives, no SAP filter, blocks of flats)": (
+ blocks_of_flats["non_intrusive_indicates_empty_cavity_no_sap_filter"].sum()
),
"Empty Cavity (EPC)": (
(
- self.standardised_asset_list["epc_indicates_empty_cavity"] &
- ~self.standardised_asset_list["non_intrusive_indicates_empty_cavity"]
+ non_blocks_of_flats["epc_indicates_empty_cavity"] &
+ ~non_blocks_of_flats["non_intrusive_indicates_empty_cavity"]
+ ).sum()
+ ),
+ "Empty Cavity (EPC, blocks of flat)": (
+ (
+ blocks_of_flats["epc_indicates_empty_cavity"] &
+ ~blocks_of_flats["non_intrusive_indicates_empty_cavity"]
).sum()
),
"Cavity Extraction": (
+ (
+ ~non_blocks_of_flats["non_intrusive_indicates_empty_cavity"] &
+ ~non_blocks_of_flats["epc_indicates_empty_cavity"] &
+ non_blocks_of_flats["non_intrusive_indicates_cavity_extraction"]
+ ).sum()
+ ),
+ "Cavity Extraction (blocks of flats)": (
+ (
+ ~blocks_of_flats["non_intrusive_indicates_empty_cavity"] &
+ ~blocks_of_flats["epc_indicates_empty_cavity"] &
+ blocks_of_flats["non_intrusive_indicates_cavity_extraction"]
+ ).sum()
+ ),
+ "Cavity Extraction (no SAP filter)": (
(
~self.standardised_asset_list["non_intrusive_indicates_empty_cavity"] &
~self.standardised_asset_list["epc_indicates_empty_cavity"] &
- self.standardised_asset_list["non_intrusive_indicates_cavity_extraction"]
+ self.standardised_asset_list["non_intrusive_indicates_cavity_extraction_no_sap_filter"]
).sum()
),
"Solar PV (Solid Floor)": (
@@ -1398,6 +1447,15 @@ class AssetList:
"Non-Intrusive Data Showed Cavity Extraction",
self.standardised_asset_list["cavity_reason"]
)
+ # extraction no sap filter
+ self.standardised_asset_list["cavity_reason"] = np.where(
+ (
+ self.standardised_asset_list["non_intrusive_indicates_cavity_extraction_no_sap_filter"] &
+ pd.isnull(self.standardised_asset_list["cavity_reason"])
+ ),
+ "Non-Intrusive Data Showed Cavity Extraction but all SAP scores allowed",
+ self.standardised_asset_list["cavity_reason"]
+ )
# Flag solar
self.standardised_asset_list["solar_reason"] = None
diff --git a/asset_list/app.py b/asset_list/app.py
index 09ccac02..84999e93 100644
--- a/asset_list/app.py
+++ b/asset_list/app.py
@@ -246,43 +246,43 @@ 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)
- # data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Colchester"
- # data_filename = "Warmfront data- Colchester Borough Homes (Complete).xlsx"
- # sheet_name = "Sheet1"
- # postcode_column = 'Full Address.1'
- # fulladdress_column = "Full Address"
- # address1_column = None
- # address1_method = "first_word"
- # address_cols_to_concat = []
- # missing_postcodes_method = None
- # landlord_year_built = "Build Date"
- # landlord_os_uprn = None
- # landlord_property_type = "Property Type"
- # landlord_wall_construction = "Wallinsul"
- # landlord_heating_system = "HeatSorc"
- # landlord_existing_pv = None
- # landlord_property_id = "Property Reference"
-
- # For Westward
- data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Westward"
- data_filename = "WESTWARD - completed list..xlsx"
+ data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Colchester"
+ data_filename = "Warmfront data- Colchester Borough Homes (Complete).xlsx"
sheet_name = "Sheet1"
- postcode_column = "WFT EDIT Postcode"
- fulladdress_column = "Address"
+ postcode_column = 'Full Address.1'
+ fulladdress_column = "Full Address"
address1_column = None
- address1_method = "house_number_extraction"
+ address1_method = "first_word"
address_cols_to_concat = []
missing_postcodes_method = None
- landlord_year_built = "Build date"
- landlord_os_uprn = "UPRN"
- landlord_property_type = "Location type"
- landlord_wall_construction = "Wall Construction (EPC)"
- landlord_heating_system = "Heat Source"
- landlord_existing_pv = "PV (Y/N)"
- landlord_property_id = "Place ref"
+ landlord_year_built = "Build Date"
+ landlord_os_uprn = None
+ landlord_property_type = "Property Type"
+ landlord_wall_construction = "Wallinsul"
+ landlord_heating_system = "HeatSorc"
+ landlord_existing_pv = None
+ landlord_property_id = "Property Reference"
+
+ # For Westward
+ # data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Westward"
+ # data_filename = "WESTWARD - completed list..xlsx"
+ # sheet_name = "Sheet1"
+ # postcode_column = "WFT EDIT 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 = "UPRN"
+ # landlord_property_type = "Location type"
+ # landlord_wall_construction = "Wall Construction (EPC)"
+ # landlord_heating_system = "Heat Source"
+ # landlord_existing_pv = "PV (Y/N)"
+ # landlord_property_id = "Place ref"
# Maps addresses to uprn in problematic cases
- MANUAL_UPRN_MAP = {}
+ manual_uprn_map = {}
asset_list = AssetList(
local_filepath=os.path.join(data_folder, data_filename),
@@ -352,7 +352,7 @@ def app():
epc_data_chunk, errors_chunk, no_epc_chunk = get_data(
df=chunk,
row_id_name=asset_list.DOMNA_PROPERTY_ID,
- manual_uprn_map=MANUAL_UPRN_MAP,
+ manual_uprn_map=manual_uprn_map,
)
# We now retrieve any failed properties
@@ -360,7 +360,7 @@ def app():
epc_data_failed, _, _ = get_data(
df=chunk_failed,
row_id_name=asset_list.DOMNA_PROPERTY_ID,
- manual_uprn_map=MANUAL_UPRN_MAP,
+ manual_uprn_map=manual_uprn_map,
epc_api_only=False
)
@@ -464,6 +464,7 @@ def app():
)
cleaned = msgpack.unpackb(cleaned, raw=False)
+ # TODO: We should break out the identification of work types to flag blocks of flats specifically
asset_list.identify_worktypes(cleaned)
pprint(asset_list.work_type_figures)
diff --git a/backend/Funding.py b/backend/Funding.py
index f0780c51..2839c7ff 100644
--- a/backend/Funding.py
+++ b/backend/Funding.py
@@ -149,7 +149,8 @@ class Funding:
:return:
"""
measure_table = pd.DataFrame([
- m for m in self.recommendations if m in measures and m["default"]
+ m for m in self.recommendations if
+ (m["type"] in measures) or (m["measure_type"] in measures) and m["default"]
])
measure_table["post_install_sap"] = measure_table["sap_points"] + self.starting_sap
@@ -180,13 +181,10 @@ class Funding:
measure_table["cost_minus_funding"] = measure_table["total"] - measure_table["estimated_funding"]
measure_table["cost_minus_funding_per_sap"] = measure_table["cost_minus_funding"] / measure_table["sap_points"]
measure_table = measure_table.sort_values(["cost_minus_funding_per_sap", "total"], ascending=[True, False])
- # Recommend the measure, with estimated funding amount
- recommended_measure = measure_table.head(1)
- return {
- "measure_type": recommended_measure["measure_type"],
- "estimated_funding": recommended_measure["estimated_funding"]
- }
+ return measure_table[
+ ["type", "measure_type", "Cost Savings", "estimated_funding"]
+ ].rename(columns={"Cost Savings": "project_score"}).to_dict("records")
def sap_to_eco_band(self, sap_points):
"""
diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py
index 76c172ee..d82e774b 100644
--- a/backend/app/plan/router.py
+++ b/backend/app/plan/router.py
@@ -825,7 +825,7 @@ async def trigger_plan(body: PlanTriggerRequest):
property_recommendations=recommendations[p.id],
project_scores_matrix=eco_project_scores_matrix,
whlg_eligible_postcodes=whlg_eligible_postcodes,
- gbis_abs_rate=20,
+ gbis_abs_rate=15,
eco4_abs_rate=15,
)
funding_calulator.check_eligibiltiy()
diff --git a/etl/customers/remote_assessments/app.py b/etl/customers/remote_assessments/app.py
index aac0a1a6..fc3b7ec6 100644
--- a/etl/customers/remote_assessments/app.py
+++ b/etl/customers/remote_assessments/app.py
@@ -4,7 +4,7 @@ from dotenv import load_dotenv
from utils.s3 import save_csv_to_s3
from etl.find_my_epc.AssetListEpcData import AssetListEpcData
-PORTFOLIO_ID = 137
+PORTFOLIO_ID = 134
USER_ID = 8
load_dotenv(dotenv_path="backend/.env")
@@ -19,10 +19,25 @@ def app():
asset_list = [
{
- "address": "41 Gainsborough Way",
- "postcode": "BA21 5XU",
- "uprn": 30016708,
+ "address": "Flat 2, 42 Malden Road, London NW5 3HG",
+ "postcode": "NW5 3HG",
+ "uprn": 5117165,
},
+ {
+ "address": "15 Bournville Lane",
+ "postcode": "B30 2JY",
+ "uprn": 100070301128
+ },
+ {
+ "address": "34 Bournville Lane",
+ "postcode": "B30 2LN",
+ "uprn": 100070301140
+ },
+ {
+ "address": "36 Bournville Lane",
+ "postcode": "B30 2LN",
+ "uprn": 100070301142
+ }
]
asset_list = pd.DataFrame(asset_list)
@@ -52,9 +67,21 @@ def app():
valuation_data = [
{
- "uprn": 30016708,
- "valuation": 189000
- }
+ "uprn": 5117165,
+ "valuation": 467_000
+ },
+ {
+ "uprn": 100070301128,
+ "valuation": 335_000
+ },
+ {
+ "uprn": 100070301140,
+ "valuation": 276_000
+ },
+ {
+ "uprn": 100070301142,
+ "valuation": 276_000
+ },
]
# Store valuation data to s3
valuation_filename = f"{USER_ID}/{PORTFOLIO_ID}/valuation.csv"
diff --git a/etl/find_my_epc/AssetListEpcData.py b/etl/find_my_epc/AssetListEpcData.py
index bce8cd1f..1d2e1472 100644
--- a/etl/find_my_epc/AssetListEpcData.py
+++ b/etl/find_my_epc/AssetListEpcData.py
@@ -72,12 +72,20 @@ class AssetListEpcData:
epc_searcher.find_property(skip_os=True)
if epc_searcher.newest_epc is None:
continue
-
- find_epc_searcher = RetrieveFindMyEpc(
- address=epc_searcher.newest_epc["address1"],
- postcode=epc_searcher.newest_epc["postcode"]
- )
- find_epc_data = find_epc_searcher.retrieve_newest_find_my_epc_data()
+ # Attempt both methods:
+ try:
+ find_epc_searcher = RetrieveFindMyEpc(
+ address=epc_searcher.newest_epc["address1"] + ", " + epc_searcher.newest_epc["address2"],
+ postcode=epc_searcher.newest_epc["postcode"]
+ )
+ find_epc_data = find_epc_searcher.retrieve_newest_find_my_epc_data()
+ except Exception as e:
+ logger.error(f"Error retrieving find my epc data: {e}")
+ find_epc_searcher = RetrieveFindMyEpc(
+ address=epc_searcher.newest_epc["address1"],
+ postcode=epc_searcher.newest_epc["postcode"]
+ )
+ find_epc_data = find_epc_searcher.retrieve_newest_find_my_epc_data()
time.sleep(0.5)
# We need uprn
diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py
index dd81680a..e4dd3a78 100644
--- a/recommendations/HeatingRecommender.py
+++ b/recommendations/HeatingRecommender.py
@@ -852,6 +852,8 @@ class HeatingRecommender:
else:
heating_simulation_config["mainheat_energy_eff_ending"] = self.property.data["mainheat-energy-eff"]
+ # TODO:We possibly shouldn't touch the hot water energy efficiency if we aren't recommending dual immersion
+ # we'll keep this for the moment though
if self.property.data["hot-water-energy-eff"] in ["Very Poor", "Poor"]:
heating_simulation_config["hot_water_energy_eff_ending"] = "Average"
else: