"""评估路由。""" from __future__ import annotations import logging from fastapi import APIRouter, Depends, HTTPException, Query from src.api.deps import require_admin from src.api.schemas import EvalSummaryOut, SettleRequest from src.db.base import AsyncSession, get_db, get_db_read from src.llm.eval import get_eval_summary, settle_prediction logger = logging.getLogger(__name__) 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 的预测无法结算(返回 400); 记录不存在返回 404。 """ try: pred = await settle_prediction(req.prediction_id, req.home_goals, req.away_goals) return {"id": pred.id, "settled": pred.settled} except ValueError as e: msg = str(e) # degraded/failed 拒绝:明确的 400,而非与"未找到"混为一谈 if "无法结算" in msg: logger.warning("settle rejected: %s", msg) raise HTTPException(400, msg) logger.warning("settle failed: %s", msg) raise HTTPException(404, "预测记录不存在") except Exception as e: logger.exception("settle error") raise HTTPException(500, "回填失败,请查看服务器日志") @router.get("/eval/summary", response_model=EvalSummaryOut, dependencies=[Depends(require_admin)]) 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, )