From cb52c9f7a3b2fe443f1bf08532ba3291bc639bcc Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 24 Nov 2023 08:15:32 +0000 Subject: [PATCH] Updated wall recommended partially for new costs --- recommendations/WallRecommendations.py | 88 +++++++++++++++++++------- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py index 12085840..4595ef22 100644 --- a/recommendations/WallRecommendations.py +++ b/recommendations/WallRecommendations.py @@ -1,6 +1,8 @@ import math from typing import List +import pandas as pd + from datatypes.enums import QuantityUnits from backend.Property import Property from BaseUtility import Definitions @@ -9,6 +11,7 @@ from recommendations.recommendation_utils import ( get_recommended_part, get_wall_u_value ) from recommendations.config import PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION +from recommendations.Costs import Costs from utils.logger import setup_logger logger = setup_logger() @@ -50,13 +53,36 @@ class WallRecommendations(Definitions): materials: List ): self.property = property_instance + self.costs = Costs(self.property) # For audit purposes, when estimating u values we'll store it self.estimated_u_value = None # Will contains a list of recommended measures self.recommendations = [] - self.materials = materials + self.cavity_wall_insulation_materials = [ + part for part in materials if part["type"] == "cavity_wall_insulation" + ] + + self.internal_wall_insulation_materials = [ + part for part in materials if part["type"] == "internal_wall_insulation" + ] + + self.internal_wall_non_insulation_materials = [ + part for part in materials if part["type"] in [ + "iwi_wall_demolition", "iwi_vapour_barrier", "iwi_redecoration" + ] + ] + + self.external_wall_insulation_materials = [ + part for part in materials if part["type"] == "external_wall_insulation" + ] + + self.external_wall_non_insulation_materials = [ + part for part in materials if part["type"] in [ + "ewi_wall_demolition", "ewi_wall_preparation", "ewi_wall_redecoration" + ] + ] @property def ewi_valid(self): @@ -200,15 +226,15 @@ class WallRecommendations(Definitions): self.recommendations = recommendations - def _find_insulation(self, parts, u_value): + def _find_insulation(self, u_value, insulation_materials, non_insulation_materials): + lowest_selected_u_value = None recommendations = [] - for part in parts: + for _, insulation_material_group in insulation_materials.groupby("description"): - for depth, cost_per_unit in zip(part["depths"], part["cost"]): - - part_u_value = r_value_per_mm_to_u_value(depth, part["r_value_per_mm"]) + for _, material in insulation_material_group.iterrows(): + part_u_value = r_value_per_mm_to_u_value(material["depth"], material["r_value_per_mm"]) _, new_u_value = calculate_u_value_uplift(u_value, part_u_value) new_u_value = math.ceil(new_u_value * 100.0) / 100.0 @@ -225,27 +251,40 @@ class WallRecommendations(Definitions): # We allow a small tolerance for error so we don't discount the recommendation entirely if new_u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: + lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value) - estimated_cost = cost_per_unit * self.property.insulation_wall_area + if material["type"] == "internal_wall_insulation": + cost_result = self.costs.internal_wall_insulation( + wall_area=self.property.insulation_wall_area, + material=material.to_dict(), + non_insulation_materials=non_insulation_materials + ) + elif material["type"] == "external_wall_insulation": + cost_result = self.costs.external_wall_insulation( + wall_area=self.property.insulation_wall_area, + material=material.to_dict(), + non_insulation_materials=non_insulation_materials + ) + else: + raise ValueError("Invalid material type") recommendations.append( { "parts": [ get_recommended_part( - part=part, - selected_depth=depth, + part=material.to_dict(), quantity=self.property.insulation_wall_area, quantity_unit=QuantityUnits.m2.value, - selected_total_cost=estimated_cost + cost_result=cost_result ) ], "type": "wall_insulation", - "description": "Install " + self._make_description(part, depth), + "description": "Install " + self._make_description(material), "starting_u_value": u_value, "new_u_value": new_u_value, "sap_points": None, - "cost": estimated_cost, + **cost_result } ) @@ -258,27 +297,32 @@ class WallRecommendations(Definitions): :return: """ - ewi_parts = [ - part for part in self.materials if part["type"] == "external_wall_insulation" - ] if self.ewi_valid else [] - - iwi_parts = [part for part in self.materials if part["type"] == "internal_wall_insulation"] - # Recommend external and internal wall insulation separately # Since external and internal wall insulation are sufficiently different, # we separate the logic for for recommending them, therefore we don't # consider diminishing returns between the two - ewi_recommendations = self._find_insulation(ewi_parts, u_value) - iwi_recommendations = self._find_insulation(iwi_parts, u_value) + ewi_recommendations = [] + if self.ewi_valid: + ewi_recommendations = self._find_insulation( + u_value=u_value, + insulation_materials=pd.DataFrame(self.external_wall_insulation_materials), + non_insulation_materials=self.external_wall_non_insulation_materials + ) + + iwi_recommendations = self._find_insulation( + u_value=u_value, + insulation_materials=pd.DataFrame(self.internal_wall_insulation_materials), + non_insulation_materials=self.internal_wall_non_insulation_materials + ) self.recommendations += ewi_recommendations + iwi_recommendations self.prune_diminishing_recommendations() @staticmethod - def _make_description(part, depth): - return f"{depth}{part['depth_unit']} {part['description']}" + def _make_description(material): + return f"{int(material['depth'])}{material['depth_unit']} {material['description']}" def prune_diminishing_recommendations(self): # For any recommendations, if we have at least 1 reommendation that does not exhibit diminishing returns