feat: Implement initial capex calculation feature

- Added CapexComponentInput, CapexParameters, CapexCalculationRequest, CapexCalculationResult, and related schemas for capex calculations.
- Introduced calculate_initial_capex function to aggregate capex components and compute totals and timelines.
- Created ProjectCapexRepository and ScenarioCapexRepository for managing capex snapshots in the database.
- Developed capex.html template for capturing and displaying initial capex data.
- Registered common Jinja2 filters for formatting currency and percentages.
- Implemented unit and integration tests for capex calculation functionality.
- Updated unit of work to include new repositories for capex management.
This commit is contained in:
2025-11-12 23:51:52 +01:00
parent 6c1570a254
commit d9fd82b2e3
16 changed files with 1566 additions and 93 deletions

View File

@@ -20,6 +20,7 @@ from config.database import Base
from services.currency import normalise_currency
from .enums import ResourceType, ScenarioStatus, sql_enum
from .profitability_snapshot import ScenarioProfitability
from .capex_snapshot import ScenarioCapexSnapshot
if TYPE_CHECKING: # pragma: no cover
from .financial_input import FinancialInput
@@ -83,6 +84,13 @@ class Scenario(Base):
order_by=lambda: ScenarioProfitability.calculated_at.desc(),
passive_deletes=True,
)
capex_snapshots: Mapped[List["ScenarioCapexSnapshot"]] = relationship(
"ScenarioCapexSnapshot",
back_populates="scenario",
cascade="all, delete-orphan",
order_by=lambda: ScenarioCapexSnapshot.calculated_at.desc(),
passive_deletes=True,
)
@validates("currency")
def _normalise_currency(self, key: str, value: str | None) -> str | None:
@@ -99,3 +107,11 @@ class Scenario(Base):
if not self.profitability_snapshots:
return None
return self.profitability_snapshots[0]
@property
def latest_capex(self) -> "ScenarioCapexSnapshot | None":
"""Return the most recent capex snapshot for this scenario."""
if not self.capex_snapshots:
return None
return self.capex_snapshots[0]