diff --git a/backend/Property.py b/backend/Property.py index 5b241bdc..c1d6324a 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -217,6 +217,9 @@ class Property: self.eco4_eligibility = None self.whlg_eligibility = None + # Ventilation + self.has_ventilation = self.identify_ventilation() + @classmethod def extract_kwargs(cls, kwargs): """ @@ -1349,3 +1352,12 @@ class Property: self.gbis_eligibiltiy = funding_calulator.gbis_eligibiltiy self.eco4_eligibility = funding_calulator.eco4_eligibility self.whlg_eligibility = funding_calulator.whlg_eligibility + + def identify_ventilation(self): + + ventilation_descriptions = [ + 'mechanical, extract only', + 'mechanical, supply and extract' + ] + + return self.data["mechanical-ventilation"] in ventilation_descriptions diff --git a/backend/app/assumptions.py b/backend/app/assumptions.py index 95271903..7dd986ed 100644 --- a/backend/app/assumptions.py +++ b/backend/app/assumptions.py @@ -58,6 +58,11 @@ DESCRIPTIONS_TO_FUEL_TYPES = { "Room heaters, wood logs": {"fuel": "Wood Logs", "cop": 1}, "Boiler and radiators, coal": {"fuel": "Coal", "cop": 0.85}, "From main system, no cylinderstat": {"fuel": "Natural Gas", "cop": 0.85}, + "Room heaters, coal": {"fuel": "Coal", "cop": 0.85}, + "Electric underfloor heating, Electric storage heaters": {"fuel": "Electricity", "cop": 1}, + 'Room heaters, electric, Boiler and radiators, mains gas': {"fuel": "Natural Gas", "cop": 0.85}, + 'Boiler and radiators, mains gas, Boiler and radiators, mains gas': {"fuel": "Natural Gas", "cop": 0.85}, + 'Room heaters, electric, Electric storage heaters': {"fuel": "Electricity", "cop": 1}, } # These are the measure types where if there is a ventilation recommendation, we force the inclusion of it diff --git a/backend/engine/engine.py b/backend/engine/engine.py index e2712e8d..11701bdb 100644 --- a/backend/engine/engine.py +++ b/backend/engine/engine.py @@ -727,7 +727,8 @@ async def model_engine(body: PlanTriggerRequest): # Additionally, if we have required measures, they should also be included. Therefore # we can discount the number of points required to get to the target SAP band (or increase) # in the case of ventilation - needs_ventilation = any(x in property_measure_types for x in assumptions.measures_needing_ventilation) + needs_ventilation = any( + x in property_measure_types for x in assumptions.measures_needing_ventilation) and not p.has_ventilation input_measures = prepare_input_measures(measures_to_optimise, body.goal, needs_ventilation) @@ -771,6 +772,10 @@ async def model_engine(body: PlanTriggerRequest): epc_to_sap_lower_bound(body.goal_value) - current_sap_points ) - fixed_gain + if body.simulate_sap_10: + # We add 3 additional SAP points to the required gain to account for SAP 10 + sap_gain += 3 + if not body.optimise: if body.goal != "Increasing EPC": raise NotImplementedError("Only EPC optimisation is currently supported") @@ -825,7 +830,11 @@ async def model_engine(body: PlanTriggerRequest): ) # If wall insulation is selected, we also include mechanical ventilation as a best practice measure - if any(x in [r["type"] for r in solution] for x in assumptions.measures_needing_ventilation): + ventilation_selected = [ + r for r in solution if "+mechanical_ventilation" in r["type"] + ] + if (any(x in [r["type"] for r in solution] for x in assumptions.measures_needing_ventilation) or + len(ventilation_selected)): ventilation_rec = next( (r[0] for r in recommendations[p.id] if r[0]["type"] == "mechanical_ventilation"), None diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py index 9c44fe4a..576b545b 100644 --- a/recommendations/HeatingRecommender.py +++ b/recommendations/HeatingRecommender.py @@ -74,6 +74,14 @@ class HeatingRecommender: "controls_suffix": "" }, "dual": None + }, + "Room heaters, electric, electric storage heaters": { + "hhr": { + "mainheating_description": "Electric storage heaters, radiators", + "recommendation_description": "Install high heat retention electric storage heaters.", + "controls_prefix": "" + }, + "dual": None } } @@ -186,7 +194,8 @@ class HeatingRecommender: ) and (not ashp_only_heating_recommendation) and ("boiler_upgrade" in measures) and - (not self.has_ashp) + (not self.has_ashp) and + (not self.property.main_heating["has_warm_air"]) ) return is_valid, has_gas_boiler diff --git a/recommendations/VentilationRecommendations.py b/recommendations/VentilationRecommendations.py index a82e4df5..31c4d023 100644 --- a/recommendations/VentilationRecommendations.py +++ b/recommendations/VentilationRecommendations.py @@ -10,11 +10,6 @@ class VentilationRecommendations(Definitions): crucial for prevent overheating risks in warmer months """ - VENTILATION_DESCRIPTIONS = [ - 'mechanical, extract only', - 'mechanical, supply and extract' - ] - def __init__( self, property_instance: Property, @@ -26,9 +21,6 @@ class VentilationRecommendations(Definitions): self.recommendation = None self.materials = [part for part in materials if part["type"] == "mechanical_ventilation"] - def identify_ventilation(self): - self.has_ventilaion = self.property.data["mechanical-ventilation"] in self.VENTILATION_DESCRIPTIONS - def recommend(self, phase): """ If there is no ventilation, we recommend installing ventilation @@ -38,8 +30,8 @@ class VentilationRecommendations(Definitions): :return: """ - self.identify_ventilation() - if self.has_ventilaion: + self.property.identify_ventilation() + if self.property.has_ventilaion: return if len(self.materials) != 1: diff --git a/recommendations/optimiser/optimiser_functions.py b/recommendations/optimiser/optimiser_functions.py index 05b9ec42..6909a3f0 100644 --- a/recommendations/optimiser/optimiser_functions.py +++ b/recommendations/optimiser/optimiser_functions.py @@ -47,19 +47,19 @@ def prepare_input_measures(property_recommendations, goal, needs_ventilation): # We bundle the impact of ventilation with the measure total = ( rec["total"] + ventilation_recommendation["total"] - if rec["type"] in assumptions.measures_needing_ventilation + if rec["type"] in assumptions.measures_needing_ventilation and needs_ventilation else rec["total"] ) gain = ( rec[goal_key] + ventilation_recommendation[goal_key] - if rec["type"] in assumptions.measures_needing_ventilation + if rec["type"] in assumptions.measures_needing_ventilation and needs_ventilation else rec[goal_key] ) rec_type = ( "+".join( [rec["type"], ventilation_recommendation["type"]] - ) if rec["type"] in assumptions.measures_needing_ventilation + ) if rec["type"] in assumptions.measures_needing_ventilation and needs_ventilation else rec["type"] )