diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml
new file mode 100644
index 00000000..7ef04e2e
--- /dev/null
+++ b/.idea/copilot.data.migration.ask.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml
new file mode 100644
index 00000000..8648f940
--- /dev/null
+++ b/.idea/copilot.data.migration.edit.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/recommendations/RoofRecommendations.py b/recommendations/RoofRecommendations.py
index 1d6fe06c..6625aeb0 100644
--- a/recommendations/RoofRecommendations.py
+++ b/recommendations/RoofRecommendations.py
@@ -170,7 +170,13 @@ class RoofRecommendations:
@staticmethod
def is_loft_insulation_appropriate(
- non_invasive_recommendations, measures, is_pitched, is_at_rafters, rir_over_loft
+ measures: List,
+ is_pitched: bool,
+ is_at_rafters: bool,
+ rir_over_loft: bool,
+ is_assumed: bool,
+ has_loft_insulation_recommendation: bool,
+ has_sloping_ceiling_recommendation: bool
) -> bool:
"""
Determine if loft insulation is appropriate
@@ -179,36 +185,59 @@ class RoofRecommendations:
:param is_pitched: Boolean - indicates whether or not the roof is pitched
:param is_at_rafters: Boolean - indicates whether or not the loft insulation is at rafters
:param rir_over_loft: Boolean - indicates whether or not there we should be doing RIR insulation
+ :param is_assumed: Boolean - indicates whether or not the roof insulation status is assumed
+ :param has_loft_insulation_recommendation: Boolean - indicates whether or not there
+ is a loft insulation non-invasive recommendation
+ :param has_sloping_ceiling_recommendation: Boolean - indicates whether or not there
+ is a sloping ceiling non-invasive recommendation
:return:
"""
has_li_in_measures = "loft_insulation" in measures
- has_li_non_invasive_recommendation = any(
- x["type"] == "loft_insulation" for x in non_invasive_recommendations
- )
- return has_li_non_invasive_recommendation or (
+ # Key business logic:
+ # If we have a pitched roof, no insulation, it's not assumed and we have a sloping ceiling recommendation,
+ # we do NOT recommend loft insulation
+ if is_pitched and not is_assumed and has_sloping_ceiling_recommendation:
+ return False
+
+ return has_loft_insulation_recommendation or (
is_pitched and has_li_in_measures and not is_at_rafters
) and not rir_over_loft
@staticmethod
def is_flat_roof_insulation_appropriate(
- is_flat: bool, measures: List, non_invasive_recommendations: List
+ is_flat: bool, measures: List, has_flat_roof_recommendation: bool
) -> bool:
"""
Determine if flat roof insulation is appropriate
:param is_flat: Boolean - indicates whether or not the roof is flat
:param measures: List - list of measures
- :param non_invasive_recommendations: List - list of non-invasive recommendations
- :return:
+ :param has_flat_roof_recommendation: Boolean - indicates whether or not there is a flat roof non-invasive
+ recommendation
+ :return: Boolean
"""
flat_roof_in_measures = "flat_roof_insulation" in measures
- flat_roof_non_invasive_rec = has_li_non_invasive_recommendation = any(
- x["type"] == "flat_roof_insulation" for x in non_invasive_recommendations
- )
- return (is_flat and flat_roof_in_measures) or flat_roof_non_invasive_rec
+ return (is_flat and flat_roof_in_measures) or has_flat_roof_recommendation
+
+ @staticmethod
+ def is_room_roof_insulation_appropriate(
+ is_room_roof, measures, rir_over_loft, has_room_roof_recommendation
+ ):
+ """
+ Determine if room roof insulation is appropriate
+ :param is_room_roof: Boolean - indicates whether or not the roof is a room roof
+ :param measures: List - list of measures
+ :param rir_over_loft: Boolean - indicates whether or not there we should be doing RIR insulation
+ :param has_room_roof_recommendation: Boolean - indicates whether or not there is a room roof non-invasive
+ recommendation
+ :return:
+ """
+ return is_room_roof and ("room_roof_insulation" in measures) or (
+ has_room_roof_recommendation or rir_over_loft
+ )
def _does_roof_need_recommendation(self, measures: List | None = None, u_value: float | None = None):
"""
@@ -243,6 +272,28 @@ class RoofRecommendations:
):
return False
+ @staticmethod
+ def _is_primary_roof_sloped(
+ is_pitched: bool, is_loft: bool, is_assumed: bool
+ ):
+ """
+ Determine if the primary roof is sloped
+ :param is_pitched: bool - is the roof pitched
+ :param is_loft: bool - is the roof a loft
+ :param is_assumed: bool - is the roof insulation status assumed
+ :return:
+ """
+ # Conditions for this to be true
+ # Case 1
+ # In the property roof description (primary roof)
+ # 1) Pitched Roof
+ # 2) Uninsulated
+ # 3) Not assumed
+ if is_pitched and not is_loft and not is_assumed:
+ return True
+
+ return False
+
def recommend(self, phase: int, measures: List | None = None, default_u_values: bool = False):
"""
Main method to recommend roof insulation measures
@@ -290,14 +341,21 @@ class RoofRecommendations:
is_loft = self.property.roof["is_loft"]
is_assumed = self.property.roof["is_assumed"]
is_at_rafters = self.property.roof["is_at_rafters"]
+ is_flat = self.property.roof["is_flat"]
+ is_room_roof = self.property.roof["is_roof_room"]
+
has_sloping_ceiling_recommendation = any(
x["type"] == "sloping_ceiling_insulation" for x in non_invasive_recommendations
)
- primary_roof_is_sloped = False # TODO
+ 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)
+ primary_roof_is_sloped = self._is_primary_roof_sloped(
+ is_pitched=is_pitched, is_loft=is_loft, is_assumed=is_assumed
+ )
rir_over_loft = (
- is_pitched and
- self.property.roof["insulation_thickness"] == "none" and
+ is_pitched and self.property.roof["insulation_thickness"] == "none" and
"room_in_roof_insulation" in [x["type"] for x in non_invasive_recommendations]
)
@@ -308,8 +366,19 @@ class RoofRecommendations:
)
needs_loft_insulation = self.is_loft_insulation_appropriate(
- non_invasive_recommendations=non_invasive_recommendations, measures=measures,
- is_pitched=is_pitched, is_at_rafters=is_at_rafters, rir_over_loft=rir_over_loft
+ measures=measures, is_pitched=is_pitched, is_at_rafters=is_at_rafters,
+ rir_over_loft=rir_over_loft, has_loft_insulation_recommendation=has_loft_insulation_recommendation,
+ is_assumed=is_assumed, has_sloping_ceiling_recommendation=has_sloping_ceiling_recommendation
+ )
+
+ needs_flat_roof_insulation = self.is_flat_roof_insulation_appropriate(
+ is_flat=is_flat, measures=measures, has_flat_roof_recommendation=has_flat_roof_recommendation
+ )
+ needs_rir_insulation = self.is_room_roof_insulation_appropriate(
+ is_room_roof=is_room_roof,
+ measures=measures,
+ rir_over_loft=rir_over_loft,
+ has_room_roof_recommendation=has_room_roof_recommendation
)
##################################################
@@ -327,10 +396,7 @@ class RoofRecommendations:
)
return
- if (
- (self.property.roof["is_flat"] and "flat_roof_insulation" in measures) or
- "flat_roof_insulation" in [x["type"] for x in non_invasive_recommendations]
- ):
+ if needs_flat_roof_insulation:
self.recommend_roof_insulation(
u_value=u_value,
insulation_thickness=0,
@@ -343,10 +409,7 @@ class RoofRecommendations:
# There are cases where the property might have a room roof as the second roof, but we have a recommendation for
# it, so we allow this override
- 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] or
- rir_over_loft
- ):
+ if needs_rir_insulation:
self.recommend_room_roof_insulation(u_value, phase, default_u_values)
return