refactor: Sprint 3 - 引入 UnitOfWork + Repository 架构

新增:
- src/db/unit_of_work.py: UnitOfWork 事务封装
- src/db/repositories.py: Match/Team/League/Prediction Repository

重构:
- 删除 src/data/match_lookup.py(由 Repository 替代)
- 数据源(bzzoiro/understat/injuries)不再自行 commit
- API 路由(ingest)改用 UnitOfWork
- LLM 服务(predict/orchestrator/eval/backtest)改用 UnitOfWork

事务边界统一由调用方控制,数据层不再自行决定 commit。
This commit is contained in:
shangfangjian
2026-09-15 00:42:05 +08:00
parent cb36dc3ef9
commit 483cb956ba
11 changed files with 281 additions and 117 deletions
+8 -8
View File
@@ -11,8 +11,8 @@ from dataclasses import dataclass, field
from sqlalchemy import and_, select
from src.db.base import AsyncSessionLocal
from src.db.models import League, Match, Prediction
from src.db.models import League, Match
from src.db.unit_of_work import get_uow
from src.llm.eval import settle_prediction
from src.llm.predict import predict_match
@@ -33,7 +33,7 @@ class BacktestMatchResult:
pred_home: float | None
pred_away: float | None
pred_1x2: str | None
confidence: float | None
subjective_confidence: float | None
correct_1x2: bool
prediction_id: int
@@ -45,7 +45,7 @@ class BacktestSummary:
scored: int
accuracy_1x2: float | None = None
avg_score_rmse: float | None = None
avg_confidence: float | None = None
avg_subjective_confidence: float | None = None
calibration: list[dict] = field(default_factory=list)
results: list[BacktestMatchResult] = field(default_factory=list)
@@ -108,9 +108,9 @@ async def run_backtest(
Returns:
BacktestSummary 含逐场结果 + 汇总统计
"""
async with AsyncSessionLocal() as db:
async with get_uow() as uow:
matches = await _get_historical_matches(
db, league_id=league_id, date_from=date_from, date_to=date_to, limit=limit
uow.session, league_id=league_id, date_from=date_from, date_to=date_to, limit=limit
)
summary = BacktestSummary(total=len(matches), scored=0)
@@ -163,10 +163,10 @@ async def run_backtest(
if errors:
summary.avg_score_rmse = round(sum(errors) / len(errors), 2)
# 平均置信度
# 平均主观置信度
confs = [r.subjective_confidence for r in summary.results if r.subjective_confidence is not None]
if confs:
summary.avg_confidence = round(sum(confs) / len(confs), 2)
summary.avg_subjective_confidence = round(sum(confs) / len(confs), 2)
# 校准:按置信度分桶,看实际准确率是否匹配
summary.calibration = _compute_calibration(summary.results)