From 027aa79a0b1a05562b9584a73cff7fae068f4f30 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 1 Aug 2024 12:44:07 +0100 Subject: [PATCH] adding bypass heating control recommendation --- backend/app/plan/router.py | 38 ++++++++--------- etl/bill_savings/data_collection.py | 5 ++- recommendations/Costs.py | 30 +++++++++++++ recommendations/HeatingControlRecommender.py | 44 ++++++++++++++++++++ 4 files changed, 96 insertions(+), 21 deletions(-) diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index a0d4e585..b6175153 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -408,25 +408,6 @@ async def trigger_plan(body: PlanTriggerRequest): if not input_properties: return Response(status_code=204) - # If we have any work to do, we create a new scenario - engine_scenario = create_scenario( - session=session, - scenario={ - "name": body.scenario_name, - "created_at": created_at, - "budget": body.budget, - "portfolio_id": body.portfolio_id, - "housing_type": body.housing_type, - "goal": body.goal, - "trigger_file_path": body.trigger_file_path, - "already_installed_file_path": body.already_installed_file_path, - "patches_file_path": body.patches_file_path, - "non_invasive_recommendations_file_path": body.non_invasive_recommendations_file_path, - "exclusions": body.exclusions, - "multi_plan": body.multi_plan - } - ) - # The materials data could be cached or local so we don't need to make # consistent requests to the backend for # the same data @@ -733,6 +714,25 @@ async def trigger_plan(body: PlanTriggerRequest): # 3) the recommendations logger.info("Uploading recommendations to the database") + # If we have any work to do, we create a new scenario + engine_scenario = create_scenario( + session=session, + scenario={ + "name": body.scenario_name, + "created_at": created_at, + "budget": body.budget, + "portfolio_id": body.portfolio_id, + "housing_type": body.housing_type, + "goal": body.goal, + "trigger_file_path": body.trigger_file_path, + "already_installed_file_path": body.already_installed_file_path, + "patches_file_path": body.patches_file_path, + "non_invasive_recommendations_file_path": body.non_invasive_recommendations_file_path, + "exclusions": body.exclusions, + "multi_plan": body.multi_plan + } + ) + property_valuation_increases = [] session.commit() new_epc_bands = {} diff --git a/etl/bill_savings/data_collection.py b/etl/bill_savings/data_collection.py index e6f6de6f..6cc2d581 100644 --- a/etl/bill_savings/data_collection.py +++ b/etl/bill_savings/data_collection.py @@ -133,12 +133,13 @@ def app(): energy_consumption_data = [] for i, directory in tqdm(enumerate(epc_directories), total=len(epc_directories)): # Skip the first 50 - if i < 57: - continue + # if i < 57: + # continue data = pd.read_csv(directory / "certificates.csv", low_memory=False) # Rename the columns to the same format as the api returns data.columns = [c.replace("_", "-").lower() for c in data.columns] + # Take just date before the date threshold data = data[data["lodgement-date"] >= EARLIEST_EPC_DATE] diff --git a/recommendations/Costs.py b/recommendations/Costs.py index ce459528..738e9b07 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -64,6 +64,8 @@ SMART_APPLIANCE_THERMOSTAT_COST = 400 PROGRAMMER_COST = 120 ROOM_THERMOSTAT_COST = 150 TRVS_COST = 35 +BYPASS_COST = 350 # Based on desktop research for a complex installation +# https://www.checkatrade.com/blog/cost-guides/cost-install-water-shut-off-valve/ # Cost for TTZC # Smart thermostat based on checkatrade https://www.checkatrade.com/blog/cost-guides/cost-smart-thermostat/ @@ -1254,6 +1256,34 @@ class Costs: "labour_days": labour_days, } + def programmer_trvs_bypass(self, number_heated_rooms, has_programmer, has_trvs, has_bypass): + + total_cost = 0 + labour_hours = 0 + + if not has_programmer: + total_cost += PROGRAMMER_COST + labour_hours += 1 + + if not has_trvs: + total_cost += TRVS_COST * number_heated_rooms + labour_hours += 0.25 * number_heated_rooms + + if not has_bypass: + total_cost += BYPASS_COST + labour_hours += 0.5 + + subtotal_before_vat = total_cost / (1 + self.VAT_RATE) + vat = total_cost - subtotal_before_vat + + return { + "total": total_cost, + "subtotal": subtotal_before_vat, + "vat": vat, + "labour_hours": labour_hours, + "labour_days": 1, + } + def heater_removal(self, n_rooms): """ Estimates the costs of removal of heaters, including the redecoration costs of the space behind the heater diff --git a/recommendations/HeatingControlRecommender.py b/recommendations/HeatingControlRecommender.py index fe3e577d..80615b30 100644 --- a/recommendations/HeatingControlRecommender.py +++ b/recommendations/HeatingControlRecommender.py @@ -40,7 +40,10 @@ class HeatingControlRecommender: return if heating_description in ["Air source heat pump, radiators, electric"]: + # For an ASHP, we can recommend time and temperature zone controls, as well as programmer, trvs and a bypass + # which are common configurations for ASHPs self.recommend_time_temperature_zone_controls() + self.recommend_programmer_trvs_bypass() def recommend_room_heaters_electric_controls(self): """ @@ -279,3 +282,44 @@ class HeatingControlRecommender: "description_simulation": description_simulation } ) + + def recommend_programmer_trvs_bypass(self): + + # We don't perform any checks here - this is likely to be used in conjunction with an ASHP recommendation + new_controls_description = "Programmer, TRVs and bypass" + ending_config = MainheatControlAttributes(new_controls_description).process() + simulation_config = check_simulation_difference( + new_config=ending_config, old_config=self.property.main_heating_controls + ) + simulation_config["mainheatc_energy_eff_ending"] = "Average" + + description_simulation = { + "mainheatcont-description": new_controls_description, + "mainheatc-energy-eff": simulation_config["mainheatc_energy_eff_ending"] + } + + cost_result = self.costs.programmer_trvs_bypass( + number_heated_rooms=int(self.property.data["number-heated-rooms"]) + ) + + description = "Install a Bypass valve, TRVs and a Programmer" + + already_installed = "heating_control" in self.property.already_installed + if already_installed: + cost_result = override_costs(cost_result) + description = "Heating controls have already been upgraded, no further action needed." + + self.recommendation.append( + { + "type": "heating_control", + "parts": [], + "description": description, + **cost_result, + "starting_u_value": None, + "new_u_value": None, + "sap_points": None, + "already_installed": already_installed, + "simulation_config": simulation_config, + "description_simulation": description_simulation + } + )