Files
Profeto/src/api/schemas.py
T
shangfangjian 7d2eabf750 chore: 死代码与重复逻辑清理
删除未引用/未调用符号:
- PredictionRepository(无引用)
- is_correct_1x2(无调用者)
- LeagueOut(路由用 list[dict])
- IngestResponse(IngestBzzoiroResponse 已替代)
- SecurityCheckError(从未 raise,assert_security_on_startup 用 sys.exit)
- short_write(仅自引用,全仓库无外部调用)
- fetchIngestJobs(列表函数无页面使用,单数 fetchIngestJob 仍保留)
- clear_prompt_cache(无入口)

去重:
- eval._actual_1x2 改为委托 utils.actual_1x2(单一权威源)

全量测试 270 通过,业务行为不变。
2026-09-22 01:55:52 +08:00

186 lines
5.5 KiB
Python

"""Pydantic schemas。"""
from __future__ import annotations
from datetime import date, datetime
from typing import Any
from pydantic import BaseModel, Field
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 TeamAliasIn(BaseModel):
"""POST /api/v1/admin/teams/aliases 请求体:为已有 Team 添加别名。"""
alias: str = Field(..., min_length=1, max_length=120, description="球队别名(原始写法)")
team_id: int = Field(..., gt=0, description="归一后的目标 teams.id")
class TeamAliasOut(BaseModel):
alias_normalized: str
team_id: int
original_alias: str
class IngestBzzoiroResponse(BaseModel):
"""POST /api/v1/ingest/bzzoiro 响应:兼容原 message 字段,新增 job_id 供轮询。"""
ok: bool = True
job_id: str = Field(..., description="采集任务 ID(GET /api/v1/admin/ingest/jobs/{job_id} 轮询)")
message: str = ""
class IngestJobOut(BaseModel):
"""采集任务状态详情。"""
id: str
task: str
params: dict
status: str # pending | running | success | failed
result: dict | None = None
error: str | None = None
created_at: datetime | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
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