mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
244 lines
8.6 KiB
Python
244 lines
8.6 KiB
Python
import pint
|
|
import re
|
|
|
|
from model_data.Property import Property
|
|
import pandas as pd
|
|
|
|
external_wall_insulation_parts = [
|
|
{
|
|
# Example product
|
|
# https://insulationgo.co.uk/100mm-rockwool-external-wall-insulation-dual-density-slabs-a1-non-combustible
|
|
# -slab-ewi-render-fire/
|
|
"type": "external_wall_insulation",
|
|
"description": "Mineral Wool External Wall Insulation",
|
|
"depths": [30, 50, 70, 80, 90, 100],
|
|
"depth_unit": "mm",
|
|
"cost": None,
|
|
"cost_unit": None,
|
|
# The u-value here is just a placehoder for now and we probably want to have multiple
|
|
# options for internal wall insulation (e.g. 50mm, 100mm, 150mm), with different material types
|
|
# and costs
|
|
"r_value_per_mm": 0.0278,
|
|
"r_value_unit": "m2K/W"
|
|
},
|
|
{
|
|
"type": "external_wall_insulation",
|
|
"description": "Expanded Polystyrene External Wall Insulation",
|
|
"depths": [],
|
|
"depth_unit": "mm",
|
|
"cost": None,
|
|
# The u-value here is just a placehoder for now and we probably want to have multiple
|
|
# options for internal wall insulation (e.g. 50mm, 100mm, 150mm), with different material types
|
|
# and costs
|
|
"u_value": None
|
|
},
|
|
{
|
|
"type": "external_wall_insulation",
|
|
"description": "Phenolic Foam External Wall Insulation",
|
|
"depths": [],
|
|
"depth_unit": "mm",
|
|
"cost": None,
|
|
# The u-value here is just a placehoder for now and we probably want to have multiple
|
|
# options for internal wall insulation (e.g. 50mm, 100mm, 150mm), with different material types
|
|
# and costs
|
|
"u_value": None
|
|
},
|
|
{
|
|
"type": "external_wall_insulation",
|
|
"description": "Polyisocyanurate Foam External Wall Insulation",
|
|
"depths": [],
|
|
"depth_unit": "mm",
|
|
"cost": None,
|
|
# The u-value here is just a placehoder for now and we probably want to have multiple
|
|
# options for internal wall insulation (e.g. 50mm, 100mm, 150mm), with different material types
|
|
# and costs
|
|
"u_value": None
|
|
},
|
|
{
|
|
"type": "external_wall_insulation",
|
|
"description": "Woof Fiber External Wall Insulation",
|
|
"depths": [],
|
|
"depth_unit": "mm",
|
|
"cost": None,
|
|
# The u-value here is just a placehoder for now and we probably want to have multiple
|
|
# options for internal wall insulation (e.g. 50mm, 100mm, 150mm), with different material types
|
|
# and costs
|
|
"u_value": None
|
|
},
|
|
{
|
|
"type": "external_wall_insulation",
|
|
"description": "Aerogel External Wall Insulation",
|
|
"depths": [],
|
|
"depth_unit": "mm",
|
|
"cost": None,
|
|
# The u-value here is just a placehoder for now and we probably want to have multiple
|
|
# options for internal wall insulation (e.g. 50mm, 100mm, 150mm), with different material types
|
|
# and costs
|
|
"u_value": None
|
|
},
|
|
{
|
|
"type": "external_wall_insulation",
|
|
"description": "Vacuum Insulation Panels External Wall Insulation",
|
|
"depths": [],
|
|
"depth_unit": "mm",
|
|
"cost": None,
|
|
# The u-value here is just a placehoder for now and we probably want to have multiple
|
|
# options for internal wall insulation (e.g. 50mm, 100mm, 150mm), with different material types
|
|
# and costs
|
|
"u_value": None
|
|
}
|
|
]
|
|
|
|
wall_parts = [
|
|
{
|
|
"id": 1,
|
|
"type": "internal_wall_insulation",
|
|
"description": "Internal wall insulation",
|
|
"depth": None,
|
|
"depth_unit": "mm",
|
|
"cost": None,
|
|
# The u-value here is just a placehoder for now and we probably want to have multiple
|
|
# options for internal wall insulation (e.g. 50mm, 100mm, 150mm), with different material types
|
|
# and costs
|
|
"u_value": 0.3
|
|
},
|
|
|
|
]
|
|
|
|
|
|
class WallRecommendations:
|
|
YEAR_WALLS_BUILT_WITH_INSULATION = 1990
|
|
U_VALUE_UNIT = 'w/m-¦k'
|
|
BUILDING_REGULATIONS_PART_L_MAX_U_VALUE = 0.18
|
|
|
|
# Add some error so that if, for example, a new part we recommend provides a u-value of 0.19,
|
|
# we still consider it as an option
|
|
U_VALUE_ERROR = 0.01
|
|
|
|
DEFAULT_U_VALUES = {
|
|
"solid_brick": 2,
|
|
}
|
|
|
|
def __init__(self, property_instance: Property):
|
|
self.property = property_instance
|
|
self.year_built = self._year_property_was_built()
|
|
|
|
# Will contains a list of recommended measures
|
|
self.recommendations = []
|
|
|
|
def _year_property_was_built(self):
|
|
"""
|
|
Estimates when the property was built based on as much available data as possible.
|
|
|
|
"""
|
|
|
|
if self.property.full_sap_epc:
|
|
return pd.to_datetime(self.property.full_sap_epc["lodgement-date"]).year
|
|
|
|
if self.property.data["construction-age-band"]:
|
|
# Take the upper limit
|
|
band = [int(x) for x in re.findall(r'\b\d{4}\b', self.property.data["construction-age-band"])]
|
|
return band[1]
|
|
|
|
raise NotImplementedError("Implement me!")
|
|
|
|
def recommend(self):
|
|
# if building built after 1990 + we're able to identify U-value +
|
|
# U-value less than 0.18 and if in or close to a conversation area,
|
|
# recommend internal wall insulation as a possible measure
|
|
|
|
u_value = self.property.walls["thermal_transmittance"]
|
|
|
|
is_cavity_wall = self.property.walls["is_cavity_wall"]
|
|
is_solid_brick = self.property.walls["is_solid_brick"]
|
|
|
|
if u_value:
|
|
if self.property.walls["thermal_transmittance_unit"] != self.U_VALUE_UNIT:
|
|
raise NotImplementedError("Haven't handled the case of other u value units yet")
|
|
|
|
if (not is_cavity_wall) and (self.year_built >= self.YEAR_WALLS_BUILT_WITH_INSULATION) and (
|
|
u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE
|
|
):
|
|
# Recommend internal wall insulation
|
|
iwi_parts = [part for part in wall_parts if part["type"] == "internal_wall_insulation"]
|
|
for part in iwi_parts:
|
|
_, new_u_value = self.calculate_u_value_uplift(u_value, part["u_value"])
|
|
new_u_value = round(new_u_value, 2)
|
|
|
|
# We allow a small tolerance for error so we don't discount the recommendation entirely
|
|
# if it's close, since this is an estimated new u-value
|
|
if new_u_value - self.U_VALUE_ERROR <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
|
self.recommendations.append(
|
|
{
|
|
**part, "new_u_value": new_u_value,
|
|
}
|
|
)
|
|
|
|
raise NotImplementedError("Not implemented yet")
|
|
|
|
@staticmethod
|
|
def calculate_u_value_uplift(u_value, insulation_u_value):
|
|
"""
|
|
Calculates the U-value uplift (improvement) when applying internal wall insulation to a wall.
|
|
|
|
|
|
:param u_value: Float, Starting U-value of the wall (without insulation) in W/m²K.
|
|
:param insulation_u_value: Float, U-value of the internal wall insulation in W/m²K.
|
|
|
|
Returns:
|
|
float: U-value uplift (improvement) achieved by applying internal wall insulation in W/m²K.
|
|
|
|
Raises:
|
|
ZeroDivisionError: If either u_value or iwi_u_value is zero.
|
|
|
|
Notes:
|
|
This function assumes 100% coverage of the internal wall insulation and does not account for other factors
|
|
such as thermal bridging or the specific configuration of the wall.
|
|
"""
|
|
|
|
inverse_u_value = 1 / u_value
|
|
inverse_insulation_u_value = 1 / insulation_u_value
|
|
|
|
inverse_u_total = inverse_u_value + inverse_insulation_u_value
|
|
new_u_value = 1 / inverse_u_total
|
|
|
|
u_value_uplift = u_value - new_u_value
|
|
|
|
return u_value_uplift, new_u_value
|
|
|
|
@staticmethod
|
|
def rvalue_per_mm(total_r_value: float, thickness_mm: float) -> float:
|
|
"""Return R-value per mm.
|
|
|
|
Parameters
|
|
----------
|
|
total_r_value : float
|
|
Total R-value (in m2K/W).
|
|
thickness_mm : float
|
|
Thickness of the material in mm.
|
|
|
|
Returns
|
|
-------
|
|
float
|
|
R-value per mm.
|
|
"""
|
|
return total_r_value / thickness_mm
|
|
|
|
@staticmethod
|
|
def r_value_per_mm_to_u_value(depth_mm: int, r_value_per_mm: float):
|
|
"""
|
|
Converts R-value per mm to U-value in W/m²K.
|
|
|
|
Parameters
|
|
----------
|
|
depth_mm : int
|
|
Depth of the material in mm.
|
|
r_value_per_mm : float
|
|
R-value per mm.
|
|
|
|
Returns
|
|
-------
|
|
float
|
|
U-value in W/m²K.
|
|
"""
|
|
return 1 / (depth_mm * r_value_per_mm)
|