set up heating recommender class

This commit is contained in:
Khalim Conn-Kowlessar 2024-02-19 18:37:50 +00:00
parent fe25228362
commit 08957d41a8
10 changed files with 234 additions and 45 deletions

View file

@ -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"]

View file

@ -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",

View file

@ -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"

View file

@ -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

View file

@ -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)

View file

@ -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,
}

View 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

View file

@ -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

View file

@ -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)

View file

@ -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