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:
+16
-24
@@ -1,6 +1,7 @@
|
||||
"""Bzzoiro 数据源:抓取 + 入库。
|
||||
|
||||
迁移自旧项目 app/data/sources/bzzoiro/,改成 async + 简化入库。
|
||||
使用 Repository 模式进行数据访问,不直接控制事务。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -18,10 +19,9 @@ from sqlalchemy import select
|
||||
|
||||
from src.core.config import settings
|
||||
from src.data.config import BZZOIRO_LEAGUE_IDS, LEAGUE_COUNTRIES, LEAGUE_NAMES, REQUEST_INTERVAL
|
||||
from src.data.match_lookup import find_existing_match, get_or_create_team
|
||||
from src.data.normalize import normalize_bzzoiro
|
||||
from src.data.sources import register
|
||||
from src.db.models import League, Match, MatchStats
|
||||
from src.db.models import League, Match, MatchStats, Team
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -125,7 +125,10 @@ class BzzoiroSource:
|
||||
date_to: str | None = None,
|
||||
status: str = "finished",
|
||||
) -> dict:
|
||||
"""采集 bzzoiro → 入库。返回统计。"""
|
||||
"""采集 bzzoiro → 入库。返回统计。
|
||||
|
||||
注意: 本方法不控制事务(commit/rollback),由调用方通过 UnitOfWork 控制。
|
||||
"""
|
||||
result: dict = {"leagues": {}, "total_inserted": 0, "total_updated": 0, "errors": []}
|
||||
|
||||
for code in leagues:
|
||||
@@ -159,29 +162,19 @@ class BzzoiroSource:
|
||||
all_team_names.add(nm.away_team)
|
||||
|
||||
if all_team_names:
|
||||
from sqlalchemy import select
|
||||
from src.db.models import Team
|
||||
stmt = select(Team).where(Team.name.in_(all_team_names))
|
||||
teams = (await db.execute(stmt)).scalars().all()
|
||||
team_name_to_id = {t.name: t.id for t in teams}
|
||||
|
||||
# 预加载已有比赛 (league_id + home_id + away_id + date)
|
||||
# 需要先获取球队 ID,所以分批处理
|
||||
date_strs = set()
|
||||
for raw in raw_events:
|
||||
nm = normalize_bzzoiro(raw, code)
|
||||
if nm and nm.date:
|
||||
date_strs.add(nm.date.date().isoformat() if hasattr(nm.date, "date") else str(nm.date))
|
||||
|
||||
if date_strs:
|
||||
from sqlalchemy import func
|
||||
stmt = (
|
||||
select(Match.home_team_id, Match.away_team_id, func.date(Match.match_date).label("d"))
|
||||
.where(Match.league_id == league.id)
|
||||
)
|
||||
rows = (await db.execute(stmt)).all()
|
||||
for row in rows:
|
||||
existing_match_keys.add((row.home_team_id, row.away_team_id, str(row.d)))
|
||||
# 预加载已有比赛
|
||||
from sqlalchemy import func
|
||||
stmt = (
|
||||
select(Match.home_team_id, Match.away_team_id, func.date(Match.match_date).label("d"))
|
||||
.where(Match.league_id == league.id)
|
||||
)
|
||||
rows = (await db.execute(stmt)).all()
|
||||
for row in rows:
|
||||
existing_match_keys.add((row.home_team_id, row.away_team_id, str(row.d)))
|
||||
|
||||
for raw in raw_events:
|
||||
try:
|
||||
@@ -255,7 +248,6 @@ class BzzoiroSource:
|
||||
league_r["inserted"] += 1
|
||||
else:
|
||||
# 已有比赛: 需要查询对象来更新
|
||||
# 注意: 这里为了简化仍查询一次,但只在"已有"时触发
|
||||
from sqlalchemy import func
|
||||
stmt = (
|
||||
select(Match)
|
||||
@@ -297,7 +289,7 @@ class BzzoiroSource:
|
||||
if changed:
|
||||
league_r["updated"] += 1
|
||||
|
||||
await db.commit()
|
||||
# 注意: 不在此处 commit,由调用方 UnitOfWork 控制事务
|
||||
result["leagues"][code] = league_r
|
||||
result["total_inserted"] += league_r["inserted"]
|
||||
result["total_updated"] += league_r["updated"]
|
||||
|
||||
Reference in New Issue
Block a user