diff --git a/backend/Property.py b/backend/Property.py index a3328156..0d7553a5 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -83,6 +83,7 @@ class Property(Definitions): self.floor_area = None self.pitched_roof_area = None self.insulation_floor_area = None + self.number_lighting_outlets = None if epc_client: self.epc_client = epc_client @@ -703,7 +704,6 @@ class Property(Definitions): 'PROPERTY_TYPE', 'UPRN', 'NUMBER_OPEN_FIREPLACES', - 'FIXED_LIGHTING_OUTLETS_COUNT', 'MULTI_GLAZE_PROPORTION', 'MECHANICAL_VENTILATION', 'PHOTO_SUPPLY', @@ -752,9 +752,21 @@ class Property(Definitions): "FLOOR_HEIGHT": self.floor_height, "NUMBER_HABITABLE_ROOMS": self.number_of_rooms, "TOTAL_FLOOR_AREA": self.floor_area, + "FIXED_LIGHTING_OUTLETS_COUNT": self.number_lighting_outlets, **epc_raw_data, "BUILT_FORM": built_form, "POSTCODE": self.data["postcode"], } return property_data + + def set_number_lighting_outlets(self, cleaned_property_data): + """ + Extracts and cleans the estimated number of lighting outlets + :return: + """ + + if self.data["fixed-lighting-outlets-count"] == "": + self.number_lighting_outlets = round(cleaned_property_data["FIXED_LIGHTING_OUTLETS_COUNT"].values[0]) + else: + self.number_lighting_outlets = float(self.data["fixed-lighting-outlets-count"]) diff --git a/backend/app/db/models/materials.py b/backend/app/db/models/materials.py index e191c5ee..64c5e166 100644 --- a/backend/app/db/models/materials.py +++ b/backend/app/db/models/materials.py @@ -32,6 +32,7 @@ class MaterialType(enum.Enum): ewi_wall_demolition = "ewi_wall_demolition" ewi_wall_preparation = "ewi_wall_preparation" ewi_wall_redecoration = "ewi_wall_redecoration" + low_energy_lighting_installation = "low_energy_lighting_installation" class DepthUnit(enum.Enum): diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index ed568bdd..c2f1de4c 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -124,6 +124,14 @@ async def trigger_plan(body: PlanTriggerRequest): # Property recommendations p.get_components(cleaned) + # This is temp - this should happen after scoring + cleaned_property_data = DataProcessor.apply_averages_cleaning( + data_to_clean=pd.DataFrame([dict(**p.get_model_data(), LOCAL_AUTHORITY=p.data["local-authority"])]), + cleaning_data=cleaning_data, + cols_to_merge_on=['PROPERTY_TYPE', 'BUILT_FORM', 'CONSTRUCTION_AGE_BAND', 'LOCAL_AUTHORITY'], + ) + p.set_number_lighting_outlets(cleaned_property_data) + recommender = Recommendations(property_instance=p, materials=materials) property_recommendations = recommender.recommend() diff --git a/recommendations/Costs.py b/recommendations/Costs.py index e9fe4495..32ab32aa 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -595,3 +595,33 @@ class Costs: contingency = self.HIGH_RISK_CONTINGENCY else: contingency = self.CONTINGENCY + + material_cost = material["material_cost"] * number_of_lights + labour_cost = material["labour_cost"] * number_of_lights * self.labour_adjustment_factor + + subtotal_before_profit = material_cost + labour_cost + + contingency_cost = subtotal_before_profit * contingency + preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES + profit_cost = subtotal_before_profit * self.PROFIT_MARGIN + + subtotal_before_vat = subtotal_before_profit + contingency_cost + preliminaries_cost + profit_cost + vat_cost = subtotal_before_vat * self.VAT_RATE + total_cost = subtotal_before_vat + vat_cost + + labour_hours = material["labour_hours_per_unit"] * number_of_lights + # Assume a single electrician installing + labour_days = (labour_hours / 8) + + return { + "total": total_cost, + "subtotal": subtotal_before_vat, + "vat": vat_cost, + "contingency": contingency_cost, + "preliminaries": preliminaries_cost, + "material": material_cost, + "profit": profit_cost, + "labour_hours": labour_hours, + "labour_days": labour_days, + "labour_cost": labour_cost + } diff --git a/recommendations/LightingRecommendations.py b/recommendations/LightingRecommendations.py index 48bb2c0f..85b440b5 100644 --- a/recommendations/LightingRecommendations.py +++ b/recommendations/LightingRecommendations.py @@ -1,5 +1,6 @@ from backend.Property import Property from typing import List +from recommendations.Costs import Costs class LightingRecommendations: @@ -11,7 +12,16 @@ class LightingRecommendations: """ self.property = property_instance - self.materials = materials + self.costs = Costs(self.property) + + material = [ + material for material in materials if material["type"] == "low_energy_lighting_installation" + ] + if len(material) != 1: + raise ValueError("Incorrect number of low energy lighting materials specified") + + self.material = material[0] + self.recommendation = [] def recommend(self): """ @@ -32,3 +42,25 @@ class LightingRecommendations: ) number_non_lel_outlets = round(number_non_lel_outlets) + + if number_non_lel_outlets == 0: + return + + # Get the cost of the fittings + cost_result = self.costs.low_energy_lighting( + number_of_lights=number_non_lel_outlets, + number_current_lel_lights=number_lighting_outlets - number_non_lel_outlets, + material=self.material + ) + + self.recommendation = [ + { + "parts": [], + "type": "sealing_open_fireplace", + "description": "Install low energy lighting in %s outlets" % str(number_non_lel_outlets), + "starting_u_value": None, + "new_u_value": None, + "sap_points": None, + **cost_result + } + ] diff --git a/recommendations/Recommendations.py b/recommendations/Recommendations.py index 17dcc5df..0d39c9cf 100644 --- a/recommendations/Recommendations.py +++ b/recommendations/Recommendations.py @@ -5,6 +5,7 @@ from recommendations.WallRecommendations import WallRecommendations from recommendations.RoofRecommendations import RoofRecommendations from recommendations.VentilationRecommendations import VentilationRecommendations from recommendations.FireplaceRecommendations import FireplaceRecommendations +from recommendations.LightingRecommendations import LightingRecommendations from backend.ml_models.AnnualBillSavings import AnnualBillSavings @@ -34,6 +35,7 @@ class Recommendations: materials=[part for part in materials if part["type"] == "mechanical_ventilation"] ) self.fireplace_recommender = FireplaceRecommendations(property_instance=property_instance) + self.lighting_recommender = LightingRecommendations(property_instance=property_instance, materials=materials) def recommend(self): @@ -69,6 +71,11 @@ class Recommendations: if self.fireplace_recommender.recommendation: property_recommendations.append(self.fireplace_recommender.recommendation) + # Lighting recommendations + self.lighting_recommender.recommend() + if self.lighting_recommender.recommendation: + property_recommendations.append(self.lighting_recommender.recommendation) + # We insert temporary ids into the recommendations which is important for the optimiser later property_recommendations = self.insert_temp_recommendation_id(property_recommendations)