fix(P0): 修复三处静默失效的阻断缺陷
P0-1 backtest 会话生命周期: - _get_historical_matches 加 selectinload(league/home_team/away_team), 并在 session 内物化为 BacktestCandidate 纯数据快照,避免 session 关闭后访问惰性关系抛 MissingGreenlet - except 收窄并改用 logger.exception 保留堆栈 - 移除未使用的 and_ / League 导入 P0-2 切片函数关系属性 MissingGreenlet: - models.py 为 Match.league/home_team/away_team/stats 声明 lazy=selectin - context_builder 的 _get_form/_get_h2h/_get_home_away 显式 selectinload (修复 form_slice/h2h_slice 恒定失败,被 fail-open 掩盖的问题) - repositories.find_by_teams_and_date 加 selectinload(Match.stats) P0-3 bzzoiro raw 变量泄漏导致血缘错乱: - normalized_matches 改为携带 (nm, raw) 元组,内层循环解包 - source_event_id / source_record_id 现在取到正确 event id - 已有比赛补建 stats 时补齐 source/source_event_id/retrieved_at/available_at - 抽取 _match_key()/_to_date() 统一日期键构造 (P1-6)
This commit is contained in:
+14
-4
@@ -75,10 +75,20 @@ class Match(Base):
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow, onupdate=_utcnow)
|
||||
|
||||
league: Mapped[League] = relationship(back_populates="matches")
|
||||
home_team: Mapped[Team] = relationship(foreign_keys=[home_team_id], back_populates="home_matches")
|
||||
away_team: Mapped[Team] = relationship(foreign_keys=[away_team_id], back_populates="away_matches")
|
||||
stats: Mapped["MatchStats | None"] = relationship(back_populates="match", cascade="all, delete-orphan")
|
||||
league: Mapped[League] = relationship(back_populates="matches", lazy="selectin")
|
||||
# lazy="selectin": 这些关系在业务里几乎总是一起读取(切片/回测/展示)。
|
||||
# 默认的 lazy="select" 在 async SQLAlchemy 下,于 session 之外或未显式
|
||||
# eager-load 时访问会抛 MissingGreenlet —— 已因此导致 form/stats/h2h
|
||||
# 三个专家切片静默失败。统一改为预加载,从根上消除这类问题。
|
||||
home_team: Mapped[Team] = relationship(
|
||||
foreign_keys=[home_team_id], back_populates="home_matches", lazy="selectin"
|
||||
)
|
||||
away_team: Mapped[Team] = relationship(
|
||||
foreign_keys=[away_team_id], back_populates="away_matches", lazy="selectin"
|
||||
)
|
||||
stats: Mapped["MatchStats | None"] = relationship(
|
||||
back_populates="match", cascade="all, delete-orphan", lazy="selectin"
|
||||
)
|
||||
predictions: Mapped[list["Prediction"]] = relationship(back_populates="match", cascade="all, delete-orphan")
|
||||
|
||||
__table_args__ = (
|
||||
|
||||
@@ -37,12 +37,17 @@ class MatchRepository:
|
||||
async def find_by_teams_and_date(
|
||||
self, league_id: int, home_team_id: int, away_team_id: int, date
|
||||
) -> Match | None:
|
||||
"""按联赛+主队+客队+日期查找比赛(天级匹配)。"""
|
||||
"""按联赛+主队+客队+日期查找比赛(天级匹配)。
|
||||
|
||||
预加载 stats:调用方(understat 回填)会读取 existing.stats,
|
||||
async session 下惰性加载会抛 MissingGreenlet。
|
||||
"""
|
||||
if hasattr(date, "date"):
|
||||
date = date.date()
|
||||
|
||||
stmt = (
|
||||
select(Match)
|
||||
.options(selectinload(Match.stats))
|
||||
.where(Match.league_id == league_id)
|
||||
.where(Match.home_team_id == home_team_id)
|
||||
.where(Match.away_team_id == away_team_id)
|
||||
|
||||
Reference in New Issue
Block a user