"""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", ]