added test cases for roof u-values

This commit is contained in:
Khalim Conn-Kowlessar 2024-10-08 14:17:24 +01:00
parent 98a91a5a35
commit 35911c0db7
6 changed files with 124 additions and 16 deletions

View file

@ -128,7 +128,7 @@ class Recommendations:
property_recommendations.append(self.wall_recomender.recommendations)
phase += 1
self.roof_recommender.recommend(phase=phase, measures=measures)
self.roof_recommender.recommend(phase=phase, measures=measures, default_u_values=self.default_u_values)
if self.roof_recommender.recommendations:
property_recommendations.append(self.roof_recommender.recommendations)
phase += 1

View file

@ -108,7 +108,7 @@ class RoofRecommendations:
return full_insulated_room_roof or room_roof_insulated_at_rafters
def recommend(self, phase, measures=None):
def recommend(self, phase, measures=None, default_u_values=False):
if self.property.roof["has_dwelling_above"]:
return
@ -171,7 +171,8 @@ class RoofRecommendations:
insulation_thickness=self.insulation_thickness,
phase=phase,
is_flat=False,
is_pitched=True
is_pitched=True,
default_u_values=default_u_values
)
return
@ -184,7 +185,8 @@ class RoofRecommendations:
insulation_thickness=0,
phase=phase,
is_flat=True,
is_pitched=False
is_pitched=False,
default_u_values=default_u_values
)
return
@ -193,7 +195,7 @@ class RoofRecommendations:
if self.property.roof["is_roof_room"] and ("room_roof_insulation" in measures) or (
"room_roof_insulation" in [x["type"] for x in non_invasive_recommendations]
):
self.recommend_room_roof_insulation(u_value, phase)
self.recommend_room_roof_insulation(u_value, phase, default_u_values)
return
raise NotImplementedError("Implement me")
@ -215,7 +217,7 @@ class RoofRecommendations:
raise ValueError("Invalid material type")
def recommend_roof_insulation(
self, u_value, insulation_thickness, phase, is_pitched, is_flat
self, u_value, insulation_thickness, phase, is_pitched, is_flat, default_u_values
):
"""
@ -241,6 +243,7 @@ class RoofRecommendations:
:param phase: Phase of the recommendation
:param is_pitched: Is the roof pitched
:param is_flat: Is the roof flat
:param default_u_values: Use default u-values
:return:
"""
@ -266,7 +269,6 @@ class RoofRecommendations:
recommendations = []
for _, insulation_material_group in insulation_materials.groupby("description"):
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
# loft is already partially insulated.
# Note: This requirement is only for loft insulation
@ -340,9 +342,35 @@ class RoofRecommendations:
new_description = f"Pitched, {int(proposed_depth)}mm loft insulation"
if default_u_values:
# We update the u-value with the default if we're using default u-values
new_u_value = get_roof_u_value(
insulation_thickness=str(int(new_thickness)),
has_dwelling_above=self.property.roof["has_dwelling_above"],
is_loft=self.property.roof["is_loft"],
is_roof_room=self.property.roof["is_roof_room"],
is_thatched=self.property.roof["is_thatched"],
age_band=self.property.age_band,
is_flat=self.property.roof["is_flat"],
is_pitched=self.property.roof["is_pitched"],
is_at_rafters=self.property.roof["is_at_rafters"],
)
elif material["type"] == "flat_roof_insulation":
new_description = "Flat, insulated"
new_efficiency = "Good"
if default_u_values:
new_u_value = get_roof_u_value(
insulation_thickness="100",
has_dwelling_above=self.property.roof["has_dwelling_above"],
is_loft=self.property.roof["is_loft"],
is_roof_room=self.property.roof["is_roof_room"],
is_thatched=self.property.roof["is_thatched"],
age_band=self.property.age_band,
is_flat=self.property.roof["is_flat"],
is_pitched=self.property.roof["is_pitched"],
is_at_rafters=self.property.roof["is_at_rafters"],
)
else:
raise ValueError("Invalid material type")

View file

@ -544,6 +544,7 @@ class WallRecommendations(Definitions):
}
if default_u_values:
# If we're using default U-values, we overwrite new_u_value
new_u_value = get_wall_u_value(
clean_description=new_description,
age_band=self.property.age_band,

View file

@ -340,6 +340,7 @@ s9_list = [
s10_list = [
{
"Age_band": "A, B, C, D",
"Insulation_Thickness": "none",
"Pitched_slates_or_tiles_insulation_between_joists_or_unknown": 2.3,
"Pitched_slates_or_tiles_insulation_at_rafters": 2.3,
"Flat_roof": 2.3,
@ -350,6 +351,7 @@ s10_list = [
},
{
"Age_band": "E",
"Insulation_Thickness": 12,
"Pitched_slates_or_tiles_insulation_between_joists_or_unknown": 1.5,
"Pitched_slates_or_tiles_insulation_at_rafters": 1.5,
"Flat_roof": 1.5,
@ -360,6 +362,7 @@ s10_list = [
},
{
"Age_band": "F",
"Insulation_Thickness": 50,
"Pitched_slates_or_tiles_insulation_between_joists_or_unknown": 0.68,
"Pitched_slates_or_tiles_insulation_at_rafters": 0.68,
"Flat_roof": 0.68,
@ -370,6 +373,7 @@ s10_list = [
},
{
"Age_band": "G",
"Insulation_Thickness": 100,
"Pitched_slates_or_tiles_insulation_between_joists_or_unknown": 0.40,
"Pitched_slates_or_tiles_insulation_at_rafters": 0.40,
"Flat_roof": 0.40,
@ -380,6 +384,7 @@ s10_list = [
},
{
"Age_band": "H",
"Insulation_Thickness": 150,
"Pitched_slates_or_tiles_insulation_between_joists_or_unknown": 0.30,
"Pitched_slates_or_tiles_insulation_at_rafters": 0.35,
"Flat_roof": 0.35,
@ -390,6 +395,7 @@ s10_list = [
},
{
"Age_band": "I",
"Insulation_Thickness": 150,
"Pitched_slates_or_tiles_insulation_between_joists_or_unknown": 0.26,
"Pitched_slates_or_tiles_insulation_at_rafters": 0.35,
"Flat_roof": 0.35,
@ -400,6 +406,7 @@ s10_list = [
},
{
"Age_band": "J",
"Insulation_Thickness": 270,
"Pitched_slates_or_tiles_insulation_between_joists_or_unknown": 0.16,
"Pitched_slates_or_tiles_insulation_at_rafters": 0.20,
"Flat_roof": 0.25,
@ -410,6 +417,7 @@ s10_list = [
},
{
"Age_band": "K",
"Insulation_Thickness": 270,
"Pitched_slates_or_tiles_insulation_between_joists_or_unknown": 0.16,
"Pitched_slates_or_tiles_insulation_at_rafters": 0.20,
"Flat_roof": 0.25,
@ -420,6 +428,7 @@ s10_list = [
},
{
"Age_band": "L",
"Insulation_Thickness": 270,
"Pitched_slates_or_tiles_insulation_between_joists_or_unknown": 0.16,
"Pitched_slates_or_tiles_insulation_at_rafters": 0.18,
"Flat_roof": 0.18,

View file

@ -239,12 +239,7 @@ def get_wall_u_value(
return float(mapped_value)
def get_u_value_from_s9(
thickness, s9, is_loft, is_roof_room, is_thatched, is_at_rafters
):
"""Get the U-value from table S9 based on the insulation thickness."""
# If the roof as pitched & insulated at the rafters, it's a room roof
def extract_thickness(thickness, is_roof_room, is_at_rafters, is_loft, is_flat):
if is_roof_room or is_at_rafters:
# TODO: We get None instead of a string none, this should be fixed
if thickness is None:
@ -258,19 +253,40 @@ def get_u_value_from_s9(
}
thickness = thickness_map[thickness]
if is_flat:
try:
thickness = int(thickness)
return thickness
except ValueError:
# If thickness is not a valid number (could be a string or None), return None
return None
if thickness in ["below average", "average", "above average", "none", None] or (
not is_loft and not is_roof_room and not is_at_rafters
):
return None
elif thickness.endswith("+"):
thickness = int(thickness[:-1])
return thickness
else:
try:
thickness = int(thickness)
return thickness
except ValueError:
# If thickness is not a valid number (could be a string or None), return None
return None
def get_u_value_from_s9(
thickness, s9, is_loft, is_roof_room, is_thatched, is_at_rafters
):
"""Get the U-value from table S9 based on the insulation thickness."""
if thickness in ["below average", "average", "above average", "none", None, "0", 0] or (
not is_loft and not is_roof_room and not is_at_rafters
):
return None
# Determine the column to refer based on the roof type
column = (
"Thatched_roof_U_value_W_m2K"
@ -323,6 +339,14 @@ def get_roof_u_value(
if has_dwelling_above:
return 0.0
thickness = extract_thickness(
thickness=insulation_thickness,
is_roof_room=is_roof_room,
is_at_rafters=is_at_rafters,
is_loft=is_loft,
is_flat=is_flat
)
# Step 1: Try to get the U-value from table S9 based on the insulation thickness
# The conditions for using table S9 are:
# - The insulation thickness is known
@ -330,7 +354,7 @@ def get_roof_u_value(
# The criteria for using this table is predominately defined by insulation around joists which is predominately
# a feature of lofts and roof rooms
u_value = get_u_value_from_s9(
thickness=insulation_thickness,
thickness=thickness,
s9=s9,
is_loft=is_loft,
is_roof_room=is_roof_room,
@ -363,9 +387,34 @@ def get_roof_u_value(
column = "Pitched_slates_or_tiles_insulation_between_joists_or_unknown"
# Get the U-value from table S10 based on the age band and the determined column
u_value = s10.loc[s10["Age_band"].str.contains(age_band), column].values[0]
if is_flat and thickness is not None:
u_value = s10.loc[
(s10["Insulation_Thickness"] == thickness) |
s10["Age_band"].str.contains(age_band),
column
].values.min()
else:
u_value = s10.loc[s10["Age_band"].str.contains(age_band), column].values[0]
return float(u_value)
u_value = float(u_value)
# As per the documentation here: https://bregroup.com/documents/d/bre-group/rdsap_2012_9-94-20-09-2019
# Table s.10
# "The value from the table applies for unknown and as built. If the roof is known to have more insulation than
# would normally be expected for the age band, either observed or on the basis of documentary evidence, use the
# lower of the value in the table and:
# 50 mm insulation 0.68
# 100 mm insulation: 0.40
# 150 mm or more insulation: 0.30"
if thickness is not None:
if thickness == 50:
u_value = min(u_value, 0.68)
if thickness == 100:
u_value = min(u_value, 0.40)
if thickness >= 150:
u_value = min(u_value, 0.30)
return u_value
def estimate_number_of_floors(property_type):

View file

@ -6,6 +6,7 @@ from recommendations import recommendation_utils
from datatypes.enums import QuantityUnits
from recommendations.tests.test_data.wall_uvalue_test_cases import wall_uvalue_test_cases
from recommendations.tests.test_data.floor_uvalue_test_cases import floor_uvalue_test_cases
from recommendations.tests.test_data.roof_uvalue_test_cases import roof_uvalue_test_cases
class TestRecommendationUtils:
@ -222,6 +223,26 @@ class TestRecommendationUtils:
u_value = recommendation_utils.get_roof_u_value(**inputs)
assert u_value == 0.0, f"Expected 0.0, but got {u_value}"
@pytest.mark.parametrize(
"test_case",
roof_uvalue_test_cases
)
def test_roof_uvalues(self, test_case):
expected_uvalue = test_case["uvalue"]
inputs = test_case.copy()
del inputs["uvalue"]
# insulation_thickness = inputs["insulation_thickness"]
# has_dwelling_above = inputs["has_dwelling_above"]
# is_loft = inputs["is_loft"]
# is_roof_room = inputs["is_roof_room"]
# is_thatched = inputs["is_thatched"]
# age_band = inputs["age_band"]
# is_flat = inputs["is_flat"]
# is_pitched = inputs["is_pitched"]
# is_at_rafters = inputs["is_at_rafters"]
uvalue = recommendation_utils.get_roof_u_value(**inputs)
assert expected_uvalue == uvalue, f"Expected u value {expected_uvalue}, recieved {uvalue}"
@pytest.mark.parametrize(
"test_case",
wall_uvalue_test_cases