From 81fc264afec0f1b128262a8590e46da3e3988b4b Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 28 Jan 2026 09:32:32 +0000 Subject: [PATCH] handling ambiguous cases for sloping ceiling vs loft insulation --- recommendations/RoofRecommendations.py | 15 ++++- .../tests/test_roof_recommendations.py | 61 ++++++++++++++++++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/recommendations/RoofRecommendations.py b/recommendations/RoofRecommendations.py index bcaea687..578173bb 100644 --- a/recommendations/RoofRecommendations.py +++ b/recommendations/RoofRecommendations.py @@ -122,12 +122,16 @@ class RoofRecommendations: @staticmethod def is_sloping_ceiling_appropriate( - is_pitched: bool, is_loft: bool, is_assumed: bool, has_sloping_ceiling_recommendation: bool, - primary_roof_is_sloped: bool, insulation_thickness: str + is_pitched: bool, is_loft: bool, is_assumed: bool, + is_flat: bool, + has_sloping_ceiling_recommendation: bool, + primary_roof_is_sloped: bool, insulation_thickness: str, + has_loft_insulation_recommendation: bool ) -> bool: """ :param is_pitched: Boolean - indicates whether or not the roof is pitched + :param is_flat: Boolean - indicates whether or not the roof is flat :param is_loft: Boolean - indicates whether or not the roof is described as a loft :param is_assumed: Boolean - indiates if the assessment of the roof is assumed or actually confirmed :param has_sloping_ceiling_recommendation: Boolean - indicates if the property has a sloping ceiling @@ -135,6 +139,7 @@ class RoofRecommendations: :param primary_roof_is_sloped: Boolean - indicates if the primary room is described a sloped (as opposed to an extension) :param insulation_thickness: String - insulation thickness of the roof + :param has_loft_insulation_recommendation: Boolean - indicates whether or not there :return: """ # We need to check: @@ -166,6 +171,11 @@ class RoofRecommendations: if has_suitable_features and needs_recommendation: return True + # In this case, we have an assumed pitched roof with average or below average insulation + # but a sloping ceiling insulation without loft + if has_sloping_ceiling_recommendation and not has_loft_insulation_recommendation and not is_flat: + return True + return False @staticmethod @@ -504,6 +514,7 @@ class RoofRecommendations: has_loft_insulation_recommendation = any(x["type"] == "loft_insulation" for x in non_invasive_recommendations) has_flat_roof_recommendation = any(x["type"] == "flat_roof_insulation" for x in non_invasive_recommendations) has_room_roof_recommendation = any(x["type"] == "room_roof_insulation" for x in non_invasive_recommendations) + # Very naive condition primary_roof_is_sloped = self._is_primary_roof_sloped( is_pitched=is_pitched, is_loft=is_loft, is_assumed=is_assumed ) diff --git a/recommendations/tests/test_roof_recommendations.py b/recommendations/tests/test_roof_recommendations.py index 48e96af7..f022f9b7 100644 --- a/recommendations/tests/test_roof_recommendations.py +++ b/recommendations/tests/test_roof_recommendations.py @@ -1,7 +1,6 @@ import pytest from unittest.mock import Mock from backend.Property import Property -from etl.customers.immo.pilot.asset_list import non_invasive_recommendations from etl.epc.Record import EPCRecord from recommendations.RoofRecommendations import RoofRecommendations from recommendations.tests.test_data.materials import materials @@ -711,3 +710,63 @@ class TestRoofRecommendations: assert len(roof_recommender.recommendations) == 3 # should all be loft insulation recommendations assert all(rec["type"] == "loft_insulation" for rec in roof_recommender.recommendations) + + def sloping_ceiling_limited_insulation(self): + property_instance = Mock( + id=0, + roof={ + "original_description": 'Pitched, limited insulation (assumed)', + 'clean_description': 'Pitched, limited 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': 'below average' + }, + roof_area=35, + data={"county": None, "local-authority-label": "Manchester"}, + already_installed=[], + find_my_epc_components=[ + {'component_name': 'Wall', 'description': 'Cavity wall, as built, no insulation (assumed)', + 'efficiency': 'poor', 'appearance_index': 0}, + {'component_name': 'Roof', 'description': 'Pitched, limited insulation (assumed)', + 'efficiency': 'Very poor', 'appearance_index': 0}, + {'component_name': 'Window', 'description': 'Fully double glazed', 'efficiency': 'Average', + 'appearance_index': 0}, + {'component_name': 'Main heating', 'description': 'Boiler and radiators, mains gas', + 'efficiency': 'Good', 'appearance_index': 0}, + {'component_name': 'Main heating control', 'description': 'TRVs and bypass', + 'efficiency': 'Average', 'appearance_index': 0}, + {'component_name': 'Hot water', 'description': 'From main system', 'efficiency': 'Good', + 'appearance_index': 0}, + {'component_name': 'Lighting', 'description': 'Low energy lighting in all fixed outlets', + 'efficiency': 'Very good', 'appearance_index': 0}, + {'component_name': 'Floor', 'description': '(another dwelling below)', 'efficiency': 'N/A', + 'appearance_index': 0}, + {'component_name': 'Secondary heating', 'description': 'None', 'efficiency': 'N/A', + 'appearance_index': 0} + ], + age_band="B", + non_invasive_recommendations=[ + {'type': 'sloping_ceiling_insulation', 'sap_points': 2, 'survey': True}, + {'type': 'flat_roof_insulation', 'sap_points': 2, 'survey': True}, + ], + ) + + # We expect a sloping ceiling insulation recommendation + roof_recommender = RoofRecommendations(property_instance=property_instance, materials=materials) + assert not roof_recommender.recommendations + + roof_recommender.recommend(phase=0) + assert len(roof_recommender.recommendations) == 1 + assert roof_recommender.recommendations[0]["type"] == "sloping_ceiling_insulation" + assert roof_recommender.recommendations[0]["measure_type"] == "sloping_ceiling_insulation" + assert roof_recommender.recommendations[0]["description"] == \ + "Insulate sloping ceilings at the rafters and re-decorate" + assert roof_recommender.recommendations[0]["simulation_config"] == { + 'roof_insulation_thickness_ending': 'average', + 'roof_thermal_transmittance_ending': 0.5, + 'roof_energy_eff_ending': 'Average' + } + assert roof_recommender.recommendations[0]["description_simulation"] == { + 'roof-description': 'Pitched, insulated', 'roof-energy-eff': 'Average' + }