From e8d31d56a68e91f866f8c2779756e53a393235fb Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 15 Aug 2023 17:33:38 +0100 Subject: [PATCH] Adding in costing framework wip --- backend/app/db/models/materials.py | 5 +++-- backend/app/plan/router.py | 9 ++++++--- recommendations/FloorRecommendations.py | 17 +++++++++++++---- recommendations/WallRecommendations.py | 15 ++++++++++++--- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/backend/app/db/models/materials.py b/backend/app/db/models/materials.py index 4c4a8a09..00430b1c 100644 --- a/backend/app/db/models/materials.py +++ b/backend/app/db/models/materials.py @@ -1,6 +1,6 @@ import enum -from sqlalchemy import Column, Integer, String, Float, Enum, TIMESTAMP +from sqlalchemy import Column, Integer, String, Float, Enum, TIMESTAMP, Boolean from sqlalchemy.orm import declarative_base from sqlalchemy.sql import func @@ -38,7 +38,7 @@ class Material(Base): description = Column(String, nullable=False) depths = Column(String) # You may want to use a specific JSON type depending on the database depth_unit = Column(Enum(DepthUnit, values_callable=lambda x: [e.value for e in x]), nullable=False) - cost = Column(Float) + cost = Column(String) cost_unit = Column(Enum(CostUnit, values_callable=lambda x: [e.value for e in x]), nullable=False) r_value_per_mm = Column(Float) r_value_unit = Column(Enum(RValueUnit, values_callable=lambda x: [e.value for e in x]), nullable=False) @@ -49,3 +49,4 @@ class Material(Base): ) link = Column(String) created_at = Column(TIMESTAMP, nullable=False, server_default=func.now()) + is_active = Column(Boolean, nullable=False, default=True) diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index eaa9a2ce..9b401a3c 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -200,8 +200,10 @@ async def trigger_plan(body: PlanTriggerRequest): # Floor recommendations floor_recommender = FloorRecommendations( - property_instance=p, uvalue_estimates=floors_u_value_estimate, - total_floor_area_group_decile=total_floor_area_group_decile + property_instance=p, + uvalue_estimates=floors_u_value_estimate, + total_floor_area_group_decile=total_floor_area_group_decile, + materials=materials_by_type["suspended_floor_insulation"] + materials_by_type["solid_floor_insulation"], ) floor_recommender.recommend() @@ -256,8 +258,9 @@ async def trigger_plan(body: PlanTriggerRequest): # TODO: We start off by optimising the recommendations recommendations_to_upload = recommendations[p.id] - if not recommendations: + if not recommendations_to_upload: continue + # Create a plan new_plan_id = create_plan( { diff --git a/recommendations/FloorRecommendations.py b/recommendations/FloorRecommendations.py index b45f6953..bf18cfe2 100644 --- a/recommendations/FloorRecommendations.py +++ b/recommendations/FloorRecommendations.py @@ -116,6 +116,13 @@ class FloorRecommendations(BaseUtility): else: self.materials = parts + self.suspended_floor_insulation_parts = [ + part for part in self.materials if part["type"] == "suspended_floor_insulation" + ] + self.solid_floor_insulation_parts = [ + part for part in self.materials if part["type"] == "solid_floor_insulation" + ] + @staticmethod def _estimate_perimeter(floor_area, num_rooms): # Compute average room size based on total floor area and number of rooms @@ -266,11 +273,11 @@ class FloorRecommendations(BaseUtility): if is_suspended: # Given the U-value, we recommend underfloor insulation - self.recommend_floor_insulation(u_value=u_value, parts=suspended_floor_insulation_parts) + self.recommend_floor_insulation(u_value=u_value, parts=self.suspended_floor_insulation_parts) if is_solid: # Given the U-value, we recommend solid floor insulation options which are usually solid foam - self.recommend_floor_insulation(u_value=u_value, parts=solid_floor_insulation_parts) + self.recommend_floor_insulation(u_value=u_value, parts=self.solid_floor_insulation_parts) @staticmethod def _make_floor_description(part, depth): @@ -284,7 +291,8 @@ class FloorRecommendations(BaseUtility): lowest_selected_u_value = None for part in parts: - for depth in part["depths"]: + 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"]) _, new_u_value = calculate_u_value_uplift(u_value, part_u_value) new_u_value = math.ceil(new_u_value * 100.0) / 100.0 @@ -306,7 +314,8 @@ class FloorRecommendations(BaseUtility): "description": self._make_floor_description(part, depth), "starting_u_value": u_value, "new_u_value": new_u_value, - "sap_points": estimate_sap_points() + "sap_points": estimate_sap_points(), + "cost": cost_per_unit * self.property.floor_area, } ) diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py index a9c590dc..fa625d53 100644 --- a/recommendations/WallRecommendations.py +++ b/recommendations/WallRecommendations.py @@ -310,7 +310,8 @@ class WallRecommendations(BaseUtility): recommendations = [] for part in parts: - for depth in part["depths"]: + 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"]) _, new_u_value = calculate_u_value_uplift(u_value, part_u_value) @@ -339,6 +340,7 @@ class WallRecommendations(BaseUtility): "starting_u_value": u_value, "new_u_value": new_u_value, "sap_points": estimate_sap_points(), + "cost": cost_per_unit * self.property.insulation_wall_area, } ) @@ -371,7 +373,10 @@ class WallRecommendations(BaseUtility): # By looping through ewi first, if there is nothing there, that ensures not combinations are tested for ewi_part in ewi_parts: for iwi_part in iwi_parts: - for ewi_depth, iwi_depth in itertools.product(ewi_part["depths"], iwi_part["depths"]): + for (ewi_depth, ewi_cost_per_unit), (iwi_depth, iwi_cost_per_unit) in itertools.product( + zip(ewi_part["depths"], ewi_part["cost"]), + zip(iwi_part["depths"], iwi_part["cost"]) + ): ewi_part_u_value = r_value_per_mm_to_u_value(ewi_depth, ewi_part["r_value_per_mm"]) iwi_part_u_value = r_value_per_mm_to_u_value(iwi_depth, iwi_part["r_value_per_mm"]) @@ -401,7 +406,11 @@ class WallRecommendations(BaseUtility): ), "starting_u_value": u_value, "new_u_value": combined_new_u_value, - "sap_points": estimate_sap_points() + "sap_points": estimate_sap_points(), + "cost": ( + ewi_cost_per_unit * self.property.insulation_wall_area + iwi_cost_per_unit * + self.property.insulation_wall_area + ), } self.recommendations.append(recommendation)