"""极简基线预测:主客场场均进球估计(不调用 LLM,不产生费用)。 用于与 LLM 预测做 eval 对比。这是最简单的统计基线,仅供研究参考, 文档与 reasoning 均明确标注「非投注建议」。 """ from __future__ import annotations import logging from datetime import datetime from sqlalchemy import case, func, select from src.db.base import AsyncSession, AsyncSessionLocal from src.db.models import Match from src.llm.predict import PredictResult logger = logging.getLogger(__name__) async def _avg_goals( db: AsyncSession, *, team_id: int, side: str, league_id: int, before: datetime | None, ) -> float: """某队在该联赛已完赛场次的场均进球(side=home/away)。""" if side == "home": goals_col = Match.home_goals team_col = Match.home_team_id else: goals_col = Match.away_goals team_col = Match.away_team_id stmt = ( select(func.avg(goals_col).label("avg_goals"), func.count().label("cnt")) .where( Match.match_status == "finished", team_col == team_id, Match.league_id == league_id, goals_col.is_not(None), ) ) if before is not None: stmt = stmt.where(Match.match_date < before) row = (await db.execute(stmt)).one() return float(row.avg_goals) if row.avg_goals is not None and row.cnt > 0 else 0.0 async def predict_baseline( match_id: int, *, backtest: bool = False, cutoff_at: datetime | None = None, ) -> PredictResult: """极简基线预测:主场场均进球 vs 客场场均进球。 返回 PredictResult(D2 统一结果类型): provider=model="baseline", 不调用 LLM,latency_ms≈0。 prediction_id 为占位 0 —— baseline 不在服务层落库, 由路由层 _persist_baseline 落库后取得真实 id。 """ async with AsyncSessionLocal() as db: match = await db.get(Match, match_id) if match is None: raise ValueError(f"match {match_id} not found") before = None if backtest and match.match_dt: from datetime import timedelta before = match.match_dt - timedelta(days=1) elif cutoff_at is not None: before = cutoff_at home_avg = await _avg_goals( db, team_id=match.home_team_id, side="home", league_id=match.league_id, before=before, ) away_avg = await _avg_goals( db, team_id=match.away_team_id, side="away", league_id=match.league_id, before=before, ) pred_home = max(0, min(10, round(home_avg))) pred_away = max(0, min(10, round(away_avg))) # 主场轻微加成(可选,这里保持极简不额外加权) if pred_home > pred_away: pred_1x2 = "1" elif pred_home < pred_away: pred_1x2 = "2" else: pred_1x2 = "X" return PredictResult( prediction_id=0, # 占位:真实 id 由路由层 _persist_baseline 落库后返回 provider="baseline", model="baseline", prompt_version="baseline_v1", mode="baseline", pred_home_goals=float(pred_home), pred_away_goals=float(pred_away), alt_pred_home_goals=None, alt_pred_away_goals=None, pred_1x2=pred_1x2, subjective_confidence=0.5, reasoning=( f"基线估计(非投注建议): 主队主场场均进球 {home_avg:.2f} → 预测 {pred_home}; " f"客队客场场均进球 {away_avg:.2f} → 预测 {pred_away}。" ), context="", # baseline 不构建 LLM 上下文 status="success", latency_ms=0, prompt_tokens=0, completion_tokens=0, raw={"home_avg": round(home_avg, 2), "away_avg": round(away_avg, 2)}, )