wrote unit tests for room roof insulation

This commit is contained in:
Khalim Conn-Kowlessar 2023-11-13 20:58:00 +09:00
parent c96fafa701
commit 15e48c2165
3 changed files with 266 additions and 4 deletions

View file

@ -67,6 +67,10 @@ class RoofRecommendations:
def make_loft_insulation_description(material, depth):
return f"Install {depth}{material['depth_unit']} of {material['description']}"
@staticmethod
def make_room_roof_insulation(material, depth):
return f"Insulate your room roof with {depth}{material['depth_unit']} of {material['description']}"
def recommend_loft_insulation(self, u_value, insulation_thickness):
"""
@ -81,6 +85,8 @@ class RoofRecommendations:
# from the base layer
loft_insulation_materials = [m for m in self.materials if m["type"] == "loft_insulation"]
if not loft_insulation_materials:
raise ValueError("No loft insulation materials found")
lowest_selected_u_value = None
recommendations = []
@ -149,7 +155,93 @@ class RoofRecommendations:
2) Insulation of external walls is covered by the wall recommendation class
3) We assume a "Gable" roof type
:param u_value:
:param insulation_thickness:
Further, we recommend internal roof insulation for the room in roof
The following document contains details around best practices for insulating a room in roof
https://assets.publishing.service.gov.uk/media/61d727d18fa8f50594b59305/retrofit-room-in-roof-insulation-best
-practice.pdf
Of particular interest are the following:
We also follow advide provided in this article on the Energy Saving Trust website, providing
high level guidance around roof insulation:
https://energysavingtrust.org.uk/advice/roof-and-loft-insulation/
To insulate a warm loft, the following advice is given
"An alternative way to insulate your loft is to fit rigid insulation boards between and over the rafters.
Rafters are the sloping timbers that make up the roof itself."
To then insulate a room roof, the following recommendation is provided:
"If you want to use your loft as a living space, or it is already being used as a living space,
then you need to make sure that all the walls and ceilings between a heated room and an unheated space
are insulated.
- Sloping ceilings can be insulated in the same way as for a warm roof,
but with a layer of plasterboard on the inside of the insulation.
- Vertical walls can be insulated in the same way.
- Flat ceilings can be insulated like a standard loft.
"
:param u_value: Current u-value of the roof
:param insulation_thickness: Current insulation thickness of the roof
:return:
"""
roof_roof_insulation_materials = [m for m in self.materials if m["type"] == "room_roof_insulation"]
if not roof_roof_insulation_materials:
raise ValueError("No room in roof insulation materials found")
if self.property.pitched_roof_area is None:
raise ValueError("pitched_roof_area not included as property attribute")
lowest_selected_u_value = None
recommendations = []
for material in roof_roof_insulation_materials:
for depth, cost_per_unit in zip(material["depths"], material["cost"]):
# We make sure we hit a depth of 270mm. We should factor in any existing insulation if the
# loft is already partially insulated
if (depth + insulation_thickness) < self.MINIMUM_LOFT_ISULATION_MM:
continue
part_u_value = r_value_per_mm_to_u_value(depth, material["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 I have a lowest U value and my new u value is higher than that but lower than the
# diminishing returns threshold, it can be considered
# If I have a lowest U value and my new u value is lower than the lowest value, it's
# further into the diminishing returns threshold and can shouldn't be
if is_diminishing_returns(
recommendations, new_u_value, lowest_selected_u_value, self.DIMINISHING_RETURNS_U_VALUE
):
continue
# We allow a small tolerance for error so we don't discount the recommendation entirely
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.pitched_roof_area
recommendations.append(
{
"parts": [
get_recommended_part(
part=material,
selected_depth=depth,
quantity=self.property.pitched_roof_area,
quantity_unit=QuantityUnits.m2.value,
selected_total_cost=estimated_cost
)
],
"type": "roof_insulation",
"description": self.make_room_roof_insulation(material, depth),
"starting_u_value": u_value,
"new_u_value": new_u_value,
"sap_points": None,
"cost": estimated_cost,
}
)
self.recommendations = recommendations

View file

@ -1,3 +1,4 @@
import numpy as np
import pytest
import math
from unittest.mock import MagicMock
@ -344,3 +345,58 @@ def test_park_home():
assert recommendation_utils.get_floor_u_value(
'suspended', 100, 40, 'A', 'park home', insulation_thickness="20mm"
) == 0
def test_esimtate_pitched_roof_area():
roof_area1 = recommendation_utils.esimtate_pitched_roof_area(
floor_area=100, floor_height=2
)
assert np.isclose(roof_area1, 107.70329614269008)
# As the floor height gets bigger, the area should get bigger
roof_area2 = recommendation_utils.esimtate_pitched_roof_area(
floor_area=100, floor_height=3
)
assert np.isclose(roof_area2, 116.61903789690601)
# As the floor area gets smaller, the area should get smaller
roof_area3 = recommendation_utils.esimtate_pitched_roof_area(
floor_area=100, floor_height=1
)
assert np.isclose(roof_area3, 101.9803902718557)
# As the floor area decreases, area should decrease
roof_area4 = recommendation_utils.esimtate_pitched_roof_area(
floor_area=50, floor_height=2
)
assert np.isclose(roof_area4, 57.44562646538029)
# As the floor area increases, area should increase
roof_area5 = recommendation_utils.esimtate_pitched_roof_area(
floor_area=150, floor_height=2
)
assert np.isclose(roof_area5, 157.797338380595)
zero_roof_area = recommendation_utils.esimtate_pitched_roof_area(
floor_area=0, floor_height=1000
)
assert zero_roof_area == 0
# If the floor height zero, we don't have a traingle, it's a flat roof
flat_roof_area = recommendation_utils.esimtate_pitched_roof_area(
floor_area=1000, floor_height=0
)
assert flat_roof_area == 1000
zero_roof_area2 = recommendation_utils.esimtate_pitched_roof_area(
floor_area=0, floor_height=0
)
assert zero_roof_area2 == 0

View file

@ -35,6 +35,19 @@ loft_insulation_materials_150mm_existing = [
}
]
room_roof_insulation_materials = [
{
'id': 18,
'type': 'room_roof_insulation',
'description': 'Example room roof insulation',
'depths': [50, 150, 220, 270, 300], 'depth_unit': 'mm', 'cost': [9, 10, 11, 12, 13],
'cost_unit': 'gbp_sq_meter',
'r_value_per_mm': 0.022727273, 'r_value_unit': 'square_meter_kelvin_per_watt',
'thermal_conductivity': 0.044, 'thermal_conductivity_unit': 'watt_per_meter_kelvin',
'link': None, 'is_active': True
}
]
class TestRoofRecommendations:
@ -205,12 +218,113 @@ class TestRoofRecommendations:
'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True, 'insulation_thickness': 'none'
}
property_instance7
property_instance7.pitched_roof_area = 110
roof_recommender7 = RoofRecommendations(
property_instance=property_instance7, materials=loft_insulation_materials
property_instance=property_instance7, materials=room_roof_insulation_materials
)
assert not roof_recommender7.recommendations
roof_recommender7.recommend()
# Even though we have 3 depths, we only end with 1 due to diminishin returns
assert len(roof_recommender7.recommendations) == 1
assert roof_recommender7.recommendations[0]["parts"][0]["depths"] == [270]
assert roof_recommender7.recommendations[0]["new_u_value"] == 0.14
assert roof_recommender7.recommendations[0]["starting_u_value"] == 0.8
assert roof_recommender7.recommendations[0]["description"] == \
"Insulate your room roof with 270mm of Example room roof insulation"
def test_ceiling_insulated_room_in_roof(self):
property_instance8 = Property(id=8, address1="fake", postcode="fake", epc_client=Mock())
property_instance8.age_band = "F"
property_instance8.floor_area = 100
property_instance8.roof = {
'original_description': 'Roof room(s), ceiling insulated',
'clean_description': 'Roof room(s), ceiling insulated',
'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False,
'is_at_rafters': False,
'is_assumed': False, 'has_dwelling_above': False, 'is_valid': True,
'insulation_thickness': 'average'
}
property_instance8.pitched_roof_area = 110
roof_recommender8 = RoofRecommendations(
property_instance=property_instance8, materials=room_roof_insulation_materials
)
assert not roof_recommender8.recommendations
roof_recommender8.recommend()
# No recommendations in this case
assert not roof_recommender8.recommendations
def test_insulated_room_in_roof(self):
property_instance9 = Property(id=9, address1="fake", postcode="fake", epc_client=Mock())
property_instance9.age_band = "F"
property_instance9.floor_area = 100
property_instance9.roof = {
'original_description': 'Roof room(s), insulated (assumed)',
'clean_description': 'Roof room(s), insulated',
'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False, 'is_at_rafters': False,
'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True, 'insulation_thickness': 'average'
}
property_instance9.pitched_roof_area = 110
roof_recommender9 = RoofRecommendations(
property_instance=property_instance9, materials=room_roof_insulation_materials
)
assert not roof_recommender9.recommendations
roof_recommender9.recommend()
# No recommendations in this case
assert not roof_recommender9.recommendations
def test_limited_insulated_room_in_roof(self):
property_instance10 = Property(id=10, address1="fake", postcode="fake", epc_client=Mock())
property_instance10.age_band = "F"
property_instance10.floor_area = 100
property_instance10.roof = {
'original_description': 'Roof room(s), limited insulation (assumed)',
'clean_description': 'Roof room(s), limited insulation',
'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False, 'is_at_rafters': False,
'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True,
'insulation_thickness': 'below average'
}
property_instance10.pitched_roof_area = 110
roof_recommender10 = RoofRecommendations(
property_instance=property_instance10, materials=room_roof_insulation_materials
)
assert not roof_recommender10.recommendations
roof_recommender10.recommend()
assert len(roof_recommender10.recommendations) == 2
assert roof_recommender10.recommendations[0]["parts"][0]["depths"] == [220]
assert roof_recommender10.recommendations[1]["parts"][0]["depths"] == [270]
assert roof_recommender10.recommendations[0]["new_u_value"] == 0.16
assert roof_recommender10.recommendations[1]["new_u_value"] == 0.14
assert roof_recommender10.recommendations[0]["starting_u_value"] == 0.8
assert roof_recommender10.recommendations[1]["starting_u_value"] == 0.8
assert roof_recommender10.recommendations[0]["description"] == \
"Insulate your room roof with 220mm of Example room roof insulation"
assert roof_recommender10.recommendations[1]["description"] == \
"Insulate your room roof with 270mm of Example room roof insulation"