mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
146 lines
6 KiB
Python
146 lines
6 KiB
Python
import math
|
|
from typing import List
|
|
from BaseUtility import Definitions
|
|
from datatypes.enums import QuantityUnits
|
|
from backend.Property import Property
|
|
from recommendations.recommendation_utils import (
|
|
r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns, update_lowest_selected_u_value,
|
|
get_recommended_part, get_floor_u_value
|
|
)
|
|
from recommendations.rdsap_tables import FLOOR_LEVEL_MAP
|
|
|
|
|
|
class FloorRecommendations(Definitions):
|
|
# part L building regulations indicate that any rennovations on an existing property's walls should
|
|
# achieve a U-value of no higher than 0.3
|
|
BUILDING_REGULATIONS_PART_L_MAX_U_VALUE = 0.25
|
|
# We don't recommend measures that are too low because it becomes expensive, therefore we aim to avoid
|
|
# diminishing returns. This value should be verified with Osmosis (TODO)
|
|
DIMINISHING_RETURNS_U_VALUE = 0.2
|
|
|
|
REGION_LOOKUP = {
|
|
"England and Wales": "England_Wales",
|
|
}
|
|
|
|
PART_L_YEAR_CUTOFF = 2002
|
|
|
|
def __init__(
|
|
self,
|
|
property_instance: Property,
|
|
materials: List,
|
|
):
|
|
self.property = property_instance
|
|
# 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.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"
|
|
]
|
|
|
|
def recommend(self):
|
|
u_value = self.property.floor["thermal_transmittance"]
|
|
is_suspended = self.property.floor["is_suspended"]
|
|
is_solid = self.property.floor["is_solid"]
|
|
floor_level = (
|
|
FLOOR_LEVEL_MAP[self.property.data["floor-level"]] if
|
|
self.property.data["floor-level"] not in self.DATA_ANOMALY_MATCHES else None
|
|
)
|
|
property_type = self.property.data["property-type"]
|
|
|
|
year_built = self.property.year_built
|
|
|
|
if self.property.floor["another_property_below"] | (self.property.floor["insulation_thickness"] in [
|
|
"average", "above average"
|
|
]):
|
|
# If there's another property below, it's likely impractical to recommend a floor upgrade,
|
|
# or if the floor is already insualted
|
|
return
|
|
|
|
# If the property is a flat that isn't at ground level, it's likely impractical to recommend a floor upgrade
|
|
if (floor_level != 0) and (property_type == "Flat"):
|
|
return
|
|
|
|
if u_value:
|
|
|
|
# By being built more recently than this, it means that the property was likely build with soild
|
|
# concrete floors with insulation already
|
|
if year_built < self.PART_L_YEAR_CUTOFF:
|
|
raise NotImplementedError("Not investigated this use case")
|
|
|
|
if u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
|
# The floor is already compliant
|
|
return
|
|
|
|
u_value = get_floor_u_value(
|
|
floor_type=self.property.floor_type,
|
|
area=float(self.property.data["total-floor-area"]),
|
|
perimeter=self.property.perimeter,
|
|
age_band=self.property.age_band,
|
|
insulation_thickness=self.property.floor["insulation_thickness"],
|
|
wall_type=self.property.wall_type
|
|
)
|
|
self.estimated_u_value = u_value
|
|
|
|
if is_suspended:
|
|
# Given the U-value, we recommend underfloor insulation
|
|
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=self.solid_floor_insulation_parts)
|
|
|
|
@staticmethod
|
|
def _make_floor_description(part, depth):
|
|
return f"Install {depth}{part['depth_unit']} {part['description']} insulation"
|
|
|
|
def recommend_floor_insulation(self, u_value, parts):
|
|
"""
|
|
This method is tasked with estimating the impact of performing suspended floor insulation
|
|
:return:
|
|
"""
|
|
|
|
lowest_selected_u_value = None
|
|
for part in parts:
|
|
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
|
|
|
|
if is_diminishing_returns(
|
|
self.recommendations, new_u_value, lowest_selected_u_value, self.DIMINISHING_RETURNS_U_VALUE
|
|
):
|
|
continue
|
|
|
|
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.floor_area
|
|
|
|
self.recommendations.append(
|
|
{
|
|
"parts": [
|
|
get_recommended_part(
|
|
part=part,
|
|
selected_depth=depth,
|
|
quantity=self.property.floor_area,
|
|
quantity_unit=QuantityUnits.m2.value,
|
|
selected_total_cost=estimated_cost
|
|
),
|
|
],
|
|
"type": "floor_insulation",
|
|
"description": self._make_floor_description(part, depth),
|
|
"starting_u_value": u_value,
|
|
"new_u_value": new_u_value,
|
|
"sap_points": None,
|
|
"cost": estimated_cost,
|
|
}
|
|
)
|