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