Updating recommendation classes with new cost data

This commit is contained in:
Khalim Conn-Kowlessar 2023-11-24 09:58:13 +00:00
parent cb52c9f7a3
commit bf2e6c1ebc
8 changed files with 67 additions and 41 deletions

View file

@ -147,7 +147,7 @@ async def trigger_plan(body: PlanTriggerRequest):
property_recommendations.append(wall_recomender.recommendations) property_recommendations.append(wall_recomender.recommendations)
# Roof recommendations # Roof recommendations
roof_recommender = RoofRecommendations(property_instance=p, materials=materials_by_type["roof"]) roof_recommender = RoofRecommendations(property_instance=p, materials=materials)
roof_recommender.recommend() roof_recommender.recommend()
if roof_recommender.recommendations: if roof_recommender.recommendations:
@ -156,7 +156,7 @@ async def trigger_plan(body: PlanTriggerRequest):
# Ventilation recommendations # Ventilation recommendations
ventilation_recomender = VentilationRecommendations( ventilation_recomender = VentilationRecommendations(
property_instance=p, property_instance=p,
materials=materials_by_type["ventilation"] materials=[part for part in materials if part["type"] == "mechanical_ventilation"]
) )
ventilation_recomender.recommend() ventilation_recomender.recommend()

View file

@ -154,7 +154,7 @@ def create_recommendation_scoring_data(
if len(parts) != 1: if len(parts) != 1:
raise ValueError("More than one part for roof insulation - investiage me") raise ValueError("More than one part for roof insulation - investiage me")
scoring_dict["roof_insulation_thickness_ENDING"] = str(parts[0]["depths"][0]) scoring_dict["roof_insulation_thickness_ENDING"] = str(int(parts[0]["depth"]))
scoring_dict["ROOF_ENERGY_EFF_ENDING"] = "Very Good" scoring_dict["ROOF_ENERGY_EFF_ENDING"] = "Very Good"
else: else:
# Fill missing roof u-values - this fill is not based on recommended upgrades # Fill missing roof u-values - this fill is not based on recommended upgrades

View file

@ -113,7 +113,7 @@ class Costs:
total_cost = subtotal_before_vat + vat_cost total_cost = subtotal_before_vat + vat_cost
labour_hours = material["labour_hours"] * wall_area labour_hours = material["labour_hours_per_unit"] * wall_area
return { return {
"total": total_cost, "total": total_cost,
@ -151,7 +151,7 @@ class Costs:
total_cost = subtotal_before_vat + vat_cost total_cost = subtotal_before_vat + vat_cost
labour_hours = material["labour_hours"] * floor_area labour_hours = material["labour_hours_per_unit"] * floor_area
return { return {
"total": total_cost, "total": total_cost,

View file

@ -43,6 +43,6 @@ class FireplaceRecommendations(Definitions):
"starting_u_value": None, "starting_u_value": None,
"new_u_value": None, "new_u_value": None,
"sap_points": None, "sap_points": None,
"cost": estimated_cost, "total": estimated_cost,
} }
] ]

View file

@ -127,7 +127,11 @@ class FloorRecommendations(Definitions):
if self.property.floor["is_solid"]: if self.property.floor["is_solid"]:
# Given the U-value, we recommend solid floor insulation options which are usually solid foam # Given the U-value, we recommend solid floor insulation options which are usually solid foam
self.recommend_floor_insulation(u_value=u_value, parts=self.solid_floor_insulation_parts) self.recommend_floor_insulation(
u_value=u_value,
insulation_materials=self.solid_floor_insulation_materials,
non_insulation_materials=self.solid_floor_non_insulation_materials
)
return return
if self.property.floor["is_to_unheated_space"] or self.property.floor["is_to_external_air"]: if self.property.floor["is_to_unheated_space"] or self.property.floor["is_to_external_air"]:

View file

@ -1,4 +1,5 @@
import math import math
import pandas as pd
from backend.Property import Property from backend.Property import Property
from typing import List from typing import List
from datatypes.enums import QuantityUnits from datatypes.enums import QuantityUnits
@ -6,6 +7,7 @@ from recommendations.recommendation_utils import (
get_roof_u_value, r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns, get_roof_u_value, r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns,
update_lowest_selected_u_value, get_recommended_part, convert_thickness_to_numeric update_lowest_selected_u_value, get_recommended_part, convert_thickness_to_numeric
) )
from recommendations.Costs import Costs
class RoofRecommendations: class RoofRecommendations:
@ -27,13 +29,17 @@ class RoofRecommendations:
materials: List materials: List
): ):
self.property = property_instance self.property = property_instance
self.costs = Costs(self.property)
# For audit purposes, when estimating u values we'll store it # For audit purposes, when estimating u values we'll store it
self.estimated_u_value = None self.estimated_u_value = None
# Will contains a list of recommended measures # Will contains a list of recommended measures
self.recommendations = [] self.recommendations = []
self.materials = materials self.loft_insulation_materials = [
part for part in materials if part["type"] == "loft_insulation"
]
self.loft_non_insulation_materials = []
def recommend(self): def recommend(self):
@ -58,7 +64,7 @@ class RoofRecommendations:
# If we have a u-value already, need to implement this # If we have a u-value already, need to implement this
if u_value: if u_value:
if u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: if u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
# The floor is already compliant # The Roof is already compliant
return return
if self.property.data["transaction-type"] == "new dwelling": if self.property.data["transaction-type"] == "new dwelling":
@ -66,6 +72,10 @@ class RoofRecommendations:
raise NotImplementedError("Implement me") raise NotImplementedError("Implement me")
u_value = get_roof_u_value(**{**self.property.roof, "age_band": self.property.age_band}) u_value = get_roof_u_value(**{**self.property.roof, "age_band": self.property.age_band})
self.estimated_u_value = u_value
if u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
# The Roof is already compliant
return
if self.property.roof["is_pitched"] or self.property.roof["is_flat"]: if self.property.roof["is_pitched"] or self.property.roof["is_flat"]:
self.recommend_roof_insulation(u_value, insulation_thickness, self.property.roof) self.recommend_roof_insulation(u_value, insulation_thickness, self.property.roof)
@ -78,18 +88,21 @@ class RoofRecommendations:
raise NotImplementedError("Implement me") raise NotImplementedError("Implement me")
@staticmethod @staticmethod
def make_loft_insulation_description(material, depth): def make_loft_insulation_description(material):
return f"Install {depth}{material['depth_unit']} of {material['description']} in your loft" return f"Install {int(material['depth'])}{material['depth_unit']} of {material['description']} in your loft"
@staticmethod @staticmethod
def make_room_roof_insulation_description(material, depth): def make_room_roof_insulation_description(material, depth):
return f"Insulate your room roof with {depth}{material['depth_unit']} of {material['description']}" return f"Insulate your room roof with {depth}{material['depth_unit']} of {material['description']}"
@staticmethod @staticmethod
def make_flat_roof_insulation_description(material, depth): def make_flat_roof_insulation_description(material):
return f"Insulate the home's flat roof with {depth}{material['depth_unit']} of {material['description']}" return (f"Insulate the home's flat roof "
f"with {int(material['depth'])}{material['depth_unit']} of {material['description']}")
def recommend_roof_insulation(self, u_value, insulation_thickness, roof): def recommend_roof_insulation(
self, u_value, insulation_thickness, roof
):
""" """
This method will recommend which insulation materials to use This method will recommend which insulation materials to use
@ -120,28 +133,31 @@ class RoofRecommendations:
# from the base layer # from the base layer
if roof["is_pitched"]: if roof["is_pitched"]:
materials = [m for m in self.materials if m["type"] == "loft_insulation"] insulation_materials = self.loft_insulation_materials
non_insulation_materials = self.loft_non_insulation_materials
elif roof["is_flat"]: elif roof["is_flat"]:
materials = [m for m in self.materials if m["type"] == "flat_roof_insulation"] raise ValueError("UPDATE ME")
else: else:
raise ValueError("Roof is not pitched or flat") raise ValueError("Roof is not pitched or flat")
if not materials: if not insulation_materials:
raise ValueError("No roof insulation materials found") raise ValueError("No roof insulation materials found")
insulation_materials = pd.DataFrame(insulation_materials)
lowest_selected_u_value = None lowest_selected_u_value = None
recommendations = [] recommendations = []
for material in materials: for _, insulation_material_group in insulation_materials.groupby("description"):
for depth, cost_per_unit in zip(material["depths"], material["cost"]): for _, material in insulation_material_group.iterrows():
# We make sure we hit a depth of 270mm. We should factor in any existing insulation if the # We make sure we hit a depth of 270mm. We should factor in any existing insulation if the
# loft is already partially insulated. # loft is already partially insulated.
# Note: This requirement is only for loft insulation # Note: This requirement is only for loft insulation
if ((depth + insulation_thickness) < self.MINIMUM_LOFT_ISULATION_MM) and roof["is_pitched"]: if ((material["depth"] + insulation_thickness) < self.MINIMUM_LOFT_ISULATION_MM) and roof["is_pitched"]:
continue continue
part_u_value = r_value_per_mm_to_u_value(depth, material["r_value_per_mm"]) 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 = calculate_u_value_uplift(u_value, part_u_value)
new_u_value = math.ceil(new_u_value * 100.0) / 100.0 new_u_value = math.ceil(new_u_value * 100.0) / 100.0
@ -161,23 +177,26 @@ class RoofRecommendations:
if new_u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: 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) lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value)
# TODO: We should use the floor area divided by the number of floors to get the area of the roof if material["type"] == "loft_insulation":
estimated_cost = cost_per_unit * self.property.floor_area cost_result = self.costs.loft_insulation(
floor_area=self.property.insulation_floor_area,
if roof["is_pitched"]: material=material
description = self.make_loft_insulation_description(material, depth) )
description = self.make_loft_insulation_description(material)
elif material["type"] == "flat_roof_insulation":
description = self.make_flat_roof_insulation_description(material)
raise ValueError("COMPLETE ME")
else: else:
description = self.make_flat_roof_insulation_description(material, depth) raise ValueError("Invalid material type")
recommendations.append( recommendations.append(
{ {
"parts": [ "parts": [
get_recommended_part( get_recommended_part(
part=material, part=material.to_dict(),
selected_depth=depth,
quantity=self.property.insulation_wall_area, quantity=self.property.insulation_wall_area,
quantity_unit=QuantityUnits.m2.value, quantity_unit=QuantityUnits.m2.value,
selected_total_cost=estimated_cost cost_result=cost_result
) )
], ],
"type": "roof_insulation", "type": "roof_insulation",
@ -185,7 +204,7 @@ class RoofRecommendations:
"starting_u_value": u_value, "starting_u_value": u_value,
"new_u_value": new_u_value, "new_u_value": new_u_value,
"sap_points": None, "sap_points": None,
"cost": estimated_cost, **cost_result
} }
) )

View file

@ -65,6 +65,6 @@ class VentilationRecommendations(Definitions):
"starting_u_value": None, "starting_u_value": None,
"new_u_value": None, "new_u_value": None,
"sap_points": None, "sap_points": None,
"cost": estimated_cost, "total": estimated_cost,
} }
] ]

View file

@ -180,7 +180,7 @@ class WallRecommendations(Definitions):
filled cavity wall filled cavity wall
""" """
cavity_wall_fills = [m for m in self.materials if m["type"] == "cavity_wall_insulation"] insulation_materials = pd.DataFrame(self.cavity_wall_insulation_materials)
cavity_width = 75 cavity_width = 75
if insulation_thickness == "below average": if insulation_thickness == "below average":
cavity_width = cavity_width * (1 - PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION) cavity_width = cavity_width * (1 - PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION)
@ -188,8 +188,9 @@ class WallRecommendations(Definitions):
# Test the different fill options # Test the different fill options
lowest_selected_u_value = None lowest_selected_u_value = None
recommendations = [] recommendations = []
for part in cavity_wall_fills: for _, material in insulation_materials.iterrows():
part_u_value = r_value_per_mm_to_u_value(cavity_width, part["r_value_per_mm"])
part_u_value = r_value_per_mm_to_u_value(cavity_width, material["r_value_per_mm"])
_, new_u_value = calculate_u_value_uplift(u_value, part_u_value) _, new_u_value = calculate_u_value_uplift(u_value, part_u_value)
new_u_value = math.ceil(new_u_value * 100.0) / 100.0 new_u_value = math.ceil(new_u_value * 100.0) / 100.0
@ -202,25 +203,27 @@ class WallRecommendations(Definitions):
if new_u_value <= self.BUILDING_REGULATIONS_PART_L_CAVITY_WALL_MAX_U_VALUE: if new_u_value <= self.BUILDING_REGULATIONS_PART_L_CAVITY_WALL_MAX_U_VALUE:
lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value) lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value)
estimated_cost = part["cost"] * self.property.insulation_wall_area cost_result = self.costs.cavity_wall_insulation(
wall_area=self.property.insulation_wall_area,
material=material.to_dict(),
)
recommendations.append( recommendations.append(
{ {
"parts": [ "parts": [
get_recommended_part( get_recommended_part(
part=part, part=material.to_dict(),
selected_depth=None,
quantity=self.property.insulation_wall_area, quantity=self.property.insulation_wall_area,
quantity_unit=QuantityUnits.m2.value, quantity_unit=QuantityUnits.m2.value,
selected_total_cost=estimated_cost cost_result=cost_result
) )
], ],
"type": "wall_insulation", "type": "wall_insulation",
"description": f"Fill cavity with {part['description']}", "description": f"Fill cavity with {material['description']}",
"starting_u_value": u_value, "starting_u_value": u_value,
"new_u_value": new_u_value, "new_u_value": new_u_value,
"sap_points": None, "sap_points": None,
"cost": estimated_cost, **cost_result
} }
) )