from __future__ import annotations from datetime import datetime from typing import TYPE_CHECKING from sqlalchemy import JSON, DateTime, ForeignKey, Integer, Numeric, String from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.sql import func from config.database import Base if TYPE_CHECKING: # pragma: no cover from .project import Project from .scenario import Scenario from .user import User class ProjectProfitability(Base): """Snapshot of aggregated profitability metrics at the project level.""" __tablename__ = "project_profitability_snapshots" id: Mapped[int] = mapped_column(Integer, primary_key=True) project_id: Mapped[int] = mapped_column( ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True ) created_by_id: Mapped[int | None] = mapped_column( ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True ) calculation_source: Mapped[str | None] = mapped_column( String(64), nullable=True) calculated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) currency_code: Mapped[str | None] = mapped_column(String(3), nullable=True) npv: Mapped[float | None] = mapped_column(Numeric(18, 2), nullable=True) irr_pct: Mapped[float | None] = mapped_column( Numeric(12, 6), nullable=True) payback_period_years: Mapped[float | None] = mapped_column( Numeric(12, 4), nullable=True ) margin_pct: Mapped[float | None] = mapped_column( Numeric(12, 6), nullable=True) revenue_total: Mapped[float | None] = mapped_column( Numeric(18, 2), nullable=True) opex_total: Mapped[float | None] = mapped_column( Numeric(18, 2), nullable=True ) sustaining_capex_total: Mapped[float | None] = mapped_column( Numeric(18, 2), nullable=True ) capex: Mapped[float | None] = mapped_column( Numeric(18, 2), nullable=True) net_cash_flow_total: Mapped[float | None] = mapped_column( Numeric(18, 2), nullable=True ) payload: Mapped[dict | None] = mapped_column(JSON, 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() ) project: Mapped[Project] = relationship( "Project", back_populates="profitability_snapshots") created_by: Mapped[User | None] = relationship("User") def __repr__(self) -> str: # pragma: no cover return ( "ProjectProfitability(id={id!r}, project_id={project_id!r}, npv={npv!r})".format( id=self.id, project_id=self.project_id, npv=self.npv ) ) class ScenarioProfitability(Base): """Snapshot of profitability metrics for an individual scenario.""" __tablename__ = "scenario_profitability_snapshots" id: Mapped[int] = mapped_column(Integer, primary_key=True) scenario_id: Mapped[int] = mapped_column( ForeignKey("scenarios.id", ondelete="CASCADE"), nullable=False, index=True ) created_by_id: Mapped[int | None] = mapped_column( ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True ) calculation_source: Mapped[str | None] = mapped_column( String(64), nullable=True) calculated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) currency_code: Mapped[str | None] = mapped_column(String(3), nullable=True) npv: Mapped[float | None] = mapped_column(Numeric(18, 2), nullable=True) irr_pct: Mapped[float | None] = mapped_column( Numeric(12, 6), nullable=True) payback_period_years: Mapped[float | None] = mapped_column( Numeric(12, 4), nullable=True ) margin_pct: Mapped[float | None] = mapped_column( Numeric(12, 6), nullable=True) revenue_total: Mapped[float | None] = mapped_column( Numeric(18, 2), nullable=True) opex_total: Mapped[float | None] = mapped_column( Numeric(18, 2), nullable=True ) sustaining_capex_total: Mapped[float | None] = mapped_column( Numeric(18, 2), nullable=True ) capex: Mapped[float | None] = mapped_column( Numeric(18, 2), nullable=True) net_cash_flow_total: Mapped[float | None] = mapped_column( Numeric(18, 2), nullable=True ) payload: Mapped[dict | None] = mapped_column(JSON, 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="profitability_snapshots") created_by: Mapped[User | None] = relationship("User") def __repr__(self) -> str: # pragma: no cover return ( "ScenarioProfitability(id={id!r}, scenario_id={scenario_id!r}, npv={npv!r})".format( id=self.id, scenario_id=self.scenario_id, npv=self.npv ) )