后端: - settle_prediction 拒绝 degraded/failed(明确错误信息) - get_eval_summary 支持 provider/model/prompt_version/mode 筛选 - 返回 filtered_settled/evaluated/skipped_degraded 等计数 - matches 游标分页方向修复(scheduled ASC 用 > 条件) - available_at 加 2h 缓冲(近似完赛时间) - bzzziro 统计字段映射注释(待真实响应验证) - injuries 区分 no_local_data 与 success 空名单 前端: - 新增 EvalPage(筛选控件 + 汇总卡片 + 准确率表格) - 挂载 /admin/eval 路由与导航 测试: - test_matches_cursor.py:游标方向 - test_available_at.py:2h 缓冲与回测防泄漏 - test_bzzoirot_stats.py:统计字段映射 - test_injuries_no_local_data.py:no_local_data vs success - test_injuries_inserted_count.py:失败批不计入 - test_eval_excludes_degraded.py:degraded 排除准确率
132 lines
3.3 KiB
Python
132 lines
3.3 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
|
|
|
|
|
|
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 = "multi" # multi(默认, 5专家+终裁) | single(单次调用)
|
|
|
|
|
|
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
|
|
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
|
|
agent_outputs: list[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;空 = 两者都采集")
|
|
|
|
|
|
class IngestResponse(BaseModel):
|
|
leagues: dict
|
|
total_inserted: int
|
|
total_updated: int
|
|
errors: list[str] = []
|
|
|
|
|
|
class IngestUnderstatRequest(BaseModel):
|
|
league: str | None = Field(None, description="联赛代码,如 'E0';空 = 全部已知联赛")
|
|
season: int = Field(default_factory=lambda: date.today().year, description="赛季起始年,如 2025 表示 2025-2026 赛季")
|
|
|
|
|
|
class IngestInjuriesRequest(BaseModel):
|
|
date: str | None = Field(None, description="日期 YYYY-MM-DD,为空则采集当天")
|
|
|
|
|
|
class IngestSimpleResponse(BaseModel):
|
|
count: int = 0
|
|
updated: int = 0
|
|
skipped: int = 0
|
|
unmatched: int = 0
|
|
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
|