mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
335 lines
12 KiB
Python
335 lines
12 KiB
Python
import pytest
|
|
import math
|
|
from unittest.mock import MagicMock
|
|
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
|
|
|
|
|
|
class TestRecommendationUtils:
|
|
@pytest.fixture
|
|
def property_mock(self):
|
|
PropertyMock = MagicMock()
|
|
PropertyMock.data = {
|
|
'total_floor_area_group_decile': 'Decile 1',
|
|
'number-habitable-rooms': 3,
|
|
'number-heated-rooms': 2
|
|
}
|
|
return PropertyMock
|
|
|
|
def test_r_value_per_mm_to_u_value(self):
|
|
assert recommendation_utils.r_value_per_mm_to_u_value(1, 2) == 0.5
|
|
with pytest.raises(ZeroDivisionError):
|
|
recommendation_utils.r_value_per_mm_to_u_value(0, 2)
|
|
|
|
def test_calculate_u_value_uplift(self):
|
|
assert recommendation_utils.calculate_u_value_uplift(1, 2) == (0.33333333333333337, 0.6666666666666666)
|
|
with pytest.raises(ZeroDivisionError):
|
|
recommendation_utils.calculate_u_value_uplift(0, 2)
|
|
with pytest.raises(ZeroDivisionError):
|
|
recommendation_utils.calculate_u_value_uplift(1, 0)
|
|
|
|
def test_is_diminishing_returns(self):
|
|
assert not recommendation_utils.is_diminishing_returns([1, 2, 3], 1, 1, 1)
|
|
assert recommendation_utils.is_diminishing_returns([1, 2, 3], 0.5, 1, 1)
|
|
assert not recommendation_utils.is_diminishing_returns([], 1, None, 1)
|
|
|
|
def test_update_lowest_selected_u_value(self):
|
|
assert recommendation_utils.update_lowest_selected_u_value(1, 2) == 1
|
|
assert recommendation_utils.update_lowest_selected_u_value(None, 2) == 2
|
|
assert recommendation_utils.update_lowest_selected_u_value(1, 0.5) == 0.5
|
|
|
|
def test_get_recommended_part(self):
|
|
part = {'depths': [1, 2, 3]}
|
|
assert recommendation_utils.get_recommended_part(
|
|
part=part, selected_depth=1, selected_total_cost=50, quantity=99, quantity_unit="m2"
|
|
) == {'depths': [1], 'estimated_cost': 50, 'quantity': 99, 'quantity_unit': QuantityUnits.m2.value}
|
|
|
|
def test_get_roof_u_value(self):
|
|
# Test case 1: Insulation thickness is known and is_loft is True
|
|
inputs = {
|
|
'insulation_thickness': '50',
|
|
'is_loft': True,
|
|
'is_roof_room': False,
|
|
'is_thatched': False,
|
|
'has_dwelling_above': False,
|
|
'is_flat': False,
|
|
'is_pitched': True,
|
|
'is_at_rafters': False,
|
|
}
|
|
for age_band in ["A", "B", "C", "D"]:
|
|
assert recommendation_utils.get_roof_u_value(**{**inputs, "age_band": age_band}) == 0.68
|
|
|
|
def test_get_roof_u_value_case_2(self):
|
|
inputs = {
|
|
'original_description': 'Pitched, 400+ mm insulation at joists',
|
|
'clean_description': 'Pitched, 400+ mm insulation at joists',
|
|
'thermal_transmittance': None,
|
|
'thermal_transmittance_unit': None,
|
|
'is_pitched': True,
|
|
'is_roof_room': False,
|
|
'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': '400+',
|
|
'age_band': "J"
|
|
}
|
|
|
|
u_value = recommendation_utils.get_roof_u_value(**inputs)
|
|
assert u_value == 0.16, f"Expected 0.16, but got {u_value}"
|
|
|
|
def test_get_roof_u_value_case_3(self):
|
|
inputs = {
|
|
'original_description': 'Room-in-roof, 200 mm insulation at rafters',
|
|
'clean_description': 'Room-in-roof, 200 mm insulation at rafters',
|
|
'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': True,
|
|
'is_assumed': False,
|
|
'has_dwelling_above': False,
|
|
'is_valid': True,
|
|
'insulation_thickness': '200',
|
|
'age_band': "J"
|
|
}
|
|
|
|
u_value = recommendation_utils.get_roof_u_value(**inputs)
|
|
assert u_value == 0.21, f"Expected 0.21, but got {u_value}"
|
|
|
|
def test_get_roof_u_value_case_4(self):
|
|
inputs = {
|
|
'original_description': 'Pitched, below average insulation',
|
|
'clean_description': 'Pitched, below average insulation',
|
|
'thermal_transmittance': None,
|
|
'thermal_transmittance_unit': None,
|
|
'is_pitched': True,
|
|
'is_roof_room': False,
|
|
'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': 'below average',
|
|
'age_band': "E"
|
|
}
|
|
|
|
u_value = recommendation_utils.get_roof_u_value(**inputs)
|
|
assert u_value == 1.5, f"Expected 1.5, but got {u_value}"
|
|
|
|
def test_get_roof_u_value_case_5(self):
|
|
# Test case where insulation thickness is exactly specified
|
|
inputs = {
|
|
'original_description': 'Pitched, 100mm insulation',
|
|
'clean_description': 'Pitched, 100mm insulation',
|
|
'thermal_transmittance': None,
|
|
'thermal_transmittance_unit': None,
|
|
'is_pitched': True,
|
|
'is_roof_room': False,
|
|
'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': '100',
|
|
'age_band': "G"
|
|
}
|
|
|
|
u_value = recommendation_utils.get_roof_u_value(**inputs)
|
|
assert u_value == 0.40, f"Expected 0.40, but got {u_value}"
|
|
|
|
def test_get_roof_u_value_case_6(self):
|
|
# Test case for a thatched roof
|
|
inputs = {
|
|
'original_description': 'Thatched, 75mm insulation',
|
|
'clean_description': 'Thatched, 75mm insulation',
|
|
'thermal_transmittance': None,
|
|
'thermal_transmittance_unit': None,
|
|
'is_pitched': False,
|
|
'is_roof_room': False,
|
|
'is_loft': False,
|
|
'is_flat': False,
|
|
'is_thatched': True,
|
|
'is_at_rafters': False,
|
|
'is_assumed': False,
|
|
'has_dwelling_above': False,
|
|
'is_valid': True,
|
|
'insulation_thickness': '75',
|
|
'age_band': "H"
|
|
}
|
|
|
|
u_value = recommendation_utils.get_roof_u_value(**inputs)
|
|
assert u_value == 0.35, f"Expected 0.35, but got {u_value}"
|
|
|
|
def test_get_roof_u_value_case_7(self):
|
|
# Test case where the roof has a room in it
|
|
inputs = {
|
|
'original_description': 'Pitched, room-in-roof, 100mm insulation',
|
|
'clean_description': 'Pitched, room-in-roof, 100mm insulation',
|
|
'thermal_transmittance': None,
|
|
'thermal_transmittance_unit': None,
|
|
'is_pitched': True,
|
|
'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': '100',
|
|
'age_band': "J"
|
|
}
|
|
|
|
u_value = recommendation_utils.get_roof_u_value(**inputs)
|
|
assert u_value == 0.40, f"Expected 0.40, but got {u_value}"
|
|
|
|
def test_get_roof_u_value_case_8(self):
|
|
# Test case where there is a dwelling above the roof, U-value should be 0
|
|
inputs = {
|
|
'original_description': 'Pitched, 100mm insulation',
|
|
'clean_description': 'Pitched, 100mm insulation',
|
|
'thermal_transmittance': None,
|
|
'thermal_transmittance_unit': None,
|
|
'is_pitched': True,
|
|
'is_roof_room': False,
|
|
'is_loft': False,
|
|
'is_flat': False,
|
|
'is_thatched': False,
|
|
'is_at_rafters': False,
|
|
'is_assumed': False,
|
|
'has_dwelling_above': True,
|
|
'is_valid': True,
|
|
'insulation_thickness': '100',
|
|
'age_band': "J"
|
|
}
|
|
|
|
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",
|
|
wall_uvalue_test_cases
|
|
)
|
|
def test_get_wall_uvalue(self, test_case):
|
|
expected_uvalue = test_case["uvalue"]
|
|
inputs = test_case.copy()
|
|
del inputs["uvalue"]
|
|
uvalue = recommendation_utils.get_wall_u_value(**inputs)
|
|
assert expected_uvalue == uvalue, f"Expected u value {expected_uvalue}, recieved {uvalue}"
|
|
|
|
@pytest.mark.parametrize("test_input", floor_uvalue_test_cases)
|
|
def test_get_floor_u_value(self, test_input):
|
|
if not isinstance(test_input["expected"], float):
|
|
with pytest.raises(test_input["expected"]):
|
|
recommendation_utils.get_floor_u_value(
|
|
test_input["floor_type"],
|
|
test_input["area"],
|
|
test_input["perimeter"],
|
|
test_input["age_band"],
|
|
test_input["wall_type"],
|
|
test_input["insulation_thickness"],
|
|
)
|
|
else:
|
|
result = recommendation_utils.get_floor_u_value(
|
|
floor_type=test_input["floor_type"],
|
|
area=test_input["area"],
|
|
perimeter=test_input["perimeter"],
|
|
age_band=test_input["age_band"],
|
|
wall_type=test_input["wall_type"],
|
|
insulation_thickness=test_input["insulation_thickness"],
|
|
)
|
|
assert result == pytest.approx(test_input["expected"], abs=1e-2)
|
|
|
|
# Test with wall_type not in default_wall_thickness
|
|
def test_wall_type_not_in_default_wall_thickness(self):
|
|
with pytest.raises(IndexError):
|
|
recommendation_utils.get_floor_u_value(
|
|
floor_type="solid",
|
|
area=100,
|
|
perimeter=40,
|
|
age_band="A",
|
|
wall_type="InvalidWallType",
|
|
insulation_thickness=None,
|
|
)
|
|
|
|
# Test with age_band not in s11
|
|
def test_age_band_not_in_s11(self):
|
|
with pytest.raises(IndexError):
|
|
recommendation_utils.get_floor_u_value(
|
|
floor_type="solid",
|
|
area=100,
|
|
perimeter=40,
|
|
age_band="Z",
|
|
wall_type="Cavity",
|
|
insulation_thickness=None,
|
|
)
|
|
|
|
|
|
def test_estimate_perimeter_regular_inputs():
|
|
assert math.isclose(
|
|
recommendation_utils.estimate_perimeter(100, 5), 40.24922359499622,
|
|
rel_tol=1e-2
|
|
)
|
|
assert math.isclose(
|
|
recommendation_utils.estimate_perimeter(123, 5), 44.63854836349408,
|
|
rel_tol=1e-2
|
|
)
|
|
|
|
|
|
def test_estimate_perimeter_zero_floor_area():
|
|
with pytest.raises(ZeroDivisionError):
|
|
recommendation_utils.estimate_perimeter(0, 5)
|
|
|
|
with pytest.raises(ValueError):
|
|
assert recommendation_utils.estimate_perimeter(0, 0) == 0
|
|
|
|
|
|
def test_estimate_perimeter_invalid_inputs():
|
|
with pytest.raises(ValueError):
|
|
recommendation_utils.estimate_perimeter(100, 0)
|
|
with pytest.raises(ValueError):
|
|
recommendation_utils.estimate_perimeter(-100, 5)
|
|
with pytest.raises(ValueError):
|
|
recommendation_utils.estimate_perimeter(100, -5)
|
|
|
|
|
|
def test_solid_floor():
|
|
assert math.isclose(
|
|
recommendation_utils.get_floor_u_value(
|
|
'solid', 100, 40, 'A', 'stone', insulation_thickness="20mm"
|
|
),
|
|
0.4, rel_tol=1e-2)
|
|
|
|
|
|
def test_suspended_floor():
|
|
assert math.isclose(
|
|
recommendation_utils.get_floor_u_value(
|
|
'suspended', 100, 40, 'A', 'timber frame', insulation_thickness="20mm"
|
|
),
|
|
0.49, rel_tol=1e-2)
|
|
|
|
|
|
def test_invalid_floor_type():
|
|
with pytest.raises(ValueError):
|
|
recommendation_utils.get_floor_u_value(
|
|
'invalid_type', 100, 40, 'A', 'stone', insulation_thickness="20mm"
|
|
)
|
|
|
|
|
|
def test_park_home():
|
|
assert recommendation_utils.get_floor_u_value(
|
|
'suspended', 100, 40, 'A', 'park home', insulation_thickness="20mm"
|
|
) == 0
|