"""Pydantic schemas for scoring endpoints.""" from __future__ import annotations from datetime import datetime from pydantic import BaseModel, Field class SubScoreResponse(BaseModel): """A single sub-score within a dimension breakdown.""" name: str score: float weight: float raw_value: float | str | None = None description: str = "" class ScoreBreakdownResponse(BaseModel): """Breakdown of a dimension score into sub-scores.""" sub_scores: list[SubScoreResponse] formula: str unavailable: list[dict[str, str]] = [] class CompositeBreakdownResponse(BaseModel): """Breakdown of the composite score showing dimension weights and re-normalization.""" weights: dict[str, float] available_dimensions: list[str] missing_dimensions: list[str] renormalized_weights: dict[str, float] formula: str class DimensionScoreResponse(BaseModel): """A single dimension score.""" dimension: str score: float is_stale: bool computed_at: datetime | None = None breakdown: ScoreBreakdownResponse | None = None class ScoreResponse(BaseModel): """Full score response for a ticker: composite + all dimensions.""" symbol: str composite_score: float | None = None composite_stale: bool = False weights: dict[str, float] = {} dimensions: list[DimensionScoreResponse] = [] missing_dimensions: list[str] = [] computed_at: datetime | None = None composite_breakdown: CompositeBreakdownResponse | None = None class WeightUpdateRequest(BaseModel): """Request to update dimension weights.""" weights: dict[str, float] = Field( ..., description="Dimension name → weight mapping. All weights must be positive.", ) class RankingEntry(BaseModel): """A single entry in the rankings list.""" symbol: str composite_score: float dimensions: list[DimensionScoreResponse] = [] class RankingResponse(BaseModel): """Rankings response: tickers sorted by composite score descending.""" rankings: list[RankingEntry] = [] weights: dict[str, float] = {}