From bf0fce8ca5af592fea52fcadb27d994c721e21ba Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Feb 2026 16:08:37 +0000 Subject: [PATCH] =?UTF-8?q?Check=20whether=20plan=20with=20EPC=20goal=20is?= =?UTF-8?q?=20compliant=20(and=20change=20goal=5Fvalue=20back=20to=20requi?= =?UTF-8?q?red)=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/db/models/recommendations.py | 2 +- backend/app/domain/classes/plan.py | 15 ++++- backend/app/domain/records/scenario_record.py | 2 +- .../tests/test_plan_is_compliant.py | 61 +++++++++---------- 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/backend/app/db/models/recommendations.py b/backend/app/db/models/recommendations.py index 82032d35..addb5e80 100644 --- a/backend/app/db/models/recommendations.py +++ b/backend/app/db/models/recommendations.py @@ -153,7 +153,7 @@ class ScenarioModel(Base): ) housing_type: Mapped[str] = mapped_column(String, nullable=False) goal: Mapped[PortfolioGoal] = mapped_column(Enum(PortfolioGoal), nullable=False) - goal_value: Mapped[Optional[str]] = mapped_column(String, nullable=False) + goal_value: Mapped[str] = mapped_column(String, nullable=False) trigger_file_path: Mapped[str] = mapped_column(String, nullable=False) already_installed_file_path: Mapped[Optional[str]] = mapped_column(String) patches_file_path: Mapped[Optional[str]] = mapped_column(String) diff --git a/backend/app/domain/classes/plan.py b/backend/app/domain/classes/plan.py index b44543a6..1efe87a5 100644 --- a/backend/app/domain/classes/plan.py +++ b/backend/app/domain/classes/plan.py @@ -6,6 +6,7 @@ from backend.app.db.models.portfolio import PortfolioGoal from backend.app.db.models.recommendations import PlanModel from backend.app.domain.classes.scenario import Scenario from backend.app.domain.records.plan_record import PlanRecord +from backend.app.utils import sap_to_epc class Plan: @@ -47,14 +48,22 @@ class Plan: @property def is_compliant(self) -> bool: - raise NotImplementedError - goal: PortfolioGoal = self.scenario.record.goal goal_value: str = self.scenario.record.goal_value match goal: case PortfolioGoal.INCREASING_EPC: - return True + if self.record.post_epc_rating: + post_epc = self.record.post_epc_rating.value + elif self.record.post_sap_points: + post_epc = sap_to_epc(self.record.post_sap_points) + else: + return False + + if post_epc <= goal_value: + return True + + return False case _: raise NotImplementedError diff --git a/backend/app/domain/records/scenario_record.py b/backend/app/domain/records/scenario_record.py index 48ddf0ca..0865cc88 100644 --- a/backend/app/domain/records/scenario_record.py +++ b/backend/app/domain/records/scenario_record.py @@ -11,6 +11,7 @@ class ScenarioRecord: created_at: datetime housing_type: str goal: PortfolioGoal + goal_value: str trigger_file_path: str multi_plan: bool is_default: bool @@ -20,7 +21,6 @@ class ScenarioRecord: non_invasive_recommendations_file_path: Optional[str] = None exclusions: Optional[str] = None - goal_value: Optional[str] = None cost: Optional[float] = None contingency: Optional[float] = None funding: Optional[float] = None diff --git a/backend/categorisation/tests/test_plan_is_compliant.py b/backend/categorisation/tests/test_plan_is_compliant.py index c0f7add0..62756652 100644 --- a/backend/categorisation/tests/test_plan_is_compliant.py +++ b/backend/categorisation/tests/test_plan_is_compliant.py @@ -1,4 +1,4 @@ -from typing import Callable, Optional +from typing import Callable import pytest from datetime import datetime @@ -15,16 +15,27 @@ def created_at_datetime() -> datetime: @pytest.fixture -def plan_factory( - created_at_datetime: datetime, -) -> Callable[[int, "Epc", "Scenario"], "Plan"]: - """ - Returns a factory function to create plans with different attributes and scenarios. - """ +def epc_c_scenario(created_at_datetime: datetime) -> "Scenario": + # arrange + scenario_record = ScenarioRecord( + name="EPC C", + created_at=created_at_datetime, + housing_type="", + goal=PortfolioGoal.INCREASING_EPC, + goal_value="C", + trigger_file_path="", + multi_plan=False, + is_default=False, + ) + return Scenario(record=scenario_record, id=1) - def _create_plan( - post_sap_points: int, post_epc_rating: "Epc", scenario: "Scenario" - ) -> "Plan": + +@pytest.fixture +def plan_factory( + epc_c_scenario: "Scenario", created_at_datetime: datetime +) -> Callable[[int, "Epc"], "Plan"]: + # returns a function to create plans with different attributes + def _create_plan(post_sap_points: int, post_epc_rating: "Epc") -> "Plan": plan_record = PlanRecord( property_id=1, portfolio_id=1, @@ -33,43 +44,27 @@ def plan_factory( post_sap_points=post_sap_points, post_epc_rating=post_epc_rating, ) - return Plan(record=plan_record, scenario=scenario, id=1) + return Plan(record=plan_record, scenario=epc_c_scenario, id=1) return _create_plan @pytest.mark.parametrize( - "scenario_name, goal_value, post_sap_points, post_epc_rating, expected_compliance", + "post_sap_points, post_epc_rating, expected_compliance", [ - ("EPC C", "C", 75, Epc.C, True), - ("EPC A", "A", 100, Epc.A, True), - ("EPC D", "D", 60, Epc.D, False), - ("Achieve EPC B", None, 100, Epc.A, True), - ("Achieve EPC B", None, 60, Epc.D, False), + (75, Epc.C, True), + (100, Epc.A, True), + (60, Epc.D, False), ], ) def test_scenario_goal_is_epc_c( - plan_factory: Callable[[int, "Epc", "Scenario"], "Plan"], - scenario_name: str, - goal_value: Optional[str], + plan_factory: Callable[[int, "Epc"], "Plan"], post_sap_points: int, post_epc_rating: "Epc", expected_compliance: bool, ) -> None: # arrange - scenario_record = ScenarioRecord( - name=scenario_name, - created_at=datetime.now(), - housing_type="", - goal=PortfolioGoal.INCREASING_EPC, - goal_value=goal_value, - trigger_file_path="", - multi_plan=False, - is_default=False, - ) - scenario = Scenario(record=scenario_record, id=1) - - plan = plan_factory(post_sap_points, post_epc_rating, scenario) + plan = plan_factory(post_sap_points, post_epc_rating) # act actual_compliance: bool = plan.is_compliant