diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index d5555d0b..6171ddbf 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -130,6 +130,16 @@ async def trigger_plan(body: PlanTriggerRequest): # with open("new_sap_dataset.pickle", "rb") as f: # new_sap_dataset = pickle.load(f) + # import pickle + # with open("cleaned.pickle", "rb") as f: + # cleaned = pickle.dump(f) + + # with open("sap_dataset.pickle", "rb") as f: + # sap_dataset = pickle.load(f) + + # with open("materials_by_type", "rb") as f: + # materials_by_type = pickle.load(f) + recommendations = {} recommendations_scoring_data = [] diff --git a/recommendations/RoofRecommendations.py b/recommendations/RoofRecommendations.py index 5a2a6bcf..06eab302 100644 --- a/recommendations/RoofRecommendations.py +++ b/recommendations/RoofRecommendations.py @@ -1,10 +1,10 @@ import math -from backend import Property +from backend.Property import Property from typing import List from datatypes.enums import QuantityUnits from recommendations.recommendation_utils import ( get_roof_u_value, r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns, - update_lowest_selected_u_value, get_recommended_part + update_lowest_selected_u_value, get_recommended_part, convert_thickness_to_numeric ) @@ -35,10 +35,13 @@ class RoofRecommendations: def recommend(self): u_value = self.property.roof["thermal_transmittance"] - insulation_thickness = self.property.walls["insulation_thickness"] + insulation_thickness = convert_thickness_to_numeric(self.property.roof["insulation_thickness"]) # We check if the roof is already insulated and if so, we exit - if insulation_thickness in ["average", "above average"]: + + # Building regulations part L recommend installing at least 270mm of insulation, however generally we + # experience diminishing returns in terms of SAP once we go beyond around 150mm of insulation + if insulation_thickness < 270: return # If we have a u-value already, need to implement this diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py index 4f0813ab..67550fc8 100644 --- a/recommendations/recommendation_utils.py +++ b/recommendations/recommendation_utils.py @@ -534,3 +534,29 @@ def calculate_r_value_per_mm(thickness_mm, thermal_conductivity_w_mK): r_value_per_mm = r_value_m2k_w / thickness_mm return r_value_per_mm + + +def convert_thickness_to_numeric(string_thickness): + """ + Roof insulation thickness could be a string like "None", "300mm+" or a numeric string. + This function will convert these strings to a number for easy usage + :param string_thickness: string measure of insulation thickness + :return: integer measure of insulation thickness + """ + + lookup = { + "none": 0, + "below average": 50, + "average": 100, + "above average": 270 + } + + mapped = lookup.get(string_thickness) + + if mapped is not None: + return mapped + + if "+" in string_thickness: + return int(string_thickness.replace("+", "")) + + return int(string_thickness) diff --git a/recommendations/tests/test_fireplace_recommendations.py b/recommendations/tests/test_fireplace_recommendations.py new file mode 100644 index 00000000..a1e0c1c6 --- /dev/null +++ b/recommendations/tests/test_fireplace_recommendations.py @@ -0,0 +1,58 @@ +from backend.Property import Property +from unittest.mock import Mock +from recommendations.FireplaceRecommendations import FireplaceRecommendations + + +class TestFirepaceRecommendations: + + def test_no_fireplaces(self): + property_instance = Property(id=0, address1="fake", postcode="fake", epc_client=Mock()) + property_instance.data = { + "number-open-fireplaces": 0 + } + + recommender = FireplaceRecommendations( + property_instance=property_instance + ) + + assert recommender.recommendation is None + + recommender.recommend() + + assert recommender.recommendation is None + + def test_one_fireplace(self): + property_instance = Property(id=0, address1="fake", postcode="fake", epc_client=Mock()) + property_instance.data = { + "number-open-fireplaces": 1 + } + + recommender = FireplaceRecommendations( + property_instance=property_instance + ) + + assert recommender.recommendation is None + + recommender.recommend() + + assert recommender.recommendation + assert recommender.recommendation[0]["type"] == "sealing_open_fireplace" + assert recommender.recommendation[0]["cost"] == 300 + + def test_multiple_fireplaces(self): + property_instance = Property(id=0, address1="fake", postcode="fake", epc_client=Mock()) + property_instance.data = { + "number-open-fireplaces": 3 + } + + recommender = FireplaceRecommendations( + property_instance=property_instance + ) + + assert recommender.recommendation is None + + recommender.recommend() + + assert recommender.recommendation + assert recommender.recommendation[0]["type"] == "sealing_open_fireplace" + assert recommender.recommendation[0]["cost"] == 900 diff --git a/recommendations/tests/test_recommendation_utils.py b/recommendations/tests/test_recommendation_utils.py index eb1a5024..dc98d946 100644 --- a/recommendations/tests/test_recommendation_utils.py +++ b/recommendations/tests/test_recommendation_utils.py @@ -277,6 +277,17 @@ class TestRecommendationUtils: insulation_thickness=None, ) + def test_convert_thickness_to_numeric(self): + + assert recommendation_utils.convert_thickness_to_numeric("none") == 0 + assert recommendation_utils.convert_thickness_to_numeric("below average") == 50 + assert recommendation_utils.convert_thickness_to_numeric("average") == 100 + assert recommendation_utils.convert_thickness_to_numeric("above average") == 270 + + assert recommendation_utils.convert_thickness_to_numeric("300+") == 300 + assert recommendation_utils.convert_thickness_to_numeric("400+") == 400 + assert recommendation_utils.convert_thickness_to_numeric("270") == 270 + def test_estimate_perimeter_regular_inputs(): assert math.isclose( diff --git a/recommendations/tests/test_roof_recommendations.py b/recommendations/tests/test_roof_recommendations.py new file mode 100644 index 00000000..b5507421 --- /dev/null +++ b/recommendations/tests/test_roof_recommendations.py @@ -0,0 +1,39 @@ +from backend.Property import Property +from unittest.mock import Mock +from recommendations.RoofRecommendations import RoofRecommendations + +loft_insulation_materials = [ + { + 'id': 18, 'type': 'loft_insulation', 'description': 'Iso Spacesaver Mineral Wool insulation', + 'depths': [270, 300], 'depth_unit': 'mm', 'cost': [9, 10], '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': 'https://flooringwarehousedirect.co.uk/product/isover-spacesaver-roll-100mm-x-1160mm-x-12-18m-14-13m2/', + 'is_active': True + } +] + + +class TestRoofRecommendations: + + def test_loft_insulation_recommendation_no_insulation(self): + property_instance = Property(id=0, address1="fake", postcode="fake", epc_client=Mock()) + property_instance.age_band = "F" + property_instance.floor_area = 100 + property_instance.roof = { + 'original_description': 'Pitched, no insulation (assumed)', + 'clean_description': 'Pitched, no 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': True, 'has_dwelling_above': False, 'is_valid': True, + 'insulation_thickness': 'none', 'roof_thermal_transmittance': None, 'roof_insulation_thickness': 'none' + } + + roof_recommender = RoofRecommendations(property_instance=property_instance, materials=loft_insulation_materials) + + assert not roof_recommender.recommendations + + roof_recommender.recommend() + + assert len(roof_recommender.recommendations)