mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
set up heating recommender class
This commit is contained in:
parent
fe25228362
commit
08957d41a8
10 changed files with 234 additions and 45 deletions
|
|
@ -345,7 +345,15 @@ class Property:
|
|||
|
||||
if recommendation["type"] == "heating_control":
|
||||
# We update the data, as defined in the recommendaton
|
||||
output.update(recommendation["simulation_config"])
|
||||
|
||||
simulation_config = recommendation["simulation_config"]
|
||||
# If any entries in simulation_config are None, we will set them to "Unknown" which is the cleaning
|
||||
# value
|
||||
for key, value in simulation_config.items():
|
||||
if value is None:
|
||||
simulation_config[key] = "Unknown"
|
||||
|
||||
output.update(simulation_config)
|
||||
|
||||
if recommendation["type"] == "solar_pv":
|
||||
output["photo_supply_ending"] = recommendation["photo_supply"]
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
'old_data': epc_searcher.older_epcs.copy(),
|
||||
}
|
||||
|
||||
# We can patch the data if we are provided data from the customer
|
||||
|
||||
prepared_epc = EPCRecord(
|
||||
epc_records=epc_records,
|
||||
run_mode="newdata",
|
||||
|
|
|
|||
|
|
@ -78,11 +78,12 @@ def app():
|
|||
"uprn": newest_epc["uprn"],
|
||||
"address": newest_epc["address1"],
|
||||
"postcode": newest_epc["postcode"],
|
||||
"walls-description": newest_epc["walls-description"],
|
||||
"roof-description": newest_epc["roof-description"],
|
||||
"floor-description": newest_epc["floor-description"],
|
||||
"total-floor-area": newest_epc["total-floor-area"],
|
||||
"full-address": newest_epc["address"]
|
||||
# "walls-description": newest_epc["walls-description"],
|
||||
# "roof-description": newest_epc["roof-description"],
|
||||
# "floor-description": newest_epc["floor-description"],
|
||||
# "total-floor-area": newest_epc["total-floor-area"],
|
||||
"full-address": newest_epc["address"],
|
||||
|
||||
}
|
||||
|
||||
processed_asset_list.append(to_append)
|
||||
|
|
@ -91,6 +92,23 @@ def app():
|
|||
processed_asset_list_df = pd.DataFrame(processed_asset_list)
|
||||
epc_data_df = pd.DataFrame(epc_data)
|
||||
|
||||
example = epc_data_df.iloc[11, :]
|
||||
rest = epc_data_df[epc_data_df["address1"] != example["address1"]]
|
||||
z = rest[
|
||||
(rest["total-floor-area"] == example["total-floor-area"]) &
|
||||
(rest["current-energy-rating"] == "C")
|
||||
]
|
||||
# Walls better in the example
|
||||
z["walls-description"]
|
||||
example["walls-description"]
|
||||
|
||||
# Example has a property above
|
||||
z["roof-description"]
|
||||
example["roof-description"]
|
||||
|
||||
compare = pd.concat([pd.DataFrame(example).T, z])
|
||||
compare["mainheat-description"]
|
||||
|
||||
# We store this data
|
||||
# Store the data in s3
|
||||
filename = f"{USER_ID}/{PORTFOLIO_ID}/test_inputs.csv"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ class MainHeatAttributes(Definitions):
|
|||
"solar assisted heat pump",
|
||||
"exhaust source heat pump",
|
||||
"community heat pump",
|
||||
"portable electric heating"
|
||||
]
|
||||
FUEL_TYPES = ["electric", "mains gas", "wood logs", "coal", "oil", "wood pellets", "anthracite",
|
||||
"dual fuel mineral and wood", "smokeless fuel", "lpg", "b30k"]
|
||||
|
|
@ -62,7 +61,8 @@ class MainHeatAttributes(Definitions):
|
|||
REMAP = {
|
||||
"electric ceiling": "electric ceiling heating",
|
||||
"electric heat pumps": "electric heat pump",
|
||||
"solar-assisted heat pump": "solar assisted heat pump"
|
||||
"solar-assisted heat pump": "solar assisted heat pump",
|
||||
"portable electric heating": "portable electric heaters",
|
||||
}
|
||||
|
||||
edge_case_result = {}
|
||||
|
|
@ -139,6 +139,8 @@ class MainHeatAttributes(Definitions):
|
|||
result.update({f'has_{ft.replace(" ", "_")}': False for ft in self.FUEL_TYPES})
|
||||
result.update({f'has_{ot.replace(" ", "_")}': False for ot in self.OTHERS})
|
||||
result['has_underfloor_heating'] = False
|
||||
# We re-map entries that are the same
|
||||
# We just drop those keys
|
||||
|
||||
if self.nodata:
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -21,8 +21,11 @@ BUCKET = os.environ.get("BUCKET", "retrofit-data-dev")
|
|||
def app():
|
||||
directories = [entry for entry in DATA_DIRECTORY.iterdir() if entry.is_dir()]
|
||||
|
||||
sample = []
|
||||
for directory in tqdm(directories):
|
||||
|
||||
data = pd.read_csv(directory / "certificates.csv", low_memory=False)
|
||||
|
||||
data = data[data["LODGEMENT_DATE"] >= EARLIEST_EPC_DATE]
|
||||
data = data[~pd.isnull(data["UPRN"])]
|
||||
data["TOTAL_FLOOR_AREA"] = data["TOTAL_FLOOR_AREA"].astype(float)
|
||||
|
|
|
|||
|
|
@ -908,3 +908,56 @@ class Costs:
|
|||
"labour_hours": labour_hours,
|
||||
"labour_days": 1,
|
||||
}
|
||||
|
||||
def electric_room_heaters(self, number_heated_rooms):
|
||||
"""
|
||||
We base the estimates for the cost of electric room heaters on the cost per room as estimated by the
|
||||
following article:
|
||||
https://www.bestelectricradiators.co.uk/blog/cost-to-install-a-new-heating-system-uk/
|
||||
|
||||
:param number_heated_rooms: int, number of rooms to be heated
|
||||
:return:
|
||||
"""
|
||||
|
||||
total_cost = 500 * number_heated_rooms
|
||||
subtotal_before_vat = total_cost / (1 + self.VAT_RATE)
|
||||
vat = total_cost - subtotal_before_vat
|
||||
|
||||
# TODO: Rough estimate to be reviewed
|
||||
labour_hours = 1 * number_heated_rooms
|
||||
labour_days = np.ceil(labour_hours / 8)
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
||||
def electric_storage_heaters(self, number_heated_rooms):
|
||||
|
||||
"""
|
||||
We base the estimates for the cost of electric storage heaters on the cost per room as estimated by the
|
||||
energy saving trust
|
||||
https://energysavingtrust.org.uk/advice/electric-heating/
|
||||
|
||||
The cost is based on the number of heated rooms
|
||||
:param number_heated_rooms: int, number of rooms to be heated
|
||||
"""
|
||||
|
||||
total_cost = 1000 * number_heated_rooms
|
||||
subtotal_before_vat = total_cost / (1 + self.VAT_RATE)
|
||||
vat = total_cost - subtotal_before_vat
|
||||
|
||||
# TODO: Rough estimate to be reviewed
|
||||
labour_hours = 3 * number_heated_rooms
|
||||
labour_days = np.ceil(labour_hours / 8)
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"subtotal": subtotal_before_vat,
|
||||
"vat": vat,
|
||||
"labour_hours": labour_hours,
|
||||
"labour_days": labour_days,
|
||||
}
|
||||
|
|
|
|||
78
recommendations/HeatingControlRecommender.py
Normal file
78
recommendations/HeatingControlRecommender.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
from recommendations.Costs import Costs
|
||||
from recommendations.recommendation_utils import check_simulation_difference
|
||||
from backend.Property import Property
|
||||
from etl.epc_clean.epc_attributes.MainheatControlAttributes import MainheatControlAttributes
|
||||
|
||||
|
||||
class HeatingControlRecommender:
|
||||
|
||||
def __init__(self, property_instance: Property):
|
||||
self.property = property_instance
|
||||
self.costs = Costs(self.property)
|
||||
|
||||
self.recommendations = []
|
||||
|
||||
def recommend(self, phase=0):
|
||||
# This first iteration of the recommender will provide very basic recommendation
|
||||
# We recommend heating controls based on the main heating system
|
||||
if self.property.main_heating["clean_description"] == "Room heaters, electric":
|
||||
self.recommend_room_heaters_electric_controls(phase=phase)
|
||||
return
|
||||
|
||||
def recommend_room_heaters_electric_controls(self, phase):
|
||||
"""
|
||||
If the home has Room heaters, electric, we start by identifying potential heating controls that could
|
||||
be upgraded, that would provide a practical impact. This will be the least invasive improvement.
|
||||
|
||||
We can then consider the heating system itself
|
||||
:return:
|
||||
"""
|
||||
if (self.property.data["mainheatc-energy-eff"] in ["Poor", "Very Poor", "Average"]) or (
|
||||
self.property.main_heating_controls["clean_description"] in ["Programmer and room thermostat"]
|
||||
):
|
||||
# We recommend Programmer and appliance thermostats as the heating control. This has an average energy
|
||||
# efficiency rating, and is likely to be more efficient than the current heating controls. if the
|
||||
# rating is poor or very poor, the home may have a Programmer and room thermostat, which is less efficient
|
||||
# than a Programmer and appliance thermostats, because it allows for much more granular control at not
|
||||
# just a room level but individual heater/appliance level
|
||||
|
||||
# Note: A room thermostat is commonly placed in a hallway, and it measures the temperature of the air
|
||||
# surrounding it. It then sends a signal to the heating system to turn on or off, depending on the
|
||||
# temperature. An appliance thermostat, on the other hand, is placed on the heater/appliance itself, and
|
||||
# measures the temperature of the heater/appliance. This allows for much more granular control, and
|
||||
# prevents overheating.
|
||||
|
||||
# In order to cost, we check if the property already has a programmer, and therefor we will just need to
|
||||
# add the cost of the appliance thermostats
|
||||
|
||||
has_programmer = self.property.main_heating_controls["switch_system"] == "programmer"
|
||||
|
||||
ending_config = MainheatControlAttributes("Programmer and appliance thermostats").process()
|
||||
# We look at what has changed in the ending config, and compare it to the current config
|
||||
|
||||
# We use this to determine how we should be updating the config
|
||||
simulation_config = check_simulation_difference(
|
||||
new_config=ending_config, old_config=self.property.main_heating_controls
|
||||
)
|
||||
# This upgrade will only take the heating system to average energy efficiency
|
||||
simulation_config["mainheatc_energy_eff_ending"] = "Good"
|
||||
|
||||
self.recommendations.append(
|
||||
{
|
||||
"phase": phase,
|
||||
"parts": [
|
||||
# TODO
|
||||
],
|
||||
"type": "heating_control",
|
||||
"description": "Upgrade heating controls to Programmer and Appliance or Smart "
|
||||
"Thermostats for more precise heating control, and prevention of overheating",
|
||||
"starting_u_value": None,
|
||||
"new_u_value": None,
|
||||
"sap_points": None,
|
||||
**self.costs.programmer_and_appliance_thermostat(has_programmer=has_programmer),
|
||||
"simulation_config": simulation_config
|
||||
}
|
||||
)
|
||||
|
||||
# We don't implement any other recommendations right now
|
||||
return
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
from recommendations.Costs import Costs
|
||||
from recommendations.recommendation_utils import check_simulation_difference
|
||||
from backend.Property import Property
|
||||
from etl.epc_clean.epc_attributes.MainheatControlAttributes import MainheatControlAttributes
|
||||
from etl.epc_clean.epc_attributes.MainheatAttributes import MainHeatAttributes
|
||||
|
||||
|
||||
class HeatingRecommender:
|
||||
|
|
@ -13,6 +14,7 @@ class HeatingRecommender:
|
|||
|
||||
def recommend(self, phase=0):
|
||||
# This first iteration of the recommender will provide very basic recommendation
|
||||
# We recommend heating controls based on the main heating system
|
||||
if self.property.main_heating["clean_description"] == "Room heaters, electric":
|
||||
self.recommend_room_heaters_electric(phase=phase)
|
||||
return
|
||||
|
|
@ -39,45 +41,50 @@ class HeatingRecommender:
|
|||
:return:
|
||||
"""
|
||||
if self.property.data["mainheat-energy-eff"] in ["Poor", "Very Poor"]:
|
||||
# We recommend Programmer and appliance thermostats as the heating control. This has an average energy
|
||||
# efficiency rating, and is likely to be more efficient than the current heating controls. if the
|
||||
# rating is poor or very poor, the home may have a Programmer and room thermostat, which is less efficient
|
||||
# than a Programmer and appliance thermostats, because it allows for much more granular control at not
|
||||
# just a room level but individual heater/appliance level
|
||||
# Re recommend two possible upgrades:
|
||||
# 1) Installation of more efficient electic room heaters
|
||||
# 2) Installation of electric storage heaters
|
||||
|
||||
# Note: A room thermostat is commonly placed in a hallway, and it measures the temperature of the air
|
||||
# surrounding it. It then sends a signal to the heating system to turn on or off, depending on the
|
||||
# temperature. An appliance thermostat, on the other hand, is placed on the heater/appliance itself, and
|
||||
# measures the temperature of the heater/appliance. This allows for much more granular control, and
|
||||
# prevents overheating.
|
||||
room_heater_recommendation = {
|
||||
"phase": phase,
|
||||
"parts": [
|
||||
# TODO
|
||||
],
|
||||
"type": "heating",
|
||||
"description": "Upgrade electric room heaters to more electric radiators",
|
||||
"starting_u_value": None,
|
||||
"new_u_value": None,
|
||||
"sap_points": None,
|
||||
**self.costs.electric_room_heaters(number_heated_rooms=self.property.data["number-heated-rooms"]),
|
||||
"simulation_config": {"mainheat_energy_eff_ending": "Average"}
|
||||
}
|
||||
|
||||
# In order to cost, we check if the property already has a programmer, and therefor we will just need to
|
||||
# add the cost of the appliance thermostats
|
||||
has_programmer = self.property.main_heating_controls["switch_system"] == "programmer"
|
||||
|
||||
ending_config = MainheatControlAttributes("Programmer and appliance thermostats").process()
|
||||
# We look at what has changed in the ending config, and compare it to the current config
|
||||
|
||||
# We use this to determine how we should be updating the config
|
||||
simulation_config = self.check_simulation_difference(
|
||||
new_config=ending_config, old_config=self.property.main_heating_controls
|
||||
ending_config = MainHeatAttributes("Electric storage heaters, radiators").process()
|
||||
simulation_config = check_simulation_difference(
|
||||
new_config=ending_config, old_config=self.property.main_heating
|
||||
)
|
||||
# This upgrade will only take the heating system to average energy efficiency
|
||||
simulation_config["mainheatc_energy_eff_ending"] = "Good"
|
||||
|
||||
self.recommendations.append(
|
||||
{
|
||||
"phase": phase,
|
||||
"parts": [
|
||||
# TODO
|
||||
],
|
||||
"type": "heating_control",
|
||||
"description": "Upgrade heating controls to Programmer and Appliance or Smart"
|
||||
"Thermostats for more precise heating control, and prevention of overheating",
|
||||
"starting_u_value": None,
|
||||
"new_u_value": None,
|
||||
"sap_points": None,
|
||||
**self.costs.programmer_and_appliance_thermostat(has_programmer=has_programmer),
|
||||
"simulation_config": simulation_config
|
||||
electric_storage_heaters_recommendation = {
|
||||
"phase": phase,
|
||||
"parts": [
|
||||
# TODO
|
||||
],
|
||||
"type": "heating",
|
||||
"description": "Install electric storage heaters",
|
||||
"starting_u_value": None,
|
||||
"new_u_value": None,
|
||||
"sap_points": None,
|
||||
**self.costs.electric_storage_heaters(number_heated_rooms=self.property.data["number-heated-rooms"]),
|
||||
"simulation_config": {
|
||||
"TODO" # TODO
|
||||
"mainheat_energy_eff_ending": "Average"
|
||||
}
|
||||
}
|
||||
|
||||
self.recommendations.extend(
|
||||
[room_heater_recommendation, electric_storage_heaters_recommendation]
|
||||
)
|
||||
|
||||
# We don't implement any other recommendations right now
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import numpy as np
|
||||
|
||||
from backend.Property import Property
|
||||
from typing import List
|
||||
from itertools import groupby
|
||||
|
|
@ -12,6 +10,7 @@ from recommendations.LightingRecommendations import LightingRecommendations
|
|||
from recommendations.SolarPvRecommendations import SolarPvRecommendations
|
||||
from recommendations.WindowsRecommendations import WindowsRecommendations
|
||||
from recommendations.HeatingRecommender import HeatingRecommender
|
||||
from recommendations.HeatingControlRecommender import HeatingControlRecommender
|
||||
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
|
||||
|
||||
|
||||
|
|
@ -44,6 +43,7 @@ class Recommendations:
|
|||
self.windows_recommender = WindowsRecommendations(property_instance=property_instance, materials=materials)
|
||||
self.solar_recommender = SolarPvRecommendations(property_instance=property_instance)
|
||||
self.heating_recommender = HeatingRecommender(property_instance=property_instance)
|
||||
self.heating_control_recommender = HeatingControlRecommender(property_instance=property_instance)
|
||||
|
||||
def recommend(self):
|
||||
|
||||
|
|
@ -99,6 +99,11 @@ class Recommendations:
|
|||
property_recommendations.append(self.heating_recommender.recommendations)
|
||||
phase += 1
|
||||
|
||||
self.heating_control_recommender.recommend(phase=phase)
|
||||
if self.heating_control_recommender.recommendations:
|
||||
property_recommendations.append(self.heating_control_recommender.recommendations)
|
||||
phase += 1
|
||||
|
||||
self.lighting_recommender.recommend(phase=phase)
|
||||
if self.lighting_recommender.recommendation:
|
||||
property_recommendations.append(self.lighting_recommender.recommendation)
|
||||
|
|
|
|||
|
|
@ -754,3 +754,16 @@ def calculate_cavity_age(newest_epc, older_epcs, cleaned):
|
|||
|
||||
cavity_age = (datetime.now() - pd.to_datetime(df["inspection-date"].max())).days
|
||||
return cavity_age
|
||||
|
||||
|
||||
def check_simulation_difference(old_config, new_config):
|
||||
"""
|
||||
Given two dictionaries, that describe the heating control configurations, this method will compare the two
|
||||
and pick out the differences. These differences will be things that have been added and things that have been
|
||||
removed. This will be used to determine how we should be updating the configuration in the simulation
|
||||
:return:
|
||||
"""
|
||||
|
||||
differences = {key + "_ending": new_config[key] for key in new_config if old_config[key] != new_config[key]}
|
||||
|
||||
return differences
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue