mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
added additional secondary heating recommendation
This commit is contained in:
parent
6d01490962
commit
b01635ddd6
9 changed files with 169 additions and 25 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$/open_uprn" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Stonewater-wave-3" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Fastapi-backend" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PyNamespacePackagesService">
|
<component name="PyNamespacePackagesService">
|
||||||
|
|
|
||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
|
@ -3,7 +3,7 @@
|
||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="sdkName" value="Python 3.10 (backend)" />
|
<option name="sdkName" value="Python 3.10 (backend)" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Stonewater-wave-3" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Fastapi-backend" project-jdk-type="Python SDK" />
|
||||||
<component name="PyCharmProfessionalAdvertiser">
|
<component name="PyCharmProfessionalAdvertiser">
|
||||||
<option name="shown" value="true" />
|
<option name="shown" value="true" />
|
||||||
</component>
|
</component>
|
||||||
|
|
|
||||||
|
|
@ -792,9 +792,14 @@ class GoogleSolarApi:
|
||||||
property_instance = [p for p in input_properties if p.id == unit["property_id"]][0]
|
property_instance = [p for p in input_properties if p.id == unit["property_id"]][0]
|
||||||
# At this level, we check if the property is suitable for solar and if now, skip
|
# At this level, we check if the property is suitable for solar and if now, skip
|
||||||
# Or if we have a solar non-invasive recommendation
|
# Or if we have a solar non-invasive recommendation
|
||||||
|
|
||||||
|
non_invasive_rec = next(
|
||||||
|
(r for r in property_instance.non_invasive_recommendations if r["type"] == "solar_pv"), {}
|
||||||
|
).get("array_wattage")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(not property_instance.is_solar_pv_valid()) or
|
(not property_instance.is_solar_pv_valid()) or
|
||||||
[r for r in property_instance.non_invasive_recommendations if r["type"] == "solar_pv"]
|
non_invasive_rec is not None
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -394,7 +394,7 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
logger.info("Getting the inputs")
|
logger.info("Getting the inputs")
|
||||||
plan_input = read_csv_from_s3(bucket_name=get_settings().PLAN_TRIGGER_BUCKET, filepath=body.trigger_file_path)
|
plan_input = read_csv_from_s3(bucket_name=get_settings().PLAN_TRIGGER_BUCKET, filepath=body.trigger_file_path)
|
||||||
# Check for duplicate UPRNS
|
# Check for duplicate UPRNS
|
||||||
input_uprns = [x.get("uprn") for x in plan_input if "uprn" in x]
|
input_uprns = [x.get("uprn") for x in plan_input if "uprn" in x and x.get("uprn")]
|
||||||
if input_uprns:
|
if input_uprns:
|
||||||
# Check for dupes
|
# Check for dupes
|
||||||
if len(input_uprns) != len(set(input_uprns)):
|
if len(input_uprns) != len(set(input_uprns)):
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import os
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from etl.epc.settings import EARLIEST_EPC_DATE
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
@ -236,7 +237,7 @@ def caha():
|
||||||
address = remap_address(address)
|
address = remap_address(address)
|
||||||
|
|
||||||
find_epc_searcher = RetrieveFindMyEpc(address=address, postcode=postcode)
|
find_epc_searcher = RetrieveFindMyEpc(address=address, postcode=postcode)
|
||||||
find_epc_data = find_epc_searcher.retrieve_newest_find_my_epc_data()
|
find_epc_data = find_epc_searcher.retrieve_newest_find_my_epc_data(sap_2012_date=EARLIEST_EPC_DATE)
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
# We need uprn
|
# We need uprn
|
||||||
searcher = SearchEpc(
|
searcher = SearchEpc(
|
||||||
|
|
@ -249,18 +250,102 @@ def caha():
|
||||||
searcher.find_property(skip_os=True)
|
searcher.find_property(skip_os=True)
|
||||||
newest_epc = searcher.newest_epc
|
newest_epc = searcher.newest_epc
|
||||||
|
|
||||||
|
uprn = newest_epc["uprn"]
|
||||||
|
if address in ["Flat D, 11 Victoria Avenue", "Flat B, 11 Victoria Avenue"]:
|
||||||
|
uprn = None
|
||||||
|
|
||||||
extracted_data.append(
|
extracted_data.append(
|
||||||
{
|
{
|
||||||
"uprn": newest_epc["uprn"],
|
"uprn": uprn,
|
||||||
**find_epc_data,
|
**find_epc_data,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
asset_list.append(
|
asset_list.append(
|
||||||
{
|
{
|
||||||
"uprn": newest_epc["uprn"],
|
"uprn": uprn,
|
||||||
"address": home["Address letter or number"],
|
"address": address,
|
||||||
"postcode": home["Postcode"],
|
"postcode": home["Postcode"],
|
||||||
"property_type": newest_epc["property-type"],
|
"property_type": newest_epc["property-type"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
non_invasive_recommendations = [
|
||||||
|
{
|
||||||
|
"uprn": r["uprn"],
|
||||||
|
"recommendations": r["recommendations"]
|
||||||
|
} for r in extracted_data
|
||||||
|
]
|
||||||
|
# for r in non_invasive_recommendations:
|
||||||
|
# new_recommendations = []
|
||||||
|
# extracted = [r for r in extracted_data if r["uprn"] == r["uprn"]][0]
|
||||||
|
# for rec in r["recommendations"]:
|
||||||
|
# if extracted["hotwater-description"] == "Gas boiler/circulator, no cylinder thermostat":
|
||||||
|
# if rec["type"] in ["hot_water_tank_insulation", "cylinder_thermostat"]:
|
||||||
|
# continue
|
||||||
|
# rec["survey"] = False
|
||||||
|
# new_recommendations.append(rec)
|
||||||
|
# r["recommendations"] = new_recommendations
|
||||||
|
|
||||||
|
# We model the two properties separately
|
||||||
|
asset_list = pd.DataFrame(asset_list)
|
||||||
|
# Drop Flat D, 11 Victoria Avenue
|
||||||
|
asset_list1 = asset_list[asset_list["address"] != "Flat D, 11 Victoria Avenue"]
|
||||||
|
asset_list2 = asset_list[asset_list["address"] == "Flat D, 11 Victoria Avenue"]
|
||||||
|
|
||||||
|
# Store the asset list in s3
|
||||||
|
filename = f"{USER_ID}/{CAHA_PORTFOLIO_ID}/asset_list1.csv"
|
||||||
|
save_csv_to_s3(
|
||||||
|
dataframe=asset_list1,
|
||||||
|
bucket_name="retrofit-plan-inputs-dev",
|
||||||
|
file_name=filename
|
||||||
|
)
|
||||||
|
|
||||||
|
filename2 = f"{USER_ID}/{CAHA_PORTFOLIO_ID}/asset_list2.csv"
|
||||||
|
save_csv_to_s3(
|
||||||
|
dataframe=asset_list2,
|
||||||
|
bucket_name="retrofit-plan-inputs-dev",
|
||||||
|
file_name=filename2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Store the non-invasive recommendations in s3
|
||||||
|
non_invasive_recommendations_filename = f"{USER_ID}/{CAHA_PORTFOLIO_ID}/non_invasive_recommendations.csv"
|
||||||
|
save_csv_to_s3(
|
||||||
|
dataframe=pd.DataFrame(non_invasive_recommendations),
|
||||||
|
bucket_name="retrofit-plan-inputs-dev",
|
||||||
|
file_name=non_invasive_recommendations_filename
|
||||||
|
)
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"portfolio_id": str(CAHA_PORTFOLIO_ID),
|
||||||
|
"housing_type": "Social",
|
||||||
|
"goal": "Increasing EPC",
|
||||||
|
"goal_value": "C",
|
||||||
|
"trigger_file_path": filename,
|
||||||
|
"already_installed_file_path": "",
|
||||||
|
"patches_file_path": "",
|
||||||
|
"non_invasive_recommendations_file_path": non_invasive_recommendations_filename,
|
||||||
|
"valuation_file_path": "",
|
||||||
|
"scenario_name": "Wave 3 Packages",
|
||||||
|
"multi_plan": True,
|
||||||
|
"budget": None,
|
||||||
|
"exclusions": ["boiler_upgrade"]
|
||||||
|
}
|
||||||
|
print(body)
|
||||||
|
|
||||||
|
body2 = {
|
||||||
|
"portfolio_id": str(CAHA_PORTFOLIO_ID),
|
||||||
|
"housing_type": "Social",
|
||||||
|
"goal": "Increasing EPC",
|
||||||
|
"goal_value": "C",
|
||||||
|
"trigger_file_path": filename2,
|
||||||
|
"already_installed_file_path": "",
|
||||||
|
"patches_file_path": "",
|
||||||
|
"non_invasive_recommendations_file_path": non_invasive_recommendations_filename,
|
||||||
|
"valuation_file_path": "",
|
||||||
|
"scenario_name": "Wave 3 Packages",
|
||||||
|
"multi_plan": True,
|
||||||
|
"budget": None,
|
||||||
|
"exclusions": ["boiler_upgrade"]
|
||||||
|
}
|
||||||
|
print(body2)
|
||||||
|
|
|
||||||
|
|
@ -1411,5 +1411,45 @@ def find_remaining_surveys():
|
||||||
|
|
||||||
assert needed.shape[0] + costed.shape[0] == surveyed.shape[0]
|
assert needed.shape[0] + costed.shape[0] == surveyed.shape[0]
|
||||||
|
|
||||||
|
|
||||||
|
def append_stonewater_id():
|
||||||
|
"""
|
||||||
|
This completes an adhoc request from Stonewater to add in their organisation Reference onto the model
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_proposed_sample = pd.read_excel(
|
||||||
|
"/Users/khalimconn-kowlessar/Documents/hestia/Customers/Stonewater/Stonewater - Bid Packages WIP 13.11.24.xlsx",
|
||||||
|
sheet_name="Modelled Packages",
|
||||||
|
header=13
|
||||||
|
)
|
||||||
|
model_proposed_sample = model_proposed_sample[~pd.isnull(model_proposed_sample["Address ID"])]
|
||||||
|
model_proposed_sample["Address ID"] = model_proposed_sample["Address ID"].astype(int)
|
||||||
|
|
||||||
|
original_archetypes = pd.read_excel(
|
||||||
|
"/Users/khalimconn-kowlessar/Documents/hestia/Customers/Stonewater/Stonewater SHDF_3_0_Board Triage 22.05.24 "
|
||||||
|
"- Archetyped V3.1.xlsx",
|
||||||
|
header=4
|
||||||
|
)
|
||||||
|
original_archetypes = original_archetypes[~pd.isnull(original_archetypes["Address ID"])]
|
||||||
|
original_archetypes = original_archetypes[original_archetypes["Address ID"] != "Address ID"]
|
||||||
|
original_archetypes["Address ID"] = original_archetypes["Address ID"].astype(int)
|
||||||
|
|
||||||
|
matched = model_proposed_sample.merge(
|
||||||
|
original_archetypes[["Address ID", 'Org. ref.']],
|
||||||
|
on="Address ID",
|
||||||
|
how="left"
|
||||||
|
)
|
||||||
|
|
||||||
|
if pd.isnull(matched["Org. ref."]).sum():
|
||||||
|
raise ValueError("Something went wrong")
|
||||||
|
|
||||||
|
# Save as CSV
|
||||||
|
matched.to_excel(
|
||||||
|
"/Users/khalimconn-kowlessar/Documents/hestia/Customers/Stonewater/Stonewater IDs.xlsx",
|
||||||
|
sheet_name="Proposed Wave 3 Sample",
|
||||||
|
index=False
|
||||||
|
)
|
||||||
|
|
||||||
# if __name__ == "__main__":
|
# if __name__ == "__main__":
|
||||||
# main()
|
# main()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import pandas as pd
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
@ -25,7 +26,7 @@ class RetrieveFindMyEpc:
|
||||||
|
|
||||||
self.address_cleaned = self.address.replace(",", "").replace(" ", "").lower()
|
self.address_cleaned = self.address.replace(",", "").replace(" ", "").lower()
|
||||||
|
|
||||||
def retrieve_newest_find_my_epc_data(self):
|
def retrieve_newest_find_my_epc_data(self, sap_2012_date=None):
|
||||||
"""
|
"""
|
||||||
For a post code and address, we pull out all the required data from the find my epc website
|
For a post code and address, we pull out all the required data from the find my epc website
|
||||||
"""
|
"""
|
||||||
|
|
@ -188,7 +189,7 @@ class RetrieveFindMyEpc:
|
||||||
raise ValueError(f"Missing key: {key}")
|
raise ValueError(f"Missing key: {key}")
|
||||||
|
|
||||||
# Finally, we format the recommendations
|
# Finally, we format the recommendations
|
||||||
recommendations = self.format_recommendations(recommendations)
|
recommendations = self.format_recommendations(recommendations, assessment_data, sap_2012_date)
|
||||||
|
|
||||||
resulting_data = {
|
resulting_data = {
|
||||||
'epc_certificate': epc_certificate,
|
'epc_certificate': epc_certificate,
|
||||||
|
|
@ -205,11 +206,12 @@ class RetrieveFindMyEpc:
|
||||||
return resulting_data
|
return resulting_data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_recommendations(recommendations):
|
def format_recommendations(recommendations, assessment_data, sap_2012_date=None):
|
||||||
"""
|
"""
|
||||||
This function converts the recommendations to a format that we can use in the engine as a non-intrusive survey
|
This function converts the recommendations to a format that we can use in the engine as a non-intrusive survey
|
||||||
:param recommendations:
|
:param recommendations: The recommendations from the EPC
|
||||||
:return:
|
:param assessment_data: The assessment data from the EPC
|
||||||
|
:param sap_2012_date: The date of the SAP 2012 update
|
||||||
"""
|
"""
|
||||||
|
|
||||||
measure_map = {
|
measure_map = {
|
||||||
|
|
@ -246,17 +248,23 @@ class RetrieveFindMyEpc:
|
||||||
"Double glazing": ["double_glazing"],
|
"Double glazing": ["double_glazing"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
survey = True
|
||||||
|
if sap_2012_date is not None:
|
||||||
|
certificate_date = datetime.strptime(assessment_data["Date of certificate"], "%d %B %Y")
|
||||||
|
if certificate_date < pd.to_datetime(sap_2012_date):
|
||||||
|
survey = False
|
||||||
|
|
||||||
formatted_recommendations = []
|
formatted_recommendations = []
|
||||||
for rec in recommendations:
|
for rec in recommendations:
|
||||||
|
|
||||||
mapped = measure_map[rec["measure"]]
|
mapped = measure_map[rec["measure"]]
|
||||||
for measure in mapped:
|
for measure in mapped:
|
||||||
formatted_recommendations.append(
|
to_append = {
|
||||||
{
|
"type": measure,
|
||||||
"type": measure,
|
"sap_points": rec["sap_points"],
|
||||||
"sap_points": rec["sap_points"],
|
"survey": survey,
|
||||||
"survey": True
|
}
|
||||||
}
|
if measure == "solar_pv":
|
||||||
)
|
to_append["suitable"] = True
|
||||||
|
formatted_recommendations.append(to_append)
|
||||||
|
|
||||||
return formatted_recommendations
|
return formatted_recommendations
|
||||||
|
|
|
||||||
|
|
@ -60,15 +60,21 @@ class HotwaterRecommendations:
|
||||||
|
|
||||||
# If there is no system present, but access to the mains, we
|
# If there is no system present, but access to the mains, we
|
||||||
|
|
||||||
|
has_tank_recommendation = [r for r in self.recommendations if r["type"] == "hot_water_tank_insulation"]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(self.property.hotwater["heater_type"] in ["electric immersion"]) &
|
(self.property.hotwater["heater_type"] in ["electric immersion"]) &
|
||||||
(self.property.data["hot-water-energy-eff"] == "Very Poor") &
|
(self.property.data["hot-water-energy-eff"] == "Very Poor") &
|
||||||
(self.property.hotwater["no_system_present"] is None)
|
(self.property.hotwater["no_system_present"] is None) &
|
||||||
|
len(has_tank_recommendation) == 0
|
||||||
):
|
):
|
||||||
self.recommend_tank_insulation(phase=phase)
|
self.recommend_tank_insulation(phase=phase)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.property.hotwater["clean_description"] == "From main system, no cylinder thermostat":
|
has_cylinder_recommendation = [r for r in self.recommendations if r["type"] == "cylinder_thermostat"]
|
||||||
|
|
||||||
|
if ((self.property.hotwater["clean_description"] == "From main system, no cylinder thermostat") &
|
||||||
|
(len(has_cylinder_recommendation) == 0)):
|
||||||
self.recommend_cylinder_thermostat(phase=phase)
|
self.recommend_cylinder_thermostat(phase=phase)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class SecondaryHeating:
|
||||||
ACCEPTED_MAINHEAT_DESCRIPTIONS = ["Boiler and radiators, mains gas"]
|
ACCEPTED_MAINHEAT_DESCRIPTIONS = ["Boiler and radiators, mains gas"]
|
||||||
ACCEPTED_SECONDHEAT_DESCRIPTIONS = ["Room heaters, electric"]
|
ACCEPTED_SECONDHEAT_DESCRIPTIONS = ["Room heaters, electric"]
|
||||||
# These are the heaters where works are required to remove them
|
# These are the heaters where works are required to remove them
|
||||||
FIXED_HEATER_DESCRIPTIONS = ["Room heaters, electric"]
|
FIXED_HEATER_DESCRIPTIONS = ["Room heaters, electric", 'Portable electric heaters (assumed)']
|
||||||
|
|
||||||
def __init__(self, property_instance: Property):
|
def __init__(self, property_instance: Property):
|
||||||
self.property = property_instance
|
self.property = property_instance
|
||||||
|
|
@ -34,7 +34,7 @@ class SecondaryHeating:
|
||||||
|
|
||||||
if self.property.data['secondheat-description'] in self.FIXED_HEATER_DESCRIPTIONS:
|
if self.property.data['secondheat-description'] in self.FIXED_HEATER_DESCRIPTIONS:
|
||||||
# We have an associated cost otherwise, there is no cost
|
# We have an associated cost otherwise, there is no cost
|
||||||
n_rooms = self.property.data['number-heated-rooms']
|
n_rooms = self.property.data['number-habitable-rooms'] - self.property.data['number-heated-rooms']
|
||||||
else:
|
else:
|
||||||
n_rooms = 0
|
n_rooms = 0
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue