- Updated form template to prefill currency input with default value and added help text for clarity. - Modified integration tests to assert more descriptive error messages for invalid currency codes. - Introduced new tests for currency normalization and validation in various scenarios, including imports and exports. - Added comprehensive tests for pricing calculations, ensuring defaults are respected and overrides function correctly. - Implemented unit tests for pricing settings repository, ensuring CRUD operations and default settings are handled properly. - Enhanced scenario pricing evaluation tests to validate currency handling and metadata defaults. - Added simulation tests to ensure Monte Carlo runs are accurate and handle various distribution scenarios.
98 lines
2.8 KiB
Python
98 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import date, datetime
|
|
|
|
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
|
|
|
from models import ResourceType, ScenarioStatus
|
|
from services.currency import CurrencyValidationError, normalise_currency
|
|
|
|
|
|
class ScenarioBase(BaseModel):
|
|
name: str
|
|
description: str | None = None
|
|
status: ScenarioStatus = ScenarioStatus.DRAFT
|
|
start_date: date | None = None
|
|
end_date: date | None = None
|
|
discount_rate: float | None = None
|
|
currency: str | None = None
|
|
primary_resource: ResourceType | None = None
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
@field_validator("currency")
|
|
@classmethod
|
|
def normalise_currency(cls, value: str | None) -> str | None:
|
|
if value is None:
|
|
return None
|
|
candidate = value if isinstance(value, str) else str(value)
|
|
candidate = candidate.strip()
|
|
if not candidate:
|
|
return None
|
|
try:
|
|
return normalise_currency(candidate)
|
|
except CurrencyValidationError as exc:
|
|
raise ValueError(str(exc)) from exc
|
|
|
|
|
|
class ScenarioCreate(ScenarioBase):
|
|
pass
|
|
|
|
|
|
class ScenarioUpdate(BaseModel):
|
|
name: str | None = None
|
|
description: str | None = None
|
|
status: ScenarioStatus | None = None
|
|
start_date: date | None = None
|
|
end_date: date | None = None
|
|
discount_rate: float | None = None
|
|
currency: str | None = None
|
|
primary_resource: ResourceType | None = None
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
@field_validator("currency")
|
|
@classmethod
|
|
def normalise_currency(cls, value: str | None) -> str | None:
|
|
if value is None:
|
|
return None
|
|
candidate = value if isinstance(value, str) else str(value)
|
|
candidate = candidate.strip()
|
|
if not candidate:
|
|
return None
|
|
try:
|
|
return normalise_currency(candidate)
|
|
except CurrencyValidationError as exc:
|
|
raise ValueError(str(exc)) from exc
|
|
|
|
|
|
class ScenarioRead(ScenarioBase):
|
|
id: int
|
|
project_id: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class ScenarioComparisonRequest(BaseModel):
|
|
scenario_ids: list[int]
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
@model_validator(mode="after")
|
|
def ensure_minimum_ids(self) -> "ScenarioComparisonRequest":
|
|
unique_ids: list[int] = list(dict.fromkeys(self.scenario_ids))
|
|
if len(unique_ids) < 2:
|
|
raise ValueError(
|
|
"At least two unique scenario identifiers are required for comparison.")
|
|
self.scenario_ids = unique_ids
|
|
return self
|
|
|
|
|
|
class ScenarioComparisonResponse(BaseModel):
|
|
project_id: int
|
|
scenarios: list[ScenarioRead]
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|