from __future__ import annotations from datetime import datetime from typing import TYPE_CHECKING, List from .enums import MiningOperationType, sql_enum from .profitability_snapshot import ProjectProfitability from .capex_snapshot import ProjectCapexSnapshot from .opex_snapshot import ProjectOpexSnapshot from sqlalchemy import DateTime, ForeignKey, Integer, String, Text 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 .scenario import Scenario from .pricing_settings import PricingSettings class Project(Base): """Top-level mining project grouping multiple scenarios.""" __tablename__ = "projects" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) location: Mapped[str | None] = mapped_column(String(255), nullable=True) operation_type: Mapped[MiningOperationType] = mapped_column( sql_enum(MiningOperationType, name="miningoperationtype"), nullable=False, default=MiningOperationType.OTHER, ) description: Mapped[str | None] = mapped_column(Text, nullable=True) pricing_settings_id: Mapped[int | None] = mapped_column( ForeignKey("pricing_settings.id", ondelete="SET NULL"), 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() ) scenarios: Mapped[List["Scenario"]] = relationship( "Scenario", back_populates="project", cascade="all, delete-orphan", passive_deletes=True, ) pricing_settings: Mapped["PricingSettings | None"] = relationship( "PricingSettings", back_populates="projects", ) profitability_snapshots: Mapped[List["ProjectProfitability"]] = relationship( "ProjectProfitability", back_populates="project", cascade="all, delete-orphan", order_by=lambda: ProjectProfitability.calculated_at.desc(), passive_deletes=True, ) capex_snapshots: Mapped[List["ProjectCapexSnapshot"]] = relationship( "ProjectCapexSnapshot", back_populates="project", cascade="all, delete-orphan", order_by=lambda: ProjectCapexSnapshot.calculated_at.desc(), passive_deletes=True, ) opex_snapshots: Mapped[List["ProjectOpexSnapshot"]] = relationship( "ProjectOpexSnapshot", back_populates="project", cascade="all, delete-orphan", order_by=lambda: ProjectOpexSnapshot.calculated_at.desc(), passive_deletes=True, ) @property def latest_profitability(self) -> "ProjectProfitability | None": """Return the most recent profitability snapshot, if any.""" if not self.profitability_snapshots: return None return self.profitability_snapshots[0] @property def latest_capex(self) -> "ProjectCapexSnapshot | None": """Return the most recent capex snapshot, if any.""" if not self.capex_snapshots: return None return self.capex_snapshots[0] @property def latest_opex(self) -> "ProjectOpexSnapshot | None": """Return the most recent opex snapshot, if any.""" if not self.opex_snapshots: return None return self.opex_snapshots[0] def __repr__(self) -> str: # pragma: no cover - helpful for debugging return f"Project(id={self.id!r}, name={self.name!r})"