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.
This commit is contained in:
108
schemas/calculations.py
Normal file
108
schemas/calculations.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Pydantic schemas for calculation workflows."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveFloat, ValidationError, field_validator
|
||||
|
||||
from services.pricing import PricingResult
|
||||
|
||||
|
||||
class ImpurityInput(BaseModel):
|
||||
"""Impurity configuration row supplied by the client."""
|
||||
|
||||
name: str = Field(..., min_length=1)
|
||||
value: float | None = Field(None, ge=0)
|
||||
threshold: float | None = Field(None, ge=0)
|
||||
penalty: float | None = Field(None)
|
||||
|
||||
@field_validator("name")
|
||||
@classmethod
|
||||
def _normalise_name(cls, value: str) -> str:
|
||||
return value.strip()
|
||||
|
||||
|
||||
class ProfitabilityCalculationRequest(BaseModel):
|
||||
"""Request payload for profitability calculations."""
|
||||
|
||||
metal: str = Field(..., min_length=1)
|
||||
ore_tonnage: PositiveFloat
|
||||
head_grade_pct: float = Field(..., gt=0, le=100)
|
||||
recovery_pct: float = Field(..., gt=0, le=100)
|
||||
payable_pct: float | None = Field(None, gt=0, le=100)
|
||||
reference_price: PositiveFloat
|
||||
treatment_charge: float = Field(0, ge=0)
|
||||
smelting_charge: float = Field(0, ge=0)
|
||||
moisture_pct: float = Field(0, ge=0, le=100)
|
||||
moisture_threshold_pct: float | None = Field(None, ge=0, le=100)
|
||||
moisture_penalty_per_pct: float | None = None
|
||||
premiums: float = Field(0)
|
||||
fx_rate: PositiveFloat = Field(1)
|
||||
currency_code: str | None = Field(None, min_length=3, max_length=3)
|
||||
processing_opex: float = Field(0, ge=0)
|
||||
sustaining_capex: float = Field(0, ge=0)
|
||||
initial_capex: float = Field(0, ge=0)
|
||||
discount_rate: float | None = Field(None, ge=0, le=100)
|
||||
periods: int = Field(10, ge=1, le=120)
|
||||
impurities: List[ImpurityInput] = Field(default_factory=list)
|
||||
|
||||
@field_validator("currency_code")
|
||||
@classmethod
|
||||
def _uppercase_currency(cls, value: str | None) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
return value.strip().upper()
|
||||
|
||||
@field_validator("metal")
|
||||
@classmethod
|
||||
def _normalise_metal(cls, value: str) -> str:
|
||||
return value.strip().lower()
|
||||
|
||||
|
||||
class ProfitabilityCosts(BaseModel):
|
||||
"""Aggregated cost components for profitability output."""
|
||||
|
||||
processing_opex_total: float
|
||||
sustaining_capex_total: float
|
||||
initial_capex: float
|
||||
|
||||
|
||||
class ProfitabilityMetrics(BaseModel):
|
||||
"""Financial KPIs yielded by the profitability calculation."""
|
||||
|
||||
npv: float | None
|
||||
irr: float | None
|
||||
payback_period: float | None
|
||||
margin: float | None
|
||||
|
||||
|
||||
class CashFlowEntry(BaseModel):
|
||||
"""Normalized cash flow row for reporting and charting."""
|
||||
|
||||
period: int
|
||||
revenue: float
|
||||
processing_opex: float
|
||||
sustaining_capex: float
|
||||
net: float
|
||||
|
||||
|
||||
class ProfitabilityCalculationResult(BaseModel):
|
||||
"""Response body summarizing profitability calculation outputs."""
|
||||
|
||||
pricing: PricingResult
|
||||
costs: ProfitabilityCosts
|
||||
metrics: ProfitabilityMetrics
|
||||
cash_flows: list[CashFlowEntry]
|
||||
currency: str | None
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ImpurityInput",
|
||||
"ProfitabilityCalculationRequest",
|
||||
"ProfitabilityCosts",
|
||||
"ProfitabilityMetrics",
|
||||
"CashFlowEntry",
|
||||
"ProfitabilityCalculationResult",
|
||||
"ValidationError",
|
||||
]
|
||||
Reference in New Issue
Block a user