"""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", pattern="^(multi|single|baseline)$", 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 # 比赛信息(可选,列表接口不返回以减少 payload) match: dict | None = None 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 ScheduleIn(BaseModel): id: str = Field(..., description="任务唯一标识,如 'daily-events'") task: str = Field(..., description="events / standings / stats / all") cron: str = Field(..., description="cron 表达式,如 '0 8 * * *' (每天 8 点)") leagues: list[str] = Field(default_factory=list, description="联赛代码列表,空=全部") enabled: bool = True class ScheduleUpdate(BaseModel): task: str | None = None cron: str | None = None leagues: list[str] | None = None enabled: bool | None = None class ScheduleOut(BaseModel): id: str task: str cron: str leagues: str | None enabled: bool last_run_at: str | None last_status: str | None 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