"""Pydantic schemas。""" from __future__ import annotations from datetime import date, datetime from typing import Any from pydantic import BaseModel, Field class LeagueOut(BaseModel): id: int code: str name: str country: str | None class MatchOut(BaseModel): id: int league_code: str | None season: str | None home_team: str away_team: str home_team_zh: str | None away_team_zh: str | None match_date: datetime match_status: str home_goals: int | None away_goals: int | None match_stage: str | None home_xg: float | None = None away_xg: float | None = None # 比赛详细统计(bzzoiro /events/{id}/stats/),无统计为 None stats: dict | None = None # 该场比赛的最近预测摘要(按时间倒序,最多 5 条;无预测为空) recent_predictions: list[PredictionOut] = [] class MatchListOut(BaseModel): items: list[MatchOut] next_cursor: str | None has_more: bool class PredictRequest(BaseModel): match_id: int provider: str | None = None model: str | None = None prompt_version: str | None = None mode: str = Field( "multi", description="multi(默认,5专家+终裁) | single(单次) | baseline(极简统计基线,不调用 LLM)", ) use_cache: bool = True backtest: bool = False cutoff_at: str | None = Field(None, description="显式截止时间 ISO8601,用于回测防未来信息") class PredictOut(BaseModel): prediction_id: int provider: str model: str prompt_version: str | None = None mode: str = "single" pred_home_goals: float | None pred_away_goals: float | None alt_pred_home_goals: int | None = None alt_pred_away_goals: int | None = None pred_1x2: str | None subjective_confidence: float | None reasoning: str | None status: str = "success" # 成本信息(可选;单次/多专家均有) latency_ms: int | None = None prompt_tokens: int | None = None completion_tokens: int | None = None # 限流提示:当请求被节流时告知用户剩余配额(可选) rate_limit_remaining: int | None = None agent_outputs: list[dict] | None = None agent_weights: dict | None = None context: str latency_ms: int | None class PredictionOut(BaseModel): id: int match_id: int provider: str model: str prompt_version: str mode: str = "single" pred_home_goals: float | None pred_away_goals: float | None alt_pred_home_goals: int | None = None alt_pred_away_goals: int | None = None pred_1x2: str | None subjective_confidence: float | None reasoning: str | None status: str = "success" agent_outputs: list[dict] | None = None agent_weights: dict | None = None created_at: datetime actual_home_goals: int | None actual_away_goals: int | None settled: bool class IngestBzzoiroRequest(BaseModel): leagues: list[str] = Field(default_factory=list, description="联赛代码列表,如 ['E0','SP1'];空 = 全部已知联赛") date_from: str | None = None date_to: str | None = None status: str | None = Field(None, description="finished/scheduled;空 = 两者都采集") task: str = Field("events", description="采集任务: events(比赛)/standings(积分榜)/stats(统计回填)/all") limit: int = Field(100, ge=1, le=500, description="stats 回填单次最大比赛数") season: str | None = Field(None, description="standings 赛季,如 '2026-2027';空 = 当前赛季") class IngestResponse(BaseModel): leagues: dict total_inserted: int total_updated: int errors: list[str] = [] class SettleRequest(BaseModel): prediction_id: int home_goals: int = Field(ge=0, le=30) away_goals: int = Field(ge=0, le=30) class EvalSummaryOut(BaseModel): summary: list[dict[str, Any]] total_settled: int filtered_settled: int evaluated: int skipped_degraded: int skipped_incomplete: int = 0