Merge pull request #341 from Hestia-Homes/retrofit-assessmet-api

Retrofit assessmet api
This commit is contained in:
KhalimCK 2024-09-09 22:35:31 +01:00 committed by GitHub
commit 213a9cf533
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 161 additions and 44 deletions

View file

@ -71,6 +71,10 @@ def get_latest_assessment_by_uprn(session: Session, uprn: int) -> Optional[Energ
:param uprn: The unique property reference number :param uprn: The unique property reference number
:return: The latest EnergyAssessment object or None if not found :return: The latest EnergyAssessment object or None if not found
""" """
if not uprn:
return EnergyAssessment.empty_response()
try: try:
# Query the EnergyAssessment model, filter by uprn, order by inspection_date in descending order # Query the EnergyAssessment model, filter by uprn, order by inspection_date in descending order
latest_assessment = session.query(EnergyAssessment).filter_by(uprn=uprn).order_by( latest_assessment = session.query(EnergyAssessment).filter_by(uprn=uprn).order_by(

View file

@ -11,7 +11,8 @@ from backend.app.db.models.portfolio import (
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
def create_property(session: Session, portfolio_id: int, address: str, postcode: str, uprn: str) -> (int, bool): def create_property(session: Session, portfolio_id: int, address: str, postcode: str, uprn: str,
energy_assessment: dict) -> (int, bool):
""" """
This function will create a record for the property in the database if it does not exist. This function will create a record for the property in the database if it does not exist.
If it does exist, it will just update the updated_at field. If it does exist, it will just update the updated_at field.
@ -39,13 +40,17 @@ def create_property(session: Session, portfolio_id: int, address: str, postcode:
except NoResultFound: except NoResultFound:
# Property doesn't exist, create a new one # Property doesn't exist, create a new one
status = PortfolioStatus.ASSESSMENT.value if len(energy_assessment["epc"]) == 0 \
else PortfolioStatus.SURVEY.value
new_property = PropertyModel( new_property = PropertyModel(
address=address, address=address,
postcode=postcode, postcode=postcode,
portfolio_id=portfolio_id, portfolio_id=portfolio_id,
uprn=uprn, uprn=uprn,
creation_status=PropertyCreationStatus.LOADING, creation_status=PropertyCreationStatus.LOADING,
status=PortfolioStatus.ASSESSMENT.value, status=status,
has_pre_condition_report=False, has_pre_condition_report=False,
has_recommendations=False has_recommendations=False
) )

View file

@ -11,6 +11,7 @@ Base = declarative_base()
class PortfolioStatus(enum.Enum): class PortfolioStatus(enum.Enum):
SCOPING = "scoping" SCOPING = "scoping"
ASSESSMENT = "assessment" ASSESSMENT = "assessment"
SURVEY = "survey"
TENDERING = "tendering" TENDERING = "tendering"
PROJECT_UNDERWAY = "project underway" PROJECT_UNDERWAY = "project underway"
COMPLETION_ON_TRACK = "completion; status: on track" COMPLETION_ON_TRACK = "completion; status: on track"

View file

@ -235,10 +235,14 @@ def create_epc_records(epc_searcher: SearchEpc, energy_assessment: dict):
EnergyAssessment.empty_response() method EnergyAssessment.empty_response() method
""" """
newest_epc = epc_searcher.newest_epc.copy()
if newest_epc["uprn"] == "" and epc_searcher.uprn:
newest_epc["uprn"] = epc_searcher.uprn
if not energy_assessment["epc"]: if not energy_assessment["epc"]:
energy_assessment_is_newer = False energy_assessment_is_newer = False
return { return {
'original_epc': epc_searcher.newest_epc.copy(), 'original_epc': newest_epc,
'full_sap_epc': epc_searcher.full_sap_epc.copy(), 'full_sap_epc': epc_searcher.full_sap_epc.copy(),
'old_data': epc_searcher.older_epcs.copy(), 'old_data': epc_searcher.older_epcs.copy(),
}, energy_assessment_is_newer }, energy_assessment_is_newer
@ -249,22 +253,22 @@ def create_epc_records(epc_searcher: SearchEpc, energy_assessment: dict):
# We insert county into the epc, since right now this isn't something that we pull out from the energy # We insert county into the epc, since right now this isn't something that we pull out from the energy
# assessment # assessment
for col in ["county", "constituency", "constituency-label", "local-authority", "local-authority-label"]: for col in ["county", "constituency", "constituency-label", "local-authority", "local-authority-label"]:
epc[col] = epc_searcher.newest_epc[col] epc[col] = newest_epc[col]
# We check if the energy assessment is newer than the newest EPC # We check if the energy assessment is newer than the newest EPC
if pd.to_datetime(energy_assessment_date) > pd.to_datetime(epc_searcher.newest_epc["inspection-date"]): if pd.to_datetime(energy_assessment_date) > pd.to_datetime(newest_epc["inspection-date"]):
# In this case, our energy assessment is newer than the EPCs available for this property # In this case, our energy assessment is newer than the EPCs available for this property
energy_assessment_is_newer = True energy_assessment_is_newer = True
return { return {
"original_epc": epc, "original_epc": epc,
"full_sap_epc": epc_searcher.full_sap_epc.copy(), "full_sap_epc": epc_searcher.full_sap_epc.copy(),
"old_data": epc_searcher.older_epcs.copy() + [epc_searcher.newest_epc.copy()] "old_data": epc_searcher.older_epcs.copy() + [newest_epc]
}, energy_assessment_is_newer }, energy_assessment_is_newer
# We check if the EPC we have produced is contained in the set of EPCs done for the property # We check if the EPC we have produced is contained in the set of EPCs done for the property
# We do this based on inspection-date and SAP # We do this based on inspection-date and SAP
epc_in_historicals = [ epc_in_historicals = [
x for x in epc_searcher.older_epcs + [epc_searcher.newest_epc] x for x in epc_searcher.older_epcs + [newest_epc]
if x["inspection-date"] == energy_assessment_date and if x["inspection-date"] == energy_assessment_date and
x["current-energy-efficiency"] == epc["current-energy-efficiency"] x["current-energy-efficiency"] == epc["current-energy-efficiency"]
] ]
@ -273,7 +277,7 @@ def create_epc_records(epc_searcher: SearchEpc, energy_assessment: dict):
if epc_in_historicals: if epc_in_historicals:
# Then the EPC we have produced is already in the set of EPCs, and our EPC is older than the newest # Then the EPC we have produced is already in the set of EPCs, and our EPC is older than the newest
return { return {
"original_epc": epc_searcher.newest_epc.copy(), "original_epc": newest_epc,
"full_sap_epc": epc_searcher.full_sap_epc.copy(), "full_sap_epc": epc_searcher.full_sap_epc.copy(),
"old_data": epc_searcher.older_epcs.copy() "old_data": epc_searcher.older_epcs.copy()
}, energy_assessment_is_newer }, energy_assessment_is_newer
@ -281,7 +285,7 @@ def create_epc_records(epc_searcher: SearchEpc, energy_assessment: dict):
# In this case, our EPC is older than the newest publically avaible one, but is not contained in # In this case, our EPC is older than the newest publically avaible one, but is not contained in
# the historicals, so it can't have been lodged, so we include it in the old data # the historicals, so it can't have been lodged, so we include it in the old data
return { return {
'original_epc': epc_searcher.newest_epc.copy(), 'original_epc': newest_epc,
'full_sap_epc': epc_searcher.full_sap_epc.copy(), 'full_sap_epc': epc_searcher.full_sap_epc.copy(),
'old_data': epc_searcher.older_epcs.copy() + [epc], 'old_data': epc_searcher.older_epcs.copy() + [epc],
}, energy_assessment_is_newer }, energy_assessment_is_newer
@ -412,7 +416,8 @@ async def trigger_plan(body: PlanTriggerRequest):
# Create a record in db # Create a record in db
property_id, is_new = create_property( property_id, is_new = create_property(
session, body.portfolio_id, epc_searcher.address_clean, epc_searcher.postcode_clean, epc_searcher.uprn session, body.portfolio_id, epc_searcher.address_clean, epc_searcher.postcode_clean, epc_searcher.uprn,
energy_assessment
) )
if not is_new and not body.multi_plan: if not is_new and not body.multi_plan:
continue continue
@ -799,6 +804,15 @@ async def trigger_plan(body: PlanTriggerRequest):
if ventilation_rec: if ventilation_rec:
selected_recommendations.add(ventilation_rec["recommendation_id"]) selected_recommendations.add(ventilation_rec["recommendation_id"])
# If we have a trickle vents recommendation, we also switch it on. We don't just check the solution
trickle_vents_rec = next(
(r[0] for r in recommendations[p.id] if r[0]["type"] == "trickle_vents"),
None
)
# If a matching recommendation was found, add its ID to the selected recommendations
if trickle_vents_rec:
selected_recommendations.add(trickle_vents_rec["recommendation_id"])
# We'll use the set of selected recommendations to filter the recommendations to upload # We'll use the set of selected recommendations to filter the recommendations to upload
final_recommendations = [ final_recommendations = [
[ [

View file

@ -13,7 +13,7 @@ def app():
"surveyor": "JAFFERSONS ENERGY CONSULTANTS", "surveyor": "JAFFERSONS ENERGY CONSULTANTS",
"project_code": "VEC001", "project_code": "VEC001",
} }
# 5 Grove Mansions
# These are the recommendations based on the on-site survey of the property. # These are the recommendations based on the on-site survey of the property.
non_intrusive_recommendations = [ non_intrusive_recommendations = [
{ {
@ -22,17 +22,17 @@ def app():
"recommendations": [ "recommendations": [
{ {
"type": "draught_proofing", "type": "draught_proofing",
"cost": 123, "cost": 100,
"survey": True, "survey": True,
"sap_points": 1 "sap_points": 1
}, },
{ {
"type": "mixed_glazing", "cost": 12345, "survey": True, "type": "mixed_glazing", "cost": 14632, "survey": True,
"description": "Install double glazing to north facing windows and secondary glazing to the " "description": "Install double glazing to north facing windows and secondary glazing to the "
"remaining windows at the front of the building", "remaining windows at the front of the building",
"sap_points": 3 "sap_points": 3
}, },
{"type": "trickle_vents", "cost": 500, "survey": True}, {"type": "trickle_vents", "cost": 1000, "survey": True},
{"type": "suspended_floor_insulation", "cost": None, "survey": True, "sap_points": 2}, {"type": "suspended_floor_insulation", "cost": None, "survey": True, "sap_points": 2},
{"type": "internal_wall_insulation", "cost": None, "survey": True, "sap_points": 5}, {"type": "internal_wall_insulation", "cost": None, "survey": True, "sap_points": 5},
] ]
@ -41,14 +41,14 @@ def app():
# 8 Grove Mansions # 8 Grove Mansions
"uprn": 10024087855, "uprn": 10024087855,
"recommendations": [ "recommendations": [
{"type": "draught_proofing", "cost": 123, "survey": True, "sap_points": 2}, {"type": "draught_proofing", "cost": 100, "survey": True, "sap_points": 2},
{ {
"type": "mixed_glazing", "cost": 12345, "survey": True, "type": "mixed_glazing", "cost": 7814, "survey": True,
"description": "Install double glazing to north facing windows and secondary glazing to the " "description": "Install double glazing to north facing windows and secondary glazing to the "
"remaining windows at the front of the building", "remaining windows at the front of the building",
"sap_points": 4 "sap_points": 4
}, },
{"type": "trickle_vents", "cost": 500, "survey": True}, {"type": "trickle_vents", "cost": 700, "survey": True},
{"type": "low_energy_lighting", "cost": None, "survey": True, "sap_points": 0}, {"type": "low_energy_lighting", "cost": None, "survey": True, "sap_points": 0},
{"type": "internal_wall_insulation", "cost": None, "survey": True, 'sap_points': 5}, {"type": "internal_wall_insulation", "cost": None, "survey": True, 'sap_points': 5},
] ]
@ -57,14 +57,14 @@ def app():
# 9 Grove Mansions # 9 Grove Mansions
"uprn": 121016128, "uprn": 121016128,
"recommendations": [ "recommendations": [
{"type": "draught_proofing", "cost": 123, "survey": True, "sap_points": 1}, {"type": "draught_proofing", "cost": 100, "survey": True, "sap_points": 1},
{ {
"type": "mixed_glazing", "cost": 12345, "survey": True, "type": "mixed_glazing", "cost": 9740, "survey": True,
"description": "Install double glazing to north facing windows and secondary glazing to the " "description": "Install double glazing to north facing windows and secondary glazing to the "
"remaining windows at the front of the building", "remaining windows at the front of the building",
"sap_points": 3 "sap_points": 3
}, },
{"type": "trickle_vents", "cost": 500, "survey": True}, {"type": "trickle_vents", "cost": 1000, "survey": True},
{"type": "low_energy_lighting", "cost": None, "survey": True, "sap_points": 1}, {"type": "low_energy_lighting", "cost": None, "survey": True, "sap_points": 1},
{"type": "suspended_floor_insulation", "cost": None, "sap_points": 1}, {"type": "suspended_floor_insulation", "cost": None, "sap_points": 1},
{"type": "internal_wall_insulation", "cost": None, "survey": True, "sap_points": 6}, {"type": "internal_wall_insulation", "cost": None, "survey": True, "sap_points": 6},
@ -75,12 +75,12 @@ def app():
"uprn": 121016124, "uprn": 121016124,
"recommendations": [ "recommendations": [
{ {
"type": "mixed_glazing", "cost": 12345, "survey": True, "type": "mixed_glazing", "cost": 12662, "survey": True,
"description": "Install double glazing to north facing windows and secondary glazing to the " "description": "Install double glazing to north facing windows and secondary glazing to the "
"remaining windows at the front of the building", "remaining windows at the front of the building",
"sap_points": 5 "sap_points": 5
}, },
{"type": "trickle_vents", "cost": 500, "survey": True}, {"type": "trickle_vents", "cost": 1300, "survey": True},
{"type": "low_energy_lighting", "cost": None, "survey": True, "sap_points": 2}, {"type": "low_energy_lighting", "cost": None, "survey": True, "sap_points": 2},
{"type": "internal_wall_insulation", "cost": None, "survey": True, "sap_points": 8}, {"type": "internal_wall_insulation", "cost": None, "survey": True, "sap_points": 8},
] ]
@ -89,14 +89,14 @@ def app():
# 14 Grove Mansions # 14 Grove Mansions
"uprn": 121016117, "uprn": 121016117,
"recommendations": [ "recommendations": [
{"type": "draught_proofing", "cost": 123, "survey": True, "sap_points": 1}, {"type": "draught_proofing", "cost": 100, "survey": True, "sap_points": 1},
{ {
"type": "mixed_glazing", "cost": 12345, "survey": True, "type": "mixed_glazing", "cost": 10736, "survey": True,
"description": "Install double glazing to north facing windows and secondary glazing to the " "description": "Install double glazing to north facing windows and secondary glazing to the "
"remaining windows at the front of the building", "remaining windows at the front of the building",
"sap_points": 4 "sap_points": 4
}, },
{"type": "trickle_vents", "cost": 500, "survey": True}, {"type": "trickle_vents", "cost": 1000, "survey": True},
{"type": "low_energy_lighting", "cost": None, "survey": True, "sap_points": 1}, {"type": "low_energy_lighting", "cost": None, "survey": True, "sap_points": 1},
{"type": "internal_wall_insulation", "cost": None, "survey": True, "sap_points": 6}, {"type": "internal_wall_insulation", "cost": None, "survey": True, "sap_points": 6},
] ]
@ -113,6 +113,7 @@ def app():
] ]
asset_list = [ asset_list = [
# These are properties where we've done a survey
{ {
"uprn": 121016121, "address": "", "postcode": "" "uprn": 121016121, "address": "", "postcode": ""
}, },
@ -131,6 +132,63 @@ def app():
{ {
"uprn": 10024087902, "address": "", "postcode": "" "uprn": 10024087902, "address": "", "postcode": ""
}, },
# These properties we just model with default data
# Flat 1
{
"uprn": 121016113, "address": "", "postcode": ""
},
# Flat 10
{
"uprn": 121016114, "address": "", "postcode": ""
},
# Flat 11
{
"uprn": 121016115, "address": "", "postcode": ""
},
# Flat 12
{
"uprn": 121016116, "address": "", "postcode": ""
},
# Flat 15
{
"uprn": 121016118, "address": "", "postcode": ""
},
# Flat 16
{
"uprn": 121016119, "address": "", "postcode": ""
},
# Flat 17
{
"address": "Flat 17 Grove Mansions", "postcode": "SW4 9SL"
},
# Flat 18
{
"uprn": 10024087901, "address": "", "postcode": ""
},
# Flat 3
{
"uprn": 121016122, "address": "", "postcode": ""
},
# Flat 4
{
"uprn": 121016123, "address": "", "postcode": ""
},
# Flat 6
{
"uprn": 121016125, "address": "", "postcode": ""
},
# Flat 7
{
"uprn": 10024087854, "address": "", "postcode": ""
},
# Flat 7A
{
"uprn": 10024087840, "address": "", "postcode": ""
},
# Flat 8A
{
"uprn": 10024087841, "address": "", "postcode": ""
},
] ]
asset_list = pd.DataFrame(asset_list) asset_list = pd.DataFrame(asset_list)
@ -162,7 +220,7 @@ def app():
"patches_file_path": "", "patches_file_path": "",
"non_invasive_recommendations_file_path": non_invasive_recommendations_filename, "non_invasive_recommendations_file_path": non_invasive_recommendations_filename,
"inclusions": [ "inclusions": [
"draught_proofing", "mixed_glazing", "trickle_vents", "low_energy_lighting", "draught_proofing", "mixed_glazing", "trickle_vents", "low_energy_lighting", "windows"
], ],
"budget": None, "budget": None,
"scenario_name": "Quick wins - do now while tenanted", "scenario_name": "Quick wins - do now while tenanted",
@ -185,7 +243,9 @@ def app():
"trickle_vents", "trickle_vents",
"low_energy_lighting", "low_energy_lighting",
"suspended_floor_insulation", "suspended_floor_insulation",
"internal_wall_insulation" "internal_wall_insulation",
"room_roof_insulation",
"windows"
], ],
"budget": None, "budget": None,
"scenario_name": "Do when void", "scenario_name": "Do when void",

View file

@ -168,9 +168,8 @@ class Costs:
# https://www.greenmatch.co.uk/windows/double-glazing/cost # https://www.greenmatch.co.uk/windows/double-glazing/cost
SASH_WINDOW_INFLATION_FACTOR = 1.5 SASH_WINDOW_INFLATION_FACTOR = 1.5
# Typically, secondary glazing can be installed for 25% of the cost of double glazed windows - to be conservative, # Based on relative costs from SCIS
# we scale the cost by half SECONDARY_GLAZING_SCALING_FACTOR = 0.85
SECONDARY_GLAZING_SCALING_FACTOR = 0.5
def __init__(self, property_instance): def __init__(self, property_instance):
""" """

View file

@ -9,6 +9,9 @@ class LightingRecommendations:
# worth more than 2 points, but this is unlikely in the context of other upgrades that can be made to the property # worth more than 2 points, but this is unlikely in the context of other upgrades that can be made to the property
SAP_LIMIT = 2 SAP_LIMIT = 2
# If more than 50% of the lighting is LEDs already, the limit is 1 SAP point
SAP_LOWER_LIMIT = 1
def __init__(self, property_instance: Property, materials: List): def __init__(self, property_instance: Property, materials: List):
""" """
:param property_instance: Instance of the Property class, for the home associated to property_id :param property_instance: Instance of the Property class, for the home associated to property_id
@ -128,6 +131,7 @@ class LightingRecommendations:
"description_simulation": { "description_simulation": {
"lighting-energy-eff": "Very Good", "lighting-energy-eff": "Very Good",
"lighting-description": "Low energy lighting in all fixed outlets", "lighting-description": "Low energy lighting in all fixed outlets",
"low-energy-lighting": 100,
}, },
**cost_result, **cost_result,
"survey": leds_recommendation_config.get("survey", False) "survey": leds_recommendation_config.get("survey", False)

View file

@ -102,6 +102,7 @@ class Recommendations:
property_recommendations = [] property_recommendations = []
phase = 0 phase = 0
measures = self.find_included_measures() measures = self.find_included_measures()
non_invasive_recommendation_types = [r["type"] for r in self.property_instance.non_invasive_recommendations]
# Building Fabric # Building Fabric
self.wall_recomender.recommend(phase=phase, measures=measures) self.wall_recomender.recommend(phase=phase, measures=measures)
@ -149,7 +150,8 @@ class Recommendations:
property_recommendations.append(self.floor_recommender.recommendations) property_recommendations.append(self.floor_recommender.recommendations)
phase += 1 phase += 1
if "windows" in measures: if "windows" in measures and "mixed_glazing" not in non_invasive_recommendation_types:
# If we have a mixed glazing recommendation, we prioritise this over the windows recommendation
self.windows_recommender.recommend(phase=phase) self.windows_recommender.recommend(phase=phase)
if self.windows_recommender.recommendation: if self.windows_recommender.recommendation:
property_recommendations.append(self.windows_recommender.recommendation) property_recommendations.append(self.windows_recommender.recommendation)
@ -529,7 +531,12 @@ class Recommendations:
# For the moment, we cap the number of SAP points that can be achieved by LEDs at 2 # For the moment, we cap the number of SAP points that can be achieved by LEDs at 2
if rec["type"] == "low_energy_lighting": if rec["type"] == "low_energy_lighting":
property_phase_impact["sap"] = min(property_phase_impact["sap"], LightingRecommendations.SAP_LIMIT)
if property_instance.data["low-energy-lighting"] < 50:
lighting_sap_limit = LightingRecommendations.SAP_LIMIT
else:
lighting_sap_limit = LightingRecommendations.SAP_LOWER_LIMIT
property_phase_impact["sap"] = min(property_phase_impact["sap"], lighting_sap_limit)
property_phase_impact["carbon"] = min( property_phase_impact["carbon"] = min(
property_phase_impact["carbon"], rec["co2_equivalent_savings"] property_phase_impact["carbon"], rec["co2_equivalent_savings"]
) )

View file

@ -155,23 +155,44 @@ class RoofRecommendations:
) )
self.estimated_u_value = u_value self.estimated_u_value = u_value
if (u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE) or ( if (u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE) or all(
"loft_insulation" not in measures m not in measures for m in MEASURE_MAP["roof_insulation"]
): ):
# The Roof is already compliant # The Roof is already compliant
return return
if (self.property.roof["is_pitched"] and "loft_insulation" in measures) or ( non_invasive_recommendations = self.property.non_invasive_recommendations
self.property.roof["is_flat"] and "flat_roof_insulation" in measures
# We firstly handle non-intrusive recommendations, which may override the normal roof insulation recommendations
if ("loft_insulation" in [x["type"] for x in non_invasive_recommendations]) or (
self.property.roof["is_pitched"] and "loft_insulation" in measures
): ):
insulation_thickness = 0 if "loft_insulation" not in measures else self.insulation_thickness self.recommend_roof_insulation(
self.recommend_roof_insulation(u_value, insulation_thickness, self.property.roof, phase) u_value=u_value,
insulation_thickness=self.insulation_thickness,
phase=phase,
is_flat=False,
is_pitched=True
)
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]
):
self.recommend_roof_insulation(
u_value=u_value,
insulation_thickness=0,
phase=phase,
is_flat=True,
is_pitched=False
)
return return
# There are cases where the property might have a room roof as the second roof, but we have a recommendation for # 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 # it, so we allow this override
if self.property.roof["is_roof_room"] and ("room_roof_insulation" in measures) or ( if self.property.roof["is_roof_room"] and ("room_roof_insulation" in measures) or (
"room_roof_insulation" in [x["type"] for x in self.property.non_invasive_recommendations] "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)
return return
@ -195,7 +216,7 @@ class RoofRecommendations:
raise ValueError("Invalid material type") raise ValueError("Invalid material type")
def recommend_roof_insulation( def recommend_roof_insulation(
self, u_value, insulation_thickness, roof, phase self, u_value, insulation_thickness, phase, is_pitched, is_flat
): ):
""" """
@ -218,7 +239,9 @@ class RoofRecommendations:
:param u_value: U-value of the roof before any retrofit measures have been installed :param u_value: U-value of the roof before any retrofit measures have been installed
:param insulation_thickness: Existing Insulation thickness of the loft :param insulation_thickness: Existing Insulation thickness of the loft
:param roof: dictionary describing the make-up of the roof :param phase: Phase of the recommendation
:param is_pitched: Is the roof pitched
:param is_flat: Is the roof flat
:return: :return:
""" """
@ -226,10 +249,10 @@ class RoofRecommendations:
# Therefore the price is 100mm + whatever thickness is rolled on top, rolled at a 90 degree angle # Therefore the price is 100mm + whatever thickness is rolled on top, rolled at a 90 degree angle
# from the base layer # from the base layer
if roof["is_pitched"]: if is_pitched:
insulation_materials = self.loft_insulation_materials insulation_materials = self.loft_insulation_materials
non_insulation_materials = self.loft_non_insulation_materials non_insulation_materials = self.loft_non_insulation_materials
elif roof["is_flat"]: elif is_flat:
insulation_materials = self.flat_roof_insulation_materials insulation_materials = self.flat_roof_insulation_materials
non_insulation_materials = self.flat_roof_non_insulation_materials non_insulation_materials = self.flat_roof_non_insulation_materials
else: else:
@ -251,7 +274,7 @@ class RoofRecommendations:
# Note: This requirement is only for loft insulation # Note: This requirement is only for loft insulation
if ( if (
(material["depth"] + insulation_thickness) < self.MINIMUM_RECOMMENDED_LOFT_INSULATION (material["depth"] + insulation_thickness) < self.MINIMUM_RECOMMENDED_LOFT_INSULATION
) and roof["is_pitched"]: ) and is_pitched:
continue continue
part_u_value = r_value_per_mm_to_u_value(material["depth"], material["r_value_per_mm"]) part_u_value = r_value_per_mm_to_u_value(material["depth"], material["r_value_per_mm"])