Files
calminer/models/project.py
zwitschi b1a6df9f90 feat: Add profitability calculation schemas and service functions
- Introduced Pydantic schemas for profitability calculations in `schemas/calculations.py`.
- Implemented service functions for profitability calculations in `services/calculations.py`.
- Added new exception class `ProfitabilityValidationError` for handling validation errors.
- Created repositories for managing project and scenario profitability snapshots.
- Developed a utility script for verifying authenticated routes.
- Added a new HTML template for the profitability calculator interface.
- Implemented a script to fix user ID sequence in the database.
2025-11-12 22:22:29 +01:00

73 lines
2.6 KiB
Python

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 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,
)
@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]
def __repr__(self) -> str: # pragma: no cover - helpful for debugging
return f"Project(id={self.id!r}, name={self.name!r})"