From 53e0a651c9afff2fbb7464783c103062243d3167 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sun, 22 Jun 2025 16:42:41 +0100 Subject: [PATCH] debugging a recommendation for hhrsh where the property currently has underfloor heating --- .idea/Model.iml | 2 +- .idea/misc.xml | 2 +- backend/app/plan/schemas.py | 4 ++ backend/engine/engine.py | 1 - recommendations/HeatingRecommender.py | 8 +++ sfr/principal_pitch/0_prepare_sample.py | 47 +++++++++++++++ sfr/principal_pitch/1_prepare_data.py | 76 +++++++++++++++++++++++++ sfr/principal_pitch/2_export_data.py | 0 8 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 sfr/principal_pitch/0_prepare_sample.py create mode 100644 sfr/principal_pitch/1_prepare_data.py create mode 100644 sfr/principal_pitch/2_export_data.py diff --git a/.idea/Model.iml b/.idea/Model.iml index 09f2e496..c6561970 100644 --- a/.idea/Model.iml +++ b/.idea/Model.iml @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index fb10c6b0..50cad4ca 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,7 +3,7 @@ - + diff --git a/backend/app/plan/schemas.py b/backend/app/plan/schemas.py index 48300f2a..6b8b192d 100644 --- a/backend/app/plan/schemas.py +++ b/backend/app/plan/schemas.py @@ -96,3 +96,7 @@ class PlanTriggerRequest(BaseModel): # When performing a remote assessment, if this has been set, it will allow the engine to # pull data from the find my epc website, to utilise as part of a remote assessment event_type: Optional[Literal["remote_assessment"]] = None + + # If true, before optimising the engine will select a slightly larger package, to account for the SAP 10 causing + # scores to drop by a few points + simulate_sap_10: Optional[bool] = False diff --git a/backend/engine/engine.py b/backend/engine/engine.py index 5316fd03..e2712e8d 100644 --- a/backend/engine/engine.py +++ b/backend/engine/engine.py @@ -30,7 +30,6 @@ import backend.app.assumptions as assumptions from backend.ml_models.api import ModelApi from backend.Property import Property -from backend.Funding import Funding from backend.apis.GoogleSolarApi import GoogleSolarApi from recommendations.optimiser.CostOptimiser import CostOptimiser diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py index 18e1110b..a776047b 100644 --- a/recommendations/HeatingRecommender.py +++ b/recommendations/HeatingRecommender.py @@ -57,6 +57,10 @@ class HeatingRecommender: }, # These are the heating types we need to produce a dual heating recommendation "dual": None + }, + 'Electric underfloor heating, electric storage heaters': { + # For this, we would recommend a heat pump + "dual": None } } @@ -109,6 +113,10 @@ class HeatingRecommender: hhr_suitable = no_mains or self.has_electric_heating_description or self.has_room_heaters + hhr_suitable = hhr_suitable and ( + "underfloor heating" not in self.property.main_heating["clean_description"] + ) + return ( hhr_suitable and (not ashp_only_heating_recommendation) and not self.has_ashp and ("high_heat_retention_storage_heater" in measures) diff --git a/sfr/principal_pitch/0_prepare_sample.py b/sfr/principal_pitch/0_prepare_sample.py new file mode 100644 index 00000000..dc1f0a1f --- /dev/null +++ b/sfr/principal_pitch/0_prepare_sample.py @@ -0,0 +1,47 @@ +""" +This is a script for preparing a sample for testing the end to end process, so that when Spring send us +data, we know it will work. +""" + +import pandas as pd + +birmingham_epcs = pd.read_csv( + "/Users/khalimconn-kowlessar/Documents/hestia/sfr/Spring JV/domestic-E08000025-Birmingham/certificates.csv" +) + +# We get the newest EPC, by UPRN and LODGEMENT_DATE +birmingham_epcs['LODGEMENT_DATE'] = pd.to_datetime(birmingham_epcs['LODGEMENT_DATE']) + +birmingham_epcs = birmingham_epcs.sort_values( + by=['UPRN', 'LODGEMENT_DATE'], + ascending=[True, False] +).drop_duplicates(subset='UPRN') + +# Take a sample of properties, EPC F or G, EPC lodged in 2025. We focus on houses/bingalows +sample = birmingham_epcs[ + (birmingham_epcs['CURRENT_ENERGY_RATING'].isin(['F', 'G'])) & + (birmingham_epcs['LODGEMENT_DATE'] >= '2025-01-01') & + (birmingham_epcs['PROPERTY_TYPE'].isin(['House', 'Bungalow'])) + ] + +# Prepare the sample, with just the columns we would expect to receive from Spring +# 1) UPRN +# 2) Address +# 3) Postcode +# 4) Property type +# 5) Built form +# 6) Number of bedrooms (we'll simulate this) +# 7) Number of bathrooms (we'll simulate this) +# 8) Valuation (We'll simulate this, around 200,000) + +sample = sample[['UPRN', 'ADDRESS', 'POSTCODE', 'PROPERTY_TYPE', 'BUILT_FORM']].copy() +sample['BEDROOMS'] = 3 # Simulating number of bedrooms +sample['BATHROOMS'] = 1 # Simulating number of bathrooms +sample['VALUATION'] = 200000 # Simulating valuation +sample.columns = [x.lower() for x in sample.columns] + +# Store this as a excel +sample.to_excel( + "/Users/khalimconn-kowlessar/Documents/hestia/sfr/Spring JV/birmingham_sample.xlsx", + index=False +) diff --git a/sfr/principal_pitch/1_prepare_data.py b/sfr/principal_pitch/1_prepare_data.py new file mode 100644 index 00000000..8e799b9e --- /dev/null +++ b/sfr/principal_pitch/1_prepare_data.py @@ -0,0 +1,76 @@ +""" +This script prepares the data for the principal pitch modelling +""" +import os +import pandas as pd +from dotenv import load_dotenv +from utils.s3 import save_csv_to_s3 +from etl.find_my_epc.AssetListEpcData import AssetListEpcData + +load_dotenv(dotenv_path="backend/.env") +EPC_AUTH_TOKEN = os.getenv("EPC_AUTH_TOKEN") +PORTFOLIO_ID = 206 +USER_ID = 8 +EPC_TARGET = "C" + +# Read the input file + +properties = pd.read_excel( + "/Users/khalimconn-kowlessar/Documents/hestia/sfr/Spring JV/birmingham_sample.xlsx" +) + +# Pull the non-invasive recommendations +asset_list_epc_client = AssetListEpcData( + asset_list=properties, + epc_auth_token=EPC_AUTH_TOKEN +) +asset_list_epc_client.get_data() +asset_list_epc_client.get_non_invasive_recommendations() +asset_list_epc_client.get_patch() +# TODO; Find some new, on-market opportunities that aren't on the EPC API, so we definitely have a patch + +# Store the asset list in s3 +filename = f"{USER_ID}/{PORTFOLIO_ID}/asset_list.csv" +save_csv_to_s3( + dataframe=properties, + bucket_name="retrofit-plan-inputs-dev", + file_name=filename +) + +# Store non-invasive recommendations in S3 +non_invasive_recommendations_filename = f"{USER_ID}/{PORTFOLIO_ID}/non_invasive_recommendations.csv" +save_csv_to_s3( + dataframe=pd.DataFrame(asset_list_epc_client.non_invasive_recommendations), + bucket_name="retrofit-plan-inputs-dev", + file_name=non_invasive_recommendations_filename +) + +# Store patches in S3 +patches_filename = "" +if asset_list_epc_client.patches: + patches_filename = f"{USER_ID}/{PORTFOLIO_ID}/patches.csv" + save_csv_to_s3( + dataframe=pd.DataFrame(asset_list_epc_client.patches), + bucket_name="retrofit-plan-inputs-dev", + file_name=patches_filename + ) + +body = { + "portfolio_id": str(PORTFOLIO_ID), + "housing_type": "Private", + "goal": "Increasing EPC", + "goal_value": "C", + "trigger_file_path": filename, + "already_installed_file_path": "", + "patches_file_path": patches_filename, + "non_invasive_recommendations_file_path": non_invasive_recommendations_filename, + "valuation_file_path": "", + "scenario_name": "EPC C", + "multi_plan": True, + "budget": None, + "ashp_cop": 3.5, + # This is new - when optimising, we drop scores by a few points to account for SAP 10 + "simulate_sap_10": True, + "exclusions": ["external_wall_insulation"] +} +print(body) diff --git a/sfr/principal_pitch/2_export_data.py b/sfr/principal_pitch/2_export_data.py new file mode 100644 index 00000000..e69de29b