High-1/2: baseline mode 违反 DB CHECK 约束
- ck_mode_enum 扩展为 ('single','multi','baseline')
- baseline 的 run_type 从 'baseline' 改为 'live'(符合现有约束)
- 新增迁移 0017_mode_baseline
High-3: 限流日志 NameError: ip 未定义
- deps.py:190 的 logger.warning 中 ip → client_ip
High-4: 生产 Cookie 缺少 Secure 标志
- auth.py 登录时根据 APP_ENV 设置 secure=True(生产)
Medium-5: 降级日志参数类型错误
- orchestrator.py:266 ok_reports(list) → len(ok_reports)(int)
附加: PredictRequest mode 字段加 pattern 校验,与 DB 约束同源
Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
165 lines
4.8 KiB
Python
165 lines
4.8 KiB
Python
"""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
|