mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Updating recommendation classes with new cost data
This commit is contained in:
parent
cb52c9f7a3
commit
bf2e6c1ebc
8 changed files with 67 additions and 41 deletions
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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"]:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue