完善评估能力:筛选参数 + degraded 排除 + 前端评估页
后端: - 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 排除准确率
This commit is contained in:
+27
-4
@@ -17,7 +17,10 @@ router = APIRouter(prefix="/api/v1", tags=["eval"])
|
||||
|
||||
@router.post("/eval/settle", dependencies=[Depends(require_admin)])
|
||||
async def settle(req: SettleRequest, db: AsyncSession = Depends(get_db)):
|
||||
"""回填实际结果。"""
|
||||
"""回填实际结果。
|
||||
|
||||
status 为 degraded/failed 的预测无法结算。
|
||||
"""
|
||||
try:
|
||||
pred = await settle_prediction(req.prediction_id, req.home_goals, req.away_goals)
|
||||
return {"id": pred.id, "settled": pred.settled}
|
||||
@@ -30,6 +33,26 @@ async def settle(req: SettleRequest, db: AsyncSession = Depends(get_db)):
|
||||
|
||||
|
||||
@router.get("/eval/summary", response_model=EvalSummaryOut, dependencies=[Depends(require_admin)])
|
||||
async def eval_summary(limit: int = Query(1000, ge=1, le=10000, description="最大评估条数")):
|
||||
"""提供商/模型准确率对比。P3-4: 默认评估最近 1000 条,可通过 limit 调整。"""
|
||||
return await get_eval_summary(limit=limit)
|
||||
async def eval_summary(
|
||||
limit: int = Query(1000, ge=1, le=10000, description="最大评估条数"),
|
||||
provider: str | None = Query(None, description="按提供商筛选"),
|
||||
model: str | None = Query(None, description="按模型筛选"),
|
||||
prompt_version: str | None = Query(None, description="按 prompt 版本筛选"),
|
||||
mode: str | None = Query(None, description="按模式筛选(single/multi)"),
|
||||
league_code: str | None = Query(None, description="按联赛代码筛选(如 E0/SP1)"),
|
||||
db: AsyncSession = Depends(get_db_read),
|
||||
):
|
||||
"""提供商/模型准确率对比。
|
||||
|
||||
P3-4: 默认评估最近 1000 条,可通过 limit 调整。
|
||||
支持按 provider / model / prompt_version / mode / league_code 筛选。
|
||||
只统计 status=success 且预测比分齐全的已结算预测,degraded 不计入。
|
||||
"""
|
||||
return await get_eval_summary(
|
||||
limit=limit,
|
||||
provider=provider,
|
||||
model=model,
|
||||
prompt_version=prompt_version,
|
||||
mode=mode,
|
||||
league_code=league_code,
|
||||
)
|
||||
|
||||
@@ -41,10 +41,19 @@ async def list_matches(
|
||||
last_date_str, last_id_str = cursor.split("|", 1)
|
||||
last_date = datetime.fromisoformat(last_date_str)
|
||||
last_id = int(last_id_str)
|
||||
q = q.where(
|
||||
(Match.match_date < last_date) |
|
||||
((Match.match_date == last_date) & (Match.id < last_id))
|
||||
)
|
||||
# 游标方向必须与排序方向一致:
|
||||
# - scheduled(ASC):取「更大」的未开赛场次
|
||||
# - 其它(DESC):取「更小」的已赛场次
|
||||
if status == "scheduled":
|
||||
q = q.where(
|
||||
(Match.match_date > last_date) |
|
||||
((Match.match_date == last_date) & (Match.id > last_id))
|
||||
)
|
||||
else:
|
||||
q = q.where(
|
||||
(Match.match_date < last_date) |
|
||||
((Match.match_date == last_date) & (Match.id < last_id))
|
||||
)
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
|
||||
|
||||
@@ -124,3 +124,8 @@ class SettleRequest(BaseModel):
|
||||
|
||||
class EvalSummaryOut(BaseModel):
|
||||
summary: list[dict[str, Any]]
|
||||
total_settled: int
|
||||
filtered_settled: int
|
||||
evaluated: int
|
||||
skipped_degraded: int
|
||||
skipped_incomplete: int = 0
|
||||
|
||||
Reference in New Issue
Block a user