from __future__ import annotations from datetime import date, datetime from typing import TYPE_CHECKING from sqlalchemy import ( Date, DateTime, Enum as SQLEnum, ForeignKey, Integer, Numeric, String, Text, ) from sqlalchemy.orm import Mapped, mapped_column, relationship, validates from sqlalchemy.sql import func from config.database import Base from .enums import CostBucket, FinancialCategory from services.currency import normalise_currency if TYPE_CHECKING: # pragma: no cover from .scenario import Scenario class FinancialInput(Base): """Line-item financial assumption attached to a scenario.""" __tablename__ = "financial_inputs" id: Mapped[int] = mapped_column(Integer, primary_key=True) scenario_id: Mapped[int] = mapped_column( ForeignKey("scenarios.id", ondelete="CASCADE"), nullable=False, index=True ) name: Mapped[str] = mapped_column(String(255), nullable=False) category: Mapped[FinancialCategory] = mapped_column( SQLEnum(FinancialCategory, name="financialcategory", create_type=False), nullable=False ) cost_bucket: Mapped[CostBucket | None] = mapped_column( SQLEnum(CostBucket, name="costbucket", create_type=False), nullable=True ) amount: Mapped[float] = mapped_column(Numeric(18, 2), nullable=False) currency: Mapped[str | None] = mapped_column(String(3), nullable=True) effective_date: Mapped[date | None] = mapped_column(Date, nullable=True) notes: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() ) scenario: Mapped["Scenario"] = relationship( "Scenario", back_populates="financial_inputs") @validates("currency") def _validate_currency(self, key: str, value: str | None) -> str | None: return normalise_currency(value) def __repr__(self) -> str: # pragma: no cover return f"FinancialInput(id={self.id!r}, scenario_id={self.scenario_id!r}, name={self.name!r})"