mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
cater for goal_value being NULL 🟥
This commit is contained in:
parent
70fd417c4a
commit
080000123f
6 changed files with 54 additions and 37 deletions
|
|
@ -32,7 +32,7 @@ class PortfolioStatus(enum.Enum):
|
||||||
NEEDS_REVIEW = "needs review"
|
NEEDS_REVIEW = "needs review"
|
||||||
|
|
||||||
|
|
||||||
class PortfolioGoal(enum.Enum):
|
class PortfolioGoal(enum.Enum): # TODO: Move to domain?
|
||||||
VALUATION_IMPROVEMENT = "Valuation Improvement"
|
VALUATION_IMPROVEMENT = "Valuation Improvement"
|
||||||
INCREASING_EPC = "Increasing EPC"
|
INCREASING_EPC = "Increasing EPC"
|
||||||
REDUCING_CO2_EMISSIONS = "Reducing CO2 emissions"
|
REDUCING_CO2_EMISSIONS = "Reducing CO2 emissions"
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from sqlalchemy.orm import declarative_base, Mapped, mapped_column
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
from datetime import datetime
|
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.materials import Material
|
||||||
from backend.app.db.models.portfolio import Epc
|
from backend.app.db.models.portfolio import Epc
|
||||||
from datatypes.enums import QuantityUnits
|
from datatypes.enums import QuantityUnits
|
||||||
|
|
@ -152,8 +152,8 @@ class ScenarioModel(Base):
|
||||||
BigInteger, ForeignKey(Portfolio.id), nullable=False
|
BigInteger, ForeignKey(Portfolio.id), nullable=False
|
||||||
)
|
)
|
||||||
housing_type: Mapped[str] = mapped_column(String, nullable=False)
|
housing_type: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
goal: Mapped[str] = mapped_column(String, nullable=False)
|
goal: Mapped[PortfolioGoal] = mapped_column(Enum(PortfolioGoal), nullable=False)
|
||||||
goal_value: Mapped[str] = mapped_column(String, nullable=False)
|
goal_value: Mapped[Optional[str]] = mapped_column(String, nullable=False)
|
||||||
trigger_file_path: 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)
|
already_installed_file_path: Mapped[Optional[str]] = mapped_column(String)
|
||||||
patches_file_path: Mapped[Optional[str]] = mapped_column(String)
|
patches_file_path: Mapped[Optional[str]] = mapped_column(String)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from backend.app.db.models.portfolio import PortfolioGoal
|
||||||
from backend.app.db.models.recommendations import PlanModel
|
from backend.app.db.models.recommendations import PlanModel
|
||||||
from backend.app.domain.classes.scenario import Scenario
|
from backend.app.domain.classes.scenario import Scenario
|
||||||
from backend.app.domain.records.plan_record import PlanRecord
|
from backend.app.domain.records.plan_record import PlanRecord
|
||||||
|
|
@ -48,5 +49,14 @@ class Plan:
|
||||||
def is_compliant(self) -> bool:
|
def is_compliant(self) -> bool:
|
||||||
raise NotImplementedError
|
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:
|
def set_default(self, value: bool) -> None:
|
||||||
self.record = replace(self.record, is_default=value)
|
self.record = replace(self.record, is_default=value)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from backend.app.domain.records.scenario_record import ScenarioRecord
|
||||||
class Scenario:
|
class Scenario:
|
||||||
def __init__(self, record: ScenarioRecord, id: Optional[int] = None):
|
def __init__(self, record: ScenarioRecord, id: Optional[int] = None):
|
||||||
self.id = id
|
self.id = id
|
||||||
self._record = record
|
self.record = record
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_sqlalchemy(cls, scenario_model: ScenarioModel) -> Scenario:
|
def from_sqlalchemy(cls, scenario_model: ScenarioModel) -> Scenario:
|
||||||
|
|
@ -55,4 +55,4 @@ class Scenario:
|
||||||
return cls(record, scenario_model.id)
|
return cls(record, scenario_model.id)
|
||||||
|
|
||||||
def set_default(self, value: bool) -> None:
|
def set_default(self, value: bool) -> None:
|
||||||
self._record = replace(self._record, is_default=value)
|
self.record = replace(self.record, is_default=value)
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@ from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from backend.app.db.models.portfolio import PortfolioGoal
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ScenarioRecord:
|
class ScenarioRecord:
|
||||||
name: str
|
name: str
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
housing_type: str
|
housing_type: str
|
||||||
goal: str
|
goal: PortfolioGoal
|
||||||
goal_value: str
|
|
||||||
trigger_file_path: str
|
trigger_file_path: str
|
||||||
multi_plan: bool
|
multi_plan: bool
|
||||||
is_default: bool
|
is_default: bool
|
||||||
|
|
@ -19,6 +20,7 @@ class ScenarioRecord:
|
||||||
non_invasive_recommendations_file_path: Optional[str] = None
|
non_invasive_recommendations_file_path: Optional[str] = None
|
||||||
exclusions: Optional[str] = None
|
exclusions: Optional[str] = None
|
||||||
|
|
||||||
|
goal_value: Optional[str] = None
|
||||||
cost: Optional[float] = None
|
cost: Optional[float] = None
|
||||||
contingency: Optional[float] = None
|
contingency: Optional[float] = None
|
||||||
funding: Optional[float] = None
|
funding: Optional[float] = None
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Callable
|
from typing import Callable, Optional
|
||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime
|
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.classes.scenario import Scenario
|
||||||
from backend.app.domain.records.plan_record import PlanRecord
|
from backend.app.domain.records.plan_record import PlanRecord
|
||||||
from backend.app.domain.records.scenario_record import ScenarioRecord
|
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
|
@pytest.fixture
|
||||||
|
|
@ -14,28 +14,17 @@ def created_at_datetime() -> datetime:
|
||||||
return datetime.now()
|
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
|
@pytest.fixture
|
||||||
def plan_factory(
|
def plan_factory(
|
||||||
epc_c_scenario: "Scenario", created_at_datetime: datetime
|
created_at_datetime: datetime,
|
||||||
) -> Callable[[int, "Epc"], "Plan"]:
|
) -> Callable[[int, "Epc", "Scenario"], "Plan"]:
|
||||||
# returns a function to create plans with different attributes
|
"""
|
||||||
def _create_plan(post_sap_points: int, post_epc_rating: "Epc") -> "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(
|
plan_record = PlanRecord(
|
||||||
property_id=1,
|
property_id=1,
|
||||||
portfolio_id=1,
|
portfolio_id=1,
|
||||||
|
|
@ -44,27 +33,43 @@ def plan_factory(
|
||||||
post_sap_points=post_sap_points,
|
post_sap_points=post_sap_points,
|
||||||
post_epc_rating=post_epc_rating,
|
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
|
return _create_plan
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@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),
|
("EPC C", "C", 75, Epc.C, True),
|
||||||
(100, Epc.A, True),
|
("EPC A", "A", 100, Epc.A, True),
|
||||||
(60, Epc.D, False),
|
("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(
|
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_sap_points: int,
|
||||||
post_epc_rating: "Epc",
|
post_epc_rating: "Epc",
|
||||||
expected_compliance: bool,
|
expected_compliance: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
# arrange
|
# 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
|
# act
|
||||||
actual_compliance: bool = plan.is_compliant
|
actual_compliance: bool = plan.is_compliant
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue