mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
wip integrating additional recs with new code
This commit is contained in:
parent
050981ae82
commit
b20e00a736
10 changed files with 336 additions and 29 deletions
|
|
@ -357,6 +357,27 @@ class Property:
|
||||||
for config in epc_transformations:
|
for config in epc_transformations:
|
||||||
for k, v in config.items():
|
for k, v in config.items():
|
||||||
if k in phase_epc_transformation:
|
if k in phase_epc_transformation:
|
||||||
|
if "-energy-eff" in k:
|
||||||
|
# We take the highest value
|
||||||
|
if phase_epc_transformation[k] == "Very Good":
|
||||||
|
continue
|
||||||
|
elif phase_epc_transformation[k] == "Good":
|
||||||
|
if v == "Very Good":
|
||||||
|
phase_epc_transformation[k] = v
|
||||||
|
elif phase_epc_transformation[k] == "Average":
|
||||||
|
if v in ["Good", "Very Good"]:
|
||||||
|
phase_epc_transformation[k] = v
|
||||||
|
elif phase_epc_transformation[k] == "Poor":
|
||||||
|
if v in ["Average", "Good", "Very Good"]:
|
||||||
|
phase_epc_transformation[k] = v
|
||||||
|
else:
|
||||||
|
phase_epc_transformation[k] = v
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
if phase_epc_transformation[k] == v:
|
||||||
|
continue
|
||||||
|
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"Already have this key in the phase_epc_transformation - implement me")
|
"Already have this key in the phase_epc_transformation - implement me")
|
||||||
phase_epc_transformation[k] = v
|
phase_epc_transformation[k] = v
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from functools import lru_cache
|
||||||
import time
|
import time
|
||||||
from backend.app.db.functions.solar_functions import get_solar_data, store_batch_data
|
from backend.app.db.functions.solar_functions import get_solar_data, store_batch_data
|
||||||
from utils.logger import setup_logger
|
from utils.logger import setup_logger
|
||||||
|
from sklearn.preprocessing import MinMaxScaler
|
||||||
|
|
||||||
logger = setup_logger()
|
logger = setup_logger()
|
||||||
|
|
||||||
|
|
@ -198,6 +199,7 @@ class GoogleSolarApi:
|
||||||
scenarios_data["scenario_type"] = scenario_type
|
scenarios_data["scenario_type"] = scenario_type
|
||||||
scenarios_data = scenarios_data.to_dict(orient="records")
|
scenarios_data = scenarios_data.to_dict(orient="records")
|
||||||
|
|
||||||
|
# TODO: Rather than just doing a straight insert, we should overwrite what's already there if it exists
|
||||||
store_batch_data(
|
store_batch_data(
|
||||||
session=session,
|
session=session,
|
||||||
api_data=self.insights_data,
|
api_data=self.insights_data,
|
||||||
|
|
@ -244,7 +246,7 @@ class GoogleSolarApi:
|
||||||
wattage = segment["panelsCount"] * self.insights_data["solarPotential"]["panelCapacityWatts"]
|
wattage = segment["panelsCount"] * self.insights_data["solarPotential"]["panelCapacityWatts"]
|
||||||
generated_dc_energy = segment["yearlyEnergyDcKwh"]
|
generated_dc_energy = segment["yearlyEnergyDcKwh"]
|
||||||
ratio = generated_dc_energy / wattage
|
ratio = generated_dc_energy / wattage
|
||||||
cost = MCS_SOLAR_PV_COST_DATA["average_cost_per_kwh"] * (generated_dc_energy / 1000)
|
cost = MCS_SOLAR_PV_COST_DATA["average_cost_per_kwh"] * (wattage / 1000)
|
||||||
roi_summary.append(
|
roi_summary.append(
|
||||||
{
|
{
|
||||||
"segmentIndex": segment["segmentIndex"],
|
"segmentIndex": segment["segmentIndex"],
|
||||||
|
|
@ -309,17 +311,19 @@ class GoogleSolarApi:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Now that we know the lifetime cnsumption of ac kwh, we can estimate the roi
|
# Now that we know the lifetime cnsumption of ac kwh, we can estimate the roi
|
||||||
|
lifetime_energy_consumption = energy_consumption * self.installation_life_span
|
||||||
roi_results = []
|
roi_results = []
|
||||||
for _, panel_config in panel_performance.iterrows():
|
for _, panel_config in panel_performance.iterrows():
|
||||||
lifetime_ac_kwh = panel_config["lifetime_ac_kwh"]
|
lifetime_ac_kwh = panel_config["lifetime_ac_kwh"]
|
||||||
lifetime_energy_consumption = energy_consumption * self.installation_life_span
|
|
||||||
|
|
||||||
|
surplus = 0
|
||||||
if lifetime_ac_kwh < lifetime_energy_consumption:
|
if lifetime_ac_kwh < lifetime_energy_consumption:
|
||||||
# We estimate the amount of electricity generated, based on the price cap
|
# We estimate the amount of electricity generated, based on the price cap
|
||||||
generation_value = lifetime_ac_kwh * AnnualBillSavings.ELECTRICITY_PRICE_CAP
|
generation_value = lifetime_ac_kwh * AnnualBillSavings.ELECTRICITY_PRICE_CAP
|
||||||
roi = generation_value / panel_config["total_cost"]
|
roi = generation_value / panel_config["total_cost"]
|
||||||
generation_deficit = lifetime_energy_consumption - lifetime_ac_kwh
|
generation_deficit = lifetime_energy_consumption - lifetime_ac_kwh
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# We now have a surplus of energy, which we can sell back to the grid
|
# We now have a surplus of energy, which we can sell back to the grid
|
||||||
surplus = lifetime_ac_kwh - lifetime_energy_consumption
|
surplus = lifetime_ac_kwh - lifetime_energy_consumption
|
||||||
surplus_value = surplus * AnnualBillSavings.ELECTRICITY_EXPORT_PAYMENT
|
surplus_value = surplus * AnnualBillSavings.ELECTRICITY_EXPORT_PAYMENT
|
||||||
|
|
@ -341,7 +345,8 @@ class GoogleSolarApi:
|
||||||
"roi": roi,
|
"roi": roi,
|
||||||
"generation_value": generation_value,
|
"generation_value": generation_value,
|
||||||
"generation_deficit": generation_deficit,
|
"generation_deficit": generation_deficit,
|
||||||
"expected_payback_years": expected_payback_years
|
"expected_payback_years": expected_payback_years,
|
||||||
|
"surplus": surplus
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -351,12 +356,28 @@ class GoogleSolarApi:
|
||||||
roi_results, how="left", on="n_panels"
|
roi_results, how="left", on="n_panels"
|
||||||
)
|
)
|
||||||
|
|
||||||
# We prioritise maximal roi, then minimal geneartion deficit, then maximal generation value (if there is still
|
# We want max roi, minimal generation deficit, and max generation value - we create a ranking score
|
||||||
# a tie). Ideally, we want the best roi over the lifetime of the solar panels, but we also want to ensure that
|
# Assign equal weights to each metric
|
||||||
# we can meet the energy demands of the building.
|
weights = {'roi': 0.6, 'generation_value': 0.2, 'generation_deficit': 0.2}
|
||||||
panel_performance = panel_performance.sort_values(
|
metrics = panel_performance[['roi', 'generation_value', 'generation_deficit']]
|
||||||
["roi", "generation_deficit", "generation_value"], ascending=[False, True, False]
|
|
||||||
|
# Normalize the columns (0 to 1 scale)
|
||||||
|
scaler = MinMaxScaler()
|
||||||
|
normalized_metrics = scaler.fit_transform(metrics)
|
||||||
|
|
||||||
|
# Convert normalized metrics back to a dataframe
|
||||||
|
normalized_metrics_df = pd.DataFrame(
|
||||||
|
normalized_metrics, columns=['roi', 'generation_value', 'generation_deficit']
|
||||||
)
|
)
|
||||||
|
normalized_metrics_df['combined_score'] = (
|
||||||
|
normalized_metrics_df['roi'] * weights['roi'] +
|
||||||
|
normalized_metrics_df['generation_value'] * weights['generation_value'] +
|
||||||
|
(1 - normalized_metrics_df['generation_deficit']) * weights['generation_deficit']
|
||||||
|
)
|
||||||
|
|
||||||
|
panel_performance['combined_score'] = normalized_metrics_df['combined_score'].values
|
||||||
|
panel_performance['rank'] = panel_performance['combined_score'].rank(ascending=False)
|
||||||
|
panel_performance = panel_performance.sort_values(by='rank')
|
||||||
|
|
||||||
panel_performance["expected_payback_years"] = np.ceil(panel_performance["expected_payback_years"]).astype(int)
|
panel_performance["expected_payback_years"] = np.ceil(panel_performance["expected_payback_years"]).astype(int)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -431,6 +431,7 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Store the data in the database
|
# Store the data in the database
|
||||||
|
# TODO: Rather than just doing a straight insert, we should overwrite what's already there if it exists
|
||||||
solar_api_client.save_to_db(
|
solar_api_client.save_to_db(
|
||||||
session=session, uprns_to_location=building_uprns[building_id], scenario_type="building"
|
session=session, uprns_to_location=building_uprns[building_id], scenario_type="building"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import pandas as pd
|
||||||
from utils.s3 import save_csv_to_s3
|
from utils.s3 import save_csv_to_s3
|
||||||
|
|
||||||
PORTFOLIO_ID = 83
|
PORTFOLIO_ID = 83
|
||||||
|
SECOND_PORTFOLIO_ID = 84
|
||||||
USER_ID = 8
|
USER_ID = 8
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -67,6 +68,181 @@ def app():
|
||||||
"patches_file_path": "",
|
"patches_file_path": "",
|
||||||
"non_invasive_recommendations_file_path": "",
|
"non_invasive_recommendations_file_path": "",
|
||||||
"budget": None,
|
"budget": None,
|
||||||
|
"exclusions": ["floor_insulation"]
|
||||||
|
}
|
||||||
|
print(body)
|
||||||
|
|
||||||
|
# Get an example of flats with solar panels from epc data
|
||||||
|
|
||||||
|
# import inspect
|
||||||
|
# import pandas as pd
|
||||||
|
# from tqdm import tqdm
|
||||||
|
# from pathlib import Path
|
||||||
|
#
|
||||||
|
# src_file_path = inspect.getfile(lambda: None)
|
||||||
|
#
|
||||||
|
# EPC_DIRECTORY = Path(src_file_path).parent / "local_data" / "all-domestic-certificates"
|
||||||
|
#
|
||||||
|
# epc_directories = [entry for entry in EPC_DIRECTORY.iterdir() if entry.is_dir()]
|
||||||
|
#
|
||||||
|
# directory = epc_directories[1]
|
||||||
|
# data = pd.read_csv(directory / "certificates.csv", low_memory=False)
|
||||||
|
# # Get flats
|
||||||
|
# data = data[data["PROPERTY_TYPE"].str.lower().str.contains("flat")]
|
||||||
|
# data = data[~pd.isnull(data["UPRN"])]
|
||||||
|
# data["UPRN"] = data["UPRN"].astype(int).astype(str)
|
||||||
|
# data = data[pd.to_datetime(data["LODGEMENT_DATE"]) > "2020-01-01"]
|
||||||
|
# flats_with_solar = data[data['PHOTO_SUPPLY'] > 0]
|
||||||
|
#
|
||||||
|
# print(flats_with_solar["UPRN"])
|
||||||
|
#
|
||||||
|
# flats_with_solar[["ADDRESS", "UPRN"]]
|
||||||
|
#
|
||||||
|
# # Good example:
|
||||||
|
# # UPRN: 10013160824, Flat 39, The Meadow, 30 Busk Meadow S5 7JH (care home with 39 flats, have solar panels)
|
||||||
|
# #
|
||||||
|
# # Mostly, For a mid-floor flat, the property doesn't show as having solar panels through the photo_supply variable
|
||||||
|
# # But actually for UPRN: 10013245713, Apartment 4, Orchard House, Gill Lane PR4 5QN, this has a dwelling above
|
||||||
|
# # but the photo_supply variable is 20
|
||||||
|
#
|
||||||
|
# # Small flat consisting of 2 units
|
||||||
|
# # UPRN: 42172953, FLAT 2, 276 CLAUGHTON ROAD, BIRKENHEAD CH41 4DX
|
||||||
|
#
|
||||||
|
# # Flat containing 5 units
|
||||||
|
# # UPRN: 10013247127 Flat 1, Old Church House PR4 5GE
|
||||||
|
# # UPRN: 10013247130 Flat 4, Old Church House PR4 5GE
|
||||||
|
#
|
||||||
|
# # Flat containing multiple units:
|
||||||
|
# # UPRNS: 10013245710, 10013245716, 10013245711, 10013245717, 10013245714, 10013245715, 10013245712, 10013245713
|
||||||
|
#
|
||||||
|
# # Look for flats with air source heat pumps!
|
||||||
|
# flats_with_asps = data[data["MAINHEAT_DESCRIPTION"].str.lower().str.contains("air source heat pump")]
|
||||||
|
# print(flats_with_asps[["UPRN", "ADDRESS"]])
|
||||||
|
|
||||||
|
|
||||||
|
def app_epc_b():
|
||||||
|
# TODO: We can insert a variable, indicating the they own all of the units in the building
|
||||||
|
asset_list = [
|
||||||
|
{
|
||||||
|
"address": "Flat 1, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
"uprn": 200140644,
|
||||||
|
"building_id": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "Flat 2, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
"uprn": 200140645,
|
||||||
|
"building_id": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "Flat 3, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
"uprn": 200140646,
|
||||||
|
"building_id": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "Flat 4, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
"uprn": 200140647,
|
||||||
|
"building_id": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "Flat 5, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
"uprn": 200140648,
|
||||||
|
"building_id": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "Flat 6, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
"uprn": 200140649,
|
||||||
|
"building_id": 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
non_invasive_recommendations = [
|
||||||
|
{
|
||||||
|
"address": "Flat 1, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
'recommendations': [
|
||||||
|
'cavity_extract_and_refill',
|
||||||
|
# 'air_source_heat_pump'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "Flat 2, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
'recommendations': [
|
||||||
|
'cavity_extract_and_refill',
|
||||||
|
# 'air_source_heat_pump'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "Flat 3, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
'recommendations': [
|
||||||
|
'cavity_extract_and_refill',
|
||||||
|
# 'air_source_heat_pump'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "Flat 4, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
'recommendations': [
|
||||||
|
'cavity_extract_and_refill',
|
||||||
|
# 'air_source_heat_pump'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "Flat 5, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
'recommendations': [
|
||||||
|
'cavity_extract_and_refill',
|
||||||
|
'loft_insulation',
|
||||||
|
# 'air_source_heat_pump'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "Flat 6, Fenton Court",
|
||||||
|
"postcode": "N2 8DS",
|
||||||
|
'recommendations': [
|
||||||
|
'cavity_extract_and_refill',
|
||||||
|
'loft_insulation',
|
||||||
|
# 'air_source_heat_pump'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
asset_list = pd.DataFrame(asset_list)
|
||||||
|
|
||||||
|
# Store the asset list in s3
|
||||||
|
filename = f"{USER_ID}/{SECOND_PORTFOLIO_ID}/non_intrusives.csv"
|
||||||
|
save_csv_to_s3(
|
||||||
|
dataframe=asset_list,
|
||||||
|
bucket_name="retrofit-plan-inputs-dev",
|
||||||
|
file_name=filename
|
||||||
|
)
|
||||||
|
|
||||||
|
# Store non-invasive recommendations in S3
|
||||||
|
non_invasive_recommendations_filename = f"{USER_ID}/{SECOND_PORTFOLIO_ID}/non_invasive_recommendations.json"
|
||||||
|
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(SECOND_PORTFOLIO_ID),
|
||||||
|
"housing_type": "Private",
|
||||||
|
"goal": "Increase EPC",
|
||||||
|
"goal_value": "B",
|
||||||
|
"trigger_file_path": filename,
|
||||||
|
"already_installed_file_path": "",
|
||||||
|
"patches_file_path": "",
|
||||||
|
"non_invasive_recommendations_file_path": non_invasive_recommendations_filename,
|
||||||
|
"budget": None,
|
||||||
|
"exclusions": ["floor_insulation"]
|
||||||
}
|
}
|
||||||
print(body)
|
print(body)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,23 +18,23 @@ regional_labour_variations = [
|
||||||
{"Region": "Northern Ireland", "Adjustment_Factor": 0.76}
|
{"Region": "Northern Ireland", "Adjustment_Factor": 0.76}
|
||||||
]
|
]
|
||||||
|
|
||||||
# This data is based on the MCS database
|
# This data is based on the MCS database - taken the figures for June 2024
|
||||||
MCS_SOLAR_PV_COST_DATA = {
|
MCS_SOLAR_PV_COST_DATA = {
|
||||||
"last_updated": "2024-06-10",
|
"last_updated": "2024-07-10",
|
||||||
"average_cost_per_kwh": 1750,
|
"average_cost_per_kwh": 1825,
|
||||||
"average_cost_per_kwh-Outer London": 1776,
|
"average_cost_per_kwh-Outer London": 1950,
|
||||||
"average_cost_per_kwh-Inner London": 1776,
|
"average_cost_per_kwh-Inner London": 1950,
|
||||||
"average_cost_per_kwh-South East England": 1672,
|
"average_cost_per_kwh-South East England": 1966,
|
||||||
"average_cost_per_kwh-South West England": 1732,
|
"average_cost_per_kwh-South West England": 1864,
|
||||||
"average_cost_per_kwh-East of England": 1721,
|
"average_cost_per_kwh-East of England": 1719,
|
||||||
"average_cost_per_kwh-East Midlands": 1730,
|
"average_cost_per_kwh-East Midlands": 1730,
|
||||||
"average_cost_per_kwh-West Midlands": 1761,
|
"average_cost_per_kwh-West Midlands": 1789,
|
||||||
"average_cost_per_kwh-North East England": 1669,
|
"average_cost_per_kwh-North East England": 1872,
|
||||||
"average_cost_per_kwh-North West England": 1764,
|
"average_cost_per_kwh-North West England": 1860,
|
||||||
"average_cost_per_kwh-Yorkshire and the Humber": 1705,
|
"average_cost_per_kwh-Yorkshire and the Humber": 1789,
|
||||||
"average_cost_per_kwh-Wales": 1896,
|
"average_cost_per_kwh-Wales": 1676,
|
||||||
"average_cost_per_kwh-Scotland": 1767,
|
"average_cost_per_kwh-Scotland": 1781,
|
||||||
"average_cost_per_kwh-Northern Ireland": 1767,
|
"average_cost_per_kwh-Northern Ireland": 1347,
|
||||||
}
|
}
|
||||||
|
|
||||||
# This data is based on the MCS database, We use the larger figure between the 2023 and 2024 average,
|
# This data is based on the MCS database, We use the larger figure between the 2023 and 2024 average,
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,10 @@ class HeatingRecommender:
|
||||||
"boiler")
|
"boiler")
|
||||||
|
|
||||||
def is_ashp_valid(self):
|
def is_ashp_valid(self):
|
||||||
|
|
||||||
|
if "air_source_heat_pump" in self.property.non_invasive_recommendations:
|
||||||
|
return True
|
||||||
|
|
||||||
suitable_property_type = self.property.data["property-type"] in ["House", "Bungalow"]
|
suitable_property_type = self.property.data["property-type"] in ["House", "Bungalow"]
|
||||||
has_air_source_heat_pump = self.property.main_heating["has_air_source_heat_pump"]
|
has_air_source_heat_pump = self.property.main_heating["has_air_source_heat_pump"]
|
||||||
|
|
||||||
|
|
@ -232,6 +236,12 @@ class HeatingRecommender:
|
||||||
"mainheat_energy_eff_ending": "Good",
|
"mainheat_energy_eff_ending": "Good",
|
||||||
"hot_water_energy_eff_ending": "Good"
|
"hot_water_energy_eff_ending": "Good"
|
||||||
}
|
}
|
||||||
|
description_simulation = {
|
||||||
|
"mainheat-description": "Air source heat pump, radiators, electric",
|
||||||
|
"mainheat-energy-eff": "Good",
|
||||||
|
"hot-water-energy-eff": "Good",
|
||||||
|
"hotwater-description": "From main system",
|
||||||
|
}
|
||||||
# Installation of a boiler improves the hot water system so we need to reflect this in
|
# Installation of a boiler improves the hot water system so we need to reflect this in
|
||||||
# the outcome of the recommendation
|
# the outcome of the recommendation
|
||||||
heating_ending_config = MainHeatAttributes("Air source heat pump, radiators, electric").process()
|
heating_ending_config = MainHeatAttributes("Air source heat pump, radiators, electric").process()
|
||||||
|
|
@ -241,6 +251,10 @@ class HeatingRecommender:
|
||||||
fuel_ending_config = {}
|
fuel_ending_config = {}
|
||||||
if self.property.main_fuel["fuel_type"] != "electricity":
|
if self.property.main_fuel["fuel_type"] != "electricity":
|
||||||
fuel_ending_config = MainFuelAttributes("electricity (not community)").process()
|
fuel_ending_config = MainFuelAttributes("electricity (not community)").process()
|
||||||
|
description_simulation = {
|
||||||
|
**description_simulation,
|
||||||
|
"main-fuel": "electricity (not community)"
|
||||||
|
}
|
||||||
|
|
||||||
# Check the simulation differences
|
# Check the simulation differences
|
||||||
heating_simulation_config = check_simulation_difference(
|
heating_simulation_config = check_simulation_difference(
|
||||||
|
|
@ -270,6 +284,12 @@ class HeatingRecommender:
|
||||||
**controls_recommender.recommendation[0]["simulation_config"]
|
**controls_recommender.recommendation[0]["simulation_config"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
description_simulation = {
|
||||||
|
**description_simulation,
|
||||||
|
"mainheatcont-description": "time and temperature zone control",
|
||||||
|
"mainheatc-energy-eff": "Very Good"
|
||||||
|
}
|
||||||
|
|
||||||
ashp_recommendation = {
|
ashp_recommendation = {
|
||||||
"phase": phase,
|
"phase": phase,
|
||||||
"parts": [
|
"parts": [
|
||||||
|
|
@ -282,6 +302,7 @@ class HeatingRecommender:
|
||||||
"sap_points": None,
|
"sap_points": None,
|
||||||
"already_installed": already_installed,
|
"already_installed": already_installed,
|
||||||
"simulation_config": simulation_config,
|
"simulation_config": simulation_config,
|
||||||
|
"description_simulation": description_simulation,
|
||||||
**ashp_costs
|
**ashp_costs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -752,6 +752,23 @@ class Recommendations:
|
||||||
predicted_appliances_kwh_reduction
|
predicted_appliances_kwh_reduction
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# We store this value for later
|
||||||
|
phase_lighting_costs[rec["phase"]] = {
|
||||||
|
"adjusted": new_lighting_cost,
|
||||||
|
"unadjusted": scoring_lighting_cost
|
||||||
|
}
|
||||||
|
|
||||||
|
phase_kwh_figures[rec["phase"]] = {
|
||||||
|
"adjusted": {
|
||||||
|
"heating": new_heating_kwh_adjusted,
|
||||||
|
"hot_water": new_hot_water_kwh_adjusted
|
||||||
|
},
|
||||||
|
"unadjusted": {
|
||||||
|
"heating": new_heating_kwh,
|
||||||
|
"hot_water": new_hot_water_kwh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if rec["type"] == "low_energy_lighting":
|
if rec["type"] == "low_energy_lighting":
|
||||||
# For the moment, we cap the number of SAP points that can be achieved by ventilation at 2
|
# For the moment, we cap the number of SAP points that can be achieved by ventilation at 2
|
||||||
rec["sap_points"] = min(predicted_sap_points, LightingRecommendations.SAP_LIMIT)
|
rec["sap_points"] = min(predicted_sap_points, LightingRecommendations.SAP_LIMIT)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ class RoofRecommendations:
|
||||||
# It is recommended that lofts should have at least 270mm of insulation. If the property has more than 200mm of
|
# It is recommended that lofts should have at least 270mm of insulation. If the property has more than 200mm of
|
||||||
# loft insulation in place already, we do not recommend anything for the moment
|
# loft insulation in place already, we do not recommend anything for the moment
|
||||||
MINIMUM_LOFT_ISULATION_MM = 200
|
MINIMUM_LOFT_ISULATION_MM = 200
|
||||||
|
MINIMUM_RECOMMENDED_LOFT_INSULATION = 280
|
||||||
# Flat roof should have at least 100mm of insulation
|
# Flat roof should have at least 100mm of insulation
|
||||||
MINIMUM_FLAT_ROOF_ISULATION_MM = 100
|
MINIMUM_FLAT_ROOF_ISULATION_MM = 100
|
||||||
|
|
||||||
|
|
@ -79,6 +80,11 @@ class RoofRecommendations:
|
||||||
"""
|
"""
|
||||||
Check if the loft is already insulated
|
Check if the loft is already insulated
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# If we have a non-invasive recommendation for the loft insulation, we can assume that the loft is not insulated
|
||||||
|
if "loft_insulation" in self.property.non_invasive_recommendations:
|
||||||
|
return False
|
||||||
|
|
||||||
return (self.insulation_thickness > self.MINIMUM_LOFT_ISULATION_MM) and self.property.roof["is_pitched"]
|
return (self.insulation_thickness > self.MINIMUM_LOFT_ISULATION_MM) and self.property.roof["is_pitched"]
|
||||||
|
|
||||||
def recommend(self, phase):
|
def recommend(self, phase):
|
||||||
|
|
@ -115,12 +121,17 @@ class RoofRecommendations:
|
||||||
u_value = get_roof_u_value(**{**self.property.roof, "age_band": self.property.age_band})
|
u_value = get_roof_u_value(**{**self.property.roof, "age_band": self.property.age_band})
|
||||||
|
|
||||||
self.estimated_u_value = u_value
|
self.estimated_u_value = u_value
|
||||||
if u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
if (u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE) and (
|
||||||
|
"loft_insulation" not in self.property.non_invasive_recommendations
|
||||||
|
):
|
||||||
# The Roof is already compliant
|
# The Roof is already compliant
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.property.roof["is_pitched"] or self.property.roof["is_flat"]:
|
if self.property.roof["is_pitched"] or self.property.roof["is_flat"]:
|
||||||
self.recommend_roof_insulation(u_value, self.insulation_thickness, self.property.roof, phase)
|
insulation_thickness = (
|
||||||
|
0 if "loft_insulation" not in self.property.non_invasive_recommendations else self.insulation_thickness
|
||||||
|
)
|
||||||
|
self.recommend_roof_insulation(u_value, insulation_thickness, self.property.roof, phase)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.property.roof["is_roof_room"]:
|
if self.property.roof["is_roof_room"]:
|
||||||
|
|
@ -200,7 +211,9 @@ class RoofRecommendations:
|
||||||
# We make sure we hit a depth of 270mm. We should factor in any existing insulation if the
|
# We make sure we hit a depth of 270mm. We should factor in any existing insulation if the
|
||||||
# loft is already partially insulated.
|
# loft is already partially insulated.
|
||||||
# Note: This requirement is only for loft insulation
|
# Note: This requirement is only for loft insulation
|
||||||
if ((material["depth"] + insulation_thickness) < self.MINIMUM_LOFT_ISULATION_MM) and roof["is_pitched"]:
|
if (
|
||||||
|
(material["depth"] + insulation_thickness) < self.MINIMUM_RECOMMENDED_LOFT_INSULATION
|
||||||
|
) and roof["is_pitched"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
part_u_value = r_value_per_mm_to_u_value(material["depth"], material["r_value_per_mm"])
|
part_u_value = r_value_per_mm_to_u_value(material["depth"], material["r_value_per_mm"])
|
||||||
|
|
@ -245,6 +258,35 @@ class RoofRecommendations:
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid material type")
|
raise ValueError("Invalid material type")
|
||||||
|
|
||||||
|
# This is based on the values we have in the training data
|
||||||
|
valid_numeric_values = [
|
||||||
|
12,
|
||||||
|
25,
|
||||||
|
50,
|
||||||
|
75,
|
||||||
|
100,
|
||||||
|
150,
|
||||||
|
200,
|
||||||
|
250,
|
||||||
|
270,
|
||||||
|
300,
|
||||||
|
350,
|
||||||
|
400,
|
||||||
|
]
|
||||||
|
|
||||||
|
proposed_depth = new_thickness
|
||||||
|
if new_thickness not in valid_numeric_values:
|
||||||
|
# Take the nearest value for scoring
|
||||||
|
proposed_depth = min(
|
||||||
|
valid_numeric_values, key=lambda x: abs(x - proposed_depth)
|
||||||
|
)
|
||||||
|
|
||||||
|
if proposed_depth >= 270:
|
||||||
|
new_efficiency = "Very Good"
|
||||||
|
else:
|
||||||
|
if self.property.data["walls-energy-eff"] not in ["Good", "Very Good"]:
|
||||||
|
new_efficiency = "Good"
|
||||||
|
|
||||||
recommendations.append(
|
recommendations.append(
|
||||||
{
|
{
|
||||||
"phase": phase,
|
"phase": phase,
|
||||||
|
|
@ -263,6 +305,10 @@ class RoofRecommendations:
|
||||||
"sap_points": None,
|
"sap_points": None,
|
||||||
"already_installed": already_installed,
|
"already_installed": already_installed,
|
||||||
"new_thickness": new_thickness,
|
"new_thickness": new_thickness,
|
||||||
|
"description_simulation": {
|
||||||
|
"roof-description": f"Pitched, {int(proposed_depth)}mm loft insulation",
|
||||||
|
"roof-energy-eff": new_efficiency
|
||||||
|
},
|
||||||
**cost_result
|
**cost_result
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class VentilationRecommendations(Definitions):
|
||||||
"already_installed": already_installed,
|
"already_installed": already_installed,
|
||||||
"sap_points": 0,
|
"sap_points": 0,
|
||||||
"heat_demand": 0,
|
"heat_demand": 0,
|
||||||
"adjusted_heat_demand": 0,
|
"kwh_savings": 0,
|
||||||
"co2_equivalent_savings": 0,
|
"co2_equivalent_savings": 0,
|
||||||
"energy_cost_savings": 0,
|
"energy_cost_savings": 0,
|
||||||
"total": estimated_cost,
|
"total": estimated_cost,
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ class WallRecommendations(Definitions):
|
||||||
|
|
||||||
self.estimated_u_value = u_value
|
self.estimated_u_value = u_value
|
||||||
|
|
||||||
if is_cavity_wall:
|
if is_cavity_wall or "cavity_extract_and_refill" in self.property.non_invasive_recommendations:
|
||||||
if u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
if u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
||||||
# Test filling cavity
|
# Test filling cavity
|
||||||
self.find_cavity_insulation(u_value, insulation_thickness, phase)
|
self.find_cavity_insulation(u_value, insulation_thickness, phase)
|
||||||
|
|
@ -357,7 +357,7 @@ class WallRecommendations(Definitions):
|
||||||
simulation_config = {
|
simulation_config = {
|
||||||
**simulation_config,
|
**simulation_config,
|
||||||
**walls_simulation_config,
|
**walls_simulation_config,
|
||||||
"walls_thermal_transmittance_ending": new_u_value
|
"walls_thermal_transmittance_ending": new_u_value,
|
||||||
}
|
}
|
||||||
|
|
||||||
recommendations.append(
|
recommendations.append(
|
||||||
|
|
@ -378,6 +378,10 @@ class WallRecommendations(Definitions):
|
||||||
"sap_points": None,
|
"sap_points": None,
|
||||||
"already_installed": already_installed,
|
"already_installed": already_installed,
|
||||||
"simulation_config": simulation_config,
|
"simulation_config": simulation_config,
|
||||||
|
"description_simulation": {
|
||||||
|
"walls-description": "Cavity wall, filled cavity",
|
||||||
|
"walls-energy-eff": "Good"
|
||||||
|
},
|
||||||
**cost_result
|
**cost_result
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue