diff --git a/backend/Property.py b/backend/Property.py index b8eb9936..4ae65d7d 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -351,6 +351,7 @@ class Property: if r["phase"] <= phase ] epc_transformations = [x["description_simulation"] for x in represenative_recs_to_this_phase] + # It is possible that we could have two simulations applied to the same descriptions # We extract these out phase_epc_transformation = {} diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index 5d75bada..00e73b56 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -448,12 +448,13 @@ async def trigger_plan(body: PlanTriggerRequest): p.set_solar_panel_configuration(unit_solar_panel_configuration) else: - # Model the solar potential at the property level - for p in input_properties: - # TODO: Complete me! - we probably won't do this for individual flats - solar_performance = solar_api_client.get( - longitude=p.spatial["longitude"], latitude=p.spatial["latitude"] - ) + # # Model the solar potential at the property level + # for p in input_properties: + # # TODO: Complete me! - we probably won't do this for individual flats + # solar_performance = solar_api_client.get( + # longitude=p.spatial["longitude"], latitude=p.spatial["latitude"] + # ) + print("Implement me") logger.info("Getting components and epc recommendations") recommendations = {} diff --git a/backend/app/plan/schemas.py b/backend/app/plan/schemas.py index fbc4d4f2..77ac4217 100644 --- a/backend/app/plan/schemas.py +++ b/backend/app/plan/schemas.py @@ -16,6 +16,7 @@ class PlanTriggerRequest(BaseModel): # Pre-defined list of possibilities for exclusions _allowed_exclusions = { + # Measure classes "wall_insulation", "ventilation", "roof_insulation", @@ -25,7 +26,9 @@ class PlanTriggerRequest(BaseModel): "heating", "hot_water", "lighting", - "solar_pv" + "solar_pv", + # Specific measures + "air_source_heat_pump", } _allowed_goals = {"Increase EPC"} diff --git a/etl/customers/vander_elliot/non_intrusives.py b/etl/customers/vander_elliot/non_intrusives.py index bbc46754..280ba968 100644 --- a/etl/customers/vander_elliot/non_intrusives.py +++ b/etl/customers/vander_elliot/non_intrusives.py @@ -119,11 +119,12 @@ def app(): "portfolio_id": str(PORTFOLIO_ID), "housing_type": "Private", "goal": "Increase EPC", - "goal_value": "A", + "goal_value": "C", "trigger_file_path": filename, "already_installed_file_path": already_installed_filename, "patches_file_path": "", "non_invasive_recommendations_file_path": "", + "exclusions": ["wall_insulation", "air_source_heat_pump"], "budget": None, } print(body) diff --git a/recommendations/HeatingControlRecommender.py b/recommendations/HeatingControlRecommender.py index a94c2304..1aae3973 100644 --- a/recommendations/HeatingControlRecommender.py +++ b/recommendations/HeatingControlRecommender.py @@ -190,7 +190,11 @@ class HeatingControlRecommender: "new_u_value": None, "sap_points": None, "already_installed": already_installed, - "simulation_config": simulation_config + "simulation_config": simulation_config, + "description_simulation": { + "mainheatcont-description": "Programmer, room thermostat and TRVS", + "mainheatc-energy-eff": "Good" + } } ) @@ -250,6 +254,10 @@ class HeatingControlRecommender: "new_u_value": None, "sap_points": None, "already_installed": already_installed, - "simulation_config": simulation_config + "simulation_config": simulation_config, + "description_simulation": { + "mainheatcont-description": "Time and temperature zone control", + "mainheatc-energy-eff": "Very Good" + } } ) diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py index d908f4b9..0afdc18f 100644 --- a/recommendations/HeatingRecommender.py +++ b/recommendations/HeatingRecommender.py @@ -42,18 +42,21 @@ class HeatingRecommender: return self.has_electric_heating_description or electric_heating_assumed - def recommend(self, has_cavity_or_loft_recommendations, phase=0): + def recommend(self, has_cavity_or_loft_recommendations, phase=0, exclusions=None): """ Produces heating recommendations :param has_cavity_or_loft_recommendations: boolean indicating if we have produced a cavity or loft insulation recommendation. If there are cavity or loft recommendations, the property would need to complete those measures before being able to get the boiler upgrade scheme benefits. The messaging in the front end would be to :param phase: indicates the phase of the retrofit programme + :param exclusions: A list of exclusions for the recommendations """ # TODO: We could have a system flush recommendation for an existing boiler, where there is no need to replace # the boiler, but instead flushing the system will make it run more efficiently. There is a cost for this # in the Costs class, stored as SYSTEM_FLUSH_COST + + exclusions = [] if exclusions is None else exclusions self.heating_recommendations = [] self.heating_control_recommendations = [] @@ -112,7 +115,7 @@ class HeatingRecommender: # In the future, we'll allow overrides, so that non-intrusive surveys can contradict these conditions # and either allow or prevent the recommendation of an air source heat pump - if self.is_ashp_valid(): + if self.is_ashp_valid(exclusions=exclusions): self.recommend_air_source_heat_pump( phase=phase, has_cavity_or_loft_recommendations=has_cavity_or_loft_recommendations ) @@ -182,11 +185,14 @@ class HeatingRecommender: description = ("Replace the existing boiler and cylinder without a thermostat with a new electric combi " "boiler") - def is_ashp_valid(self): + def is_ashp_valid(self, exclusions): if "air_source_heat_pump" in self.property.non_invasive_recommendations: return True + if "air_source_heat_pump" in exclusions: + return False + 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"] diff --git a/recommendations/LightingRecommendations.py b/recommendations/LightingRecommendations.py index 31720579..1186b0a9 100644 --- a/recommendations/LightingRecommendations.py +++ b/recommendations/LightingRecommendations.py @@ -109,8 +109,12 @@ class LightingRecommendations: # For SAP points, we use the fact that lighting is usually worth 2 points and we scale this to # the proportion of lights that will be set to low energy "sap_points": round(2 * (number_non_lel_outlets / number_lighting_outlets), 2), - "heat_demand": heat_demand_change, + "kwh_savings": heat_demand_change, "co2_equivalent_savings": carbon_change, + "description_simulation": { + "lighting-energy-eff": "Very Good", + "lighting-description": "Low energy lighting in all fixed outlets", + }, **cost_result } ] diff --git a/recommendations/Recommendations.py b/recommendations/Recommendations.py index 03e6f284..fcdd513f 100644 --- a/recommendations/Recommendations.py +++ b/recommendations/Recommendations.py @@ -119,7 +119,9 @@ class Recommendations: has_cavity_or_loft_recommendations = len(cavity_or_loft_recommendations) > 0 self.heating_recommender.recommend( - phase=phase, has_cavity_or_loft_recommendations=has_cavity_or_loft_recommendations + phase=phase, + has_cavity_or_loft_recommendations=has_cavity_or_loft_recommendations, + exclusions=self.exclusions ) if ( self.heating_recommender.heating_recommendations or @@ -294,6 +296,13 @@ class Recommendations: if rec["type"] != "solar_pv": return 0, 0 + if property_instance.solar_panel_configuration is None: + print("PLACEHOLDER ESTIMATES") + # 50% reduction average + kwh_reduction = property_instance.energy_consumption_estimates["adjusted"]["appliances"] * 0.5 + predicted_appliances_cost_reduction = kwh_reduction * AnnualBillSavings.ELECTRICITY_PRICE_CAP + return predicted_appliances_cost_reduction, kwh_reduction + # Calulate the amount of energy the solar panel array will generate for this unit unit_energy_consumption = ( rec["initial_ac_kwh_per_year"] * @@ -721,10 +730,14 @@ class Recommendations: heating_kwh_reduction = 0 if predicted_heating_cost_reduction == 0 else ( phase_kwh_figures[previous_phase]["adjusted"]["heating"] - new_heating_kwh_adjusted ) + if heating_kwh_reduction < 0: + heating_kwh_reduction = 0 hot_water_kwh_reduction = 0 if predicted_hot_water_cost_reduction == 0 else ( phase_kwh_figures[previous_phase]["adjusted"]["hot_water"] - new_hot_water_kwh_adjusted ) + if hot_water_kwh_reduction < 0: + hot_water_kwh_reduction = 0 lighting_kwh_reduction = predicted_lighting_cost_reduction / AnnualBillSavings.ELECTRICITY_PRICE_CAP @@ -773,7 +786,7 @@ class Recommendations: # 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["co2_equivalent_savings"] = min(predicted_co2_savings, rec["co2_equivalent_savings"]) - rec["heat_demand"] = min(predicted_heat_demand, rec["heat_demand"]) + rec["heat_demand"] = predicted_heat_demand else: rec["sap_points"] = predicted_sap_points rec["co2_equivalent_savings"] = predicted_co2_savings diff --git a/recommendations/SolarPvRecommendations.py b/recommendations/SolarPvRecommendations.py index af1e7f27..276573ec 100644 --- a/recommendations/SolarPvRecommendations.py +++ b/recommendations/SolarPvRecommendations.py @@ -244,6 +244,7 @@ class SolarPvRecommendations: # This is required for simulating the SAP impact. solar_pv_percentage is between 0 & 1 so we scale # back up here "photo_supply": 100 * roof_coverage, - "has_battery": has_battery + "has_battery": has_battery, + "description_simulation": {"photo-supply": 100 * roof_coverage}, } ) diff --git a/recommendations/WindowsRecommendations.py b/recommendations/WindowsRecommendations.py index 8c0cc493..29c75989 100644 --- a/recommendations/WindowsRecommendations.py +++ b/recommendations/WindowsRecommendations.py @@ -115,5 +115,12 @@ class WindowsRecommendations: "already_installed": already_installed, **cost_result, "is_secondary_glazing": is_secondary_glazing, + # TODO: Make this condition on is_secondary_glazing + "description_simulation": { + "multi-glaze-proportion": 100, + "windows-energy-eff": "Average", + "windows-description": "Fully double glazed", + "glazed-type": "double glazing installed during or after 2002", + } } ]