cater for goal_value being NULL 🟥

This commit is contained in:
Daniel Roth 2026-02-12 15:40:03 +00:00
parent 70fd417c4a
commit 080000123f
6 changed files with 54 additions and 37 deletions

View file

@ -32,7 +32,7 @@ class PortfolioStatus(enum.Enum):
NEEDS_REVIEW = "needs review"
class PortfolioGoal(enum.Enum):
class PortfolioGoal(enum.Enum): # TODO: Move to domain?
VALUATION_IMPROVEMENT = "Valuation Improvement"
INCREASING_EPC = "Increasing EPC"
REDUCING_CO2_EMISSIONS = "Reducing CO2 emissions"

View file

@ -13,7 +13,7 @@ from sqlalchemy.orm import declarative_base, Mapped, mapped_column
from sqlalchemy.sql import func
from datetime import datetime
from backend.app.db.models.portfolio import Portfolio, PropertyModel
from backend.app.db.models.portfolio import Portfolio, PortfolioGoal, PropertyModel
from backend.app.db.models.materials import Material
from backend.app.db.models.portfolio import Epc
from datatypes.enums import QuantityUnits
@ -152,8 +152,8 @@ class ScenarioModel(Base):
BigInteger, ForeignKey(Portfolio.id), nullable=False
)
housing_type: Mapped[str] = mapped_column(String, nullable=False)
goal: Mapped[str] = mapped_column(String, nullable=False)
goal_value: 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)
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)

View file

@ -2,6 +2,7 @@ from __future__ import annotations
from dataclasses import replace
from typing import Optional
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
@ -48,5 +49,14 @@ class Plan:
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
case _:
raise NotImplementedError
def set_default(self, value: bool) -> None:
self.record = replace(self.record, is_default=value)

View file

@ -9,7 +9,7 @@ from backend.app.domain.records.scenario_record import ScenarioRecord
class Scenario:
def __init__(self, record: ScenarioRecord, id: Optional[int] = None):
self.id = id
self._record = record
self.record = record
@classmethod
def from_sqlalchemy(cls, scenario_model: ScenarioModel) -> Scenario:
@ -55,4 +55,4 @@ class Scenario:
return cls(record, scenario_model.id)
def set_default(self, value: bool) -> None:
self._record = replace(self._record, is_default=value)
self.record = replace(self.record, is_default=value)

View file

@ -2,14 +2,15 @@ from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from backend.app.db.models.portfolio import PortfolioGoal
@dataclass(frozen=True)
class ScenarioRecord:
name: str
created_at: datetime
housing_type: str
goal: str
goal_value: str
goal: PortfolioGoal
trigger_file_path: str
multi_plan: bool
is_default: bool
@ -19,6 +20,7 @@ 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

View file

@ -1,4 +1,4 @@
from typing import Callable
from typing import Callable, Optional
import pytest
from datetime import datetime
@ -6,7 +6,7 @@ from backend.app.domain.classes.plan import Plan
from backend.app.domain.classes.scenario import Scenario
from backend.app.domain.records.plan_record import PlanRecord
from backend.app.domain.records.scenario_record import ScenarioRecord
from backend.app.db.models.portfolio import Epc
from backend.app.db.models.portfolio import Epc, PortfolioGoal
@pytest.fixture
@ -14,28 +14,17 @@ def created_at_datetime() -> datetime:
return datetime.now()
@pytest.fixture
def epc_c_scenario(created_at_datetime: datetime) -> "Scenario":
# arrange
scenario_record = ScenarioRecord(
name="EPC C",
created_at=created_at_datetime,
housing_type="",
goal="EPC",
goal_value="C",
trigger_file_path="",
multi_plan=False,
is_default=False,
)
return Scenario(record=scenario_record, id=1)
@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":
created_at_datetime: datetime,
) -> Callable[[int, "Epc", "Scenario"], "Plan"]:
"""
Returns a factory function to create plans with different attributes and scenarios.
"""
def _create_plan(
post_sap_points: int, post_epc_rating: "Epc", scenario: "Scenario"
) -> "Plan":
plan_record = PlanRecord(
property_id=1,
portfolio_id=1,
@ -44,27 +33,43 @@ def plan_factory(
post_sap_points=post_sap_points,
post_epc_rating=post_epc_rating,
)
return Plan(record=plan_record, scenario=epc_c_scenario, id=1)
return Plan(record=plan_record, scenario=scenario, id=1)
return _create_plan
@pytest.mark.parametrize(
"post_sap_points, post_epc_rating, expected_compliance",
"scenario_name, goal_value, post_sap_points, post_epc_rating, expected_compliance",
[
(75, Epc.C, True),
(100, Epc.A, True),
(60, Epc.D, False),
("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),
],
)
def test_scenario_goal_is_epc_c(
plan_factory: Callable[[int, "Epc"], "Plan"],
plan_factory: Callable[[int, "Epc", "Scenario"], "Plan"],
scenario_name: str,
goal_value: Optional[str],
post_sap_points: int,
post_epc_rating: "Epc",
expected_compliance: bool,
) -> None:
# arrange
plan = plan_factory(post_sap_points, post_epc_rating)
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)
# act
actual_compliance: bool = plan.is_compliant