From 91eb9c68f1600970541606fdae3869d19ee724cb Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 28 Mar 2024 14:49:19 +0000 Subject: [PATCH] Adding validation to PlanTriggerRequest --- backend/app/plan/schemas.py | 47 +++++++++++++-- recommendations/Recommendations.py | 94 +++++++++++++++++------------- 2 files changed, 95 insertions(+), 46 deletions(-) diff --git a/backend/app/plan/schemas.py b/backend/app/plan/schemas.py index 1e95fb2f..c13e754e 100644 --- a/backend/app/plan/schemas.py +++ b/backend/app/plan/schemas.py @@ -1,12 +1,51 @@ -from pydantic import BaseModel +from pydantic import BaseModel, conlist, validator +from typing import Optional class PlanTriggerRequest(BaseModel): - budget: float | None = None + budget: Optional[float] = None goal: str housing_type: str goal_value: str portfolio_id: int trigger_file_path: str - # optional exclusions list - exclusions: list[str] | None = None + exclusions: Optional[conlist(str, min_items=1)] = None + + # Pre-defined list of possibilities for exclusions + _allowed_exclusions = { + "wall_insulation", + "ventilation", + "roof_insulation", + "floor_insulation", + "windows", + "fireplace", + "heating", + "hot_water", + "lighting", + "solar_pv" + } + + _allowed_goals = {"Increase EPC"} + + _allowed_housing_types = {"Social", "Private"} + + # Validator to ensure exclusions are within the pre-defined possibilities + @validator('exclusions', each_item=True) + def check_exclusions(self, v): + if v not in self._allowed_exclusions: + raise ValueError(f"{v} is not an allowed exclusion") + return v + + # Validator to ensure that the goal is within the pre-defined possibilities + @validator('goal') + def check_goal(self, v): + if v not in self._allowed_goals: + raise ValueError(f"{v} is not a valid goal") + return v + + # Validator to ensure that the housing type is within the pre-defined possibilities + @validator('housing_type') + def check_housing_type(self, v): + if v not in self.allowed_housing_types: + raise ValueError(f"{v} is not a valid housing type") + return v diff --git a/recommendations/Recommendations.py b/recommendations/Recommendations.py index 9f838e1c..d3436ef0 100644 --- a/recommendations/Recommendations.py +++ b/recommendations/Recommendations.py @@ -22,7 +22,8 @@ class Recommendations: def __init__( self, property_instance: Property, - materials: List + materials: List, + exclusions: List[str] = None, ): """ :param property_instance: Instance of the Property class, for the home associated to property_id @@ -31,6 +32,7 @@ class Recommendations: self.property_instance = property_instance self.materials = materials + self.exclusions = exclusions if exclusions else [] self.floor_recommender = FloorRecommendations(property_instance=property_instance, materials=materials) self.wall_recomender = WallRecommendations(property_instance=property_instance, materials=materials) @@ -58,67 +60,75 @@ class Recommendations: property_recommendations = [] phase = 0 - print("WALL RECOMMENDATIONS HAVE BEEN COMMENTED OUT TEMPORARILY - ADD ME BACK IN") - if portfolio_id != 66: - # Building Fabric + # Building Fabric + if "wall_insulation" not in self.exclusions: self.wall_recomender.recommend(phase=phase) if self.wall_recomender.recommendations: property_recommendations.append(self.wall_recomender.recommendations) phase += 1 - # Ventilation recommendations - # We only produce a ventilation recommendation if the property is recommended to have wall or roof - # insulation - # We will not attribute a SAP impact to the ventilation recommendation, since we've seen that this has no - # real impact on the SAP score. Therefore, we don't need to include phasing for ventilation. If we have any - # wall or roof recommendations, we will ensure that ventilation is included in the simulation + # Ventilation recommendations + # We only produce a ventilation recommendation if the property is recommended to have wall or roof + # insulation + # We will not attribute a SAP impact to the ventilation recommendation, since we've seen that this has no + # real impact on the SAP score. Therefore, we don't need to include phasing for ventilation. If we have any + # wall or roof recommendations, we will ensure that ventilation is included in the simulation + if "ventilation" not in self.exclusions: if self.wall_recomender.recommendations or self.roof_recommender.recommendations: self.ventilation_recomender.recommend() if self.ventilation_recomender.recommendation: property_recommendations.append(self.ventilation_recomender.recommendation) - self.roof_recommender.recommend(phase=phase) - if self.roof_recommender.recommendations: - property_recommendations.append(self.roof_recommender.recommendations) - phase += 1 + if "roof_insulation" not in self.exclusions: + self.roof_recommender.recommend(phase=phase) + if self.roof_recommender.recommendations: + property_recommendations.append(self.roof_recommender.recommendations) + phase += 1 - self.floor_recommender.recommend(phase=phase) - if self.floor_recommender.recommendations: - property_recommendations.append(self.floor_recommender.recommendations) - phase += 1 + if "floor_insulation" not in self.exclusions: + self.floor_recommender.recommend(phase=phase) + if self.floor_recommender.recommendations: + property_recommendations.append(self.floor_recommender.recommendations) + phase += 1 - self.windows_recommender.recommend(phase=phase) - if self.windows_recommender.recommendation: - property_recommendations.append(self.windows_recommender.recommendation) - phase += 1 + if "windows" not in self.exclusions: + self.windows_recommender.recommend(phase=phase) + if self.windows_recommender.recommendation: + property_recommendations.append(self.windows_recommender.recommendation) + phase += 1 - self.fireplace_recommender.recommend(phase=phase) - if self.fireplace_recommender.recommendation: - property_recommendations.append(self.fireplace_recommender.recommendation) - phase += 1 + if "fireplace" not in self.exclusions: + self.fireplace_recommender.recommend(phase=phase) + if self.fireplace_recommender.recommendation: + property_recommendations.append(self.fireplace_recommender.recommendation) + phase += 1 # Heating and Electical systems - self.heating_recommender.recommend(phase=phase) - if self.heating_recommender.recommendations: - property_recommendations.append(self.heating_recommender.recommendations) - phase += 1 + if "heating" not in self.exclusions: + self.heating_recommender.recommend(phase=phase) + if self.heating_recommender.recommendations: + property_recommendations.append(self.heating_recommender.recommendations) + phase += 1 # Hot water - self.hotwater_recommender.recommend(phase=phase) - if self.hotwater_recommender.recommendations: - property_recommendations.append(self.hotwater_recommender.recommendations) - phase += 1 + if "hot_water" not in self.exclusions: + self.hotwater_recommender.recommend(phase=phase) + if self.hotwater_recommender.recommendations: + property_recommendations.append(self.hotwater_recommender.recommendations) + phase += 1 - self.lighting_recommender.recommend(phase=phase) - if self.lighting_recommender.recommendation: - property_recommendations.append(self.lighting_recommender.recommendation) - phase += 1 + if "lighting" not in self.exclusions: + self.lighting_recommender.recommend(phase=phase) + if self.lighting_recommender.recommendation: + property_recommendations.append(self.lighting_recommender.recommendation) + phase += 1 # Renewables - self.solar_recommender.recommend(phase=phase) - if self.solar_recommender.recommendation: - property_recommendations.append(self.solar_recommender.recommendation) - phase += 1 + if "solar_pv" not in self.exclusions: + self.solar_recommender.recommend(phase=phase) + if self.solar_recommender.recommendation: + property_recommendations.append(self.solar_recommender.recommendation) + phase += 1 # We insert temporary ids into the recommendations which is important for the optimiser later property_recommendations = self.insert_temp_recommendation_id(property_recommendations)