refactor: context_builder 按 slice 拆到 src/llm/slices/ 包
单文件拆分(仅搬迁无逻辑修改): - common.py 共享类型/头信息/_outcome/_is_stats_available - form.py form_slice + _get_form - h2h.py h2h_slice + _get_h2h - stats.py stats_slice(复用 form._get_form) - home_away.py home_away_slice + _get_home_away - standings.py standings_slice - aggregate.py build_context context_builder.py 改为纯 re-export 门面,公开签名不变。 同步修复测试 patch 目标(p0_home_away/h2h_perspective/multi_agent_cutoff) 与 regressions 源码断言(读 slices/*.py)。 全量测试 270 通过。
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""E - 历史交锋切片: 交手史与胜负规律(h2h)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from src.db.base import AsyncSessionLocal
|
||||
from src.db.models import Match
|
||||
from src.llm.slices.common import MatchHeader, SliceResult
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
||||
async def h2h_slice(header: MatchHeader, *, limit: int = 8, before=None, db: AsyncSession | None = None) -> SliceResult:
|
||||
"""E - 历史交锋切片: 过去数年 + 近期交手数据,提取交手规律。before=match_date 用于回测。
|
||||
|
||||
db: 可选共享 session,避免每个切片独立建连(见 context_builder 模块 docstring)。
|
||||
"""
|
||||
if db is not None:
|
||||
h2h = await _get_h2h(db, header.home_team_id, header.away_team_id, before=before, limit=limit)
|
||||
else:
|
||||
async with AsyncSessionLocal() as new_db:
|
||||
h2h = await _get_h2h(new_db, header.home_team_id, header.away_team_id, before=before, limit=limit)
|
||||
lines = [f"── 历史交锋(近 {limit} 次) ──"]
|
||||
n_with_score = 0
|
||||
if h2h:
|
||||
# 从当前主队视角统计:判断当前主队在每场交锋中是主是客
|
||||
current_home_wins = current_home_draws = current_home_losses = 0
|
||||
for hm in h2h:
|
||||
d = hm.match_date.strftime("%Y-%m") if hm.match_date else "?"
|
||||
if hm.home_goals is not None:
|
||||
n_with_score += 1
|
||||
# 判断当前主队当时是主队还是客队
|
||||
if hm.home_team_id == header.home_team_id:
|
||||
# 当前主队当时是主队
|
||||
if hm.home_goals > hm.away_goals:
|
||||
current_home_wins += 1
|
||||
elif hm.home_goals == hm.away_goals:
|
||||
current_home_draws += 1
|
||||
else:
|
||||
current_home_losses += 1
|
||||
else:
|
||||
# 当前主队当时是客队(从客队视角看赛果)
|
||||
if hm.away_goals > hm.home_goals:
|
||||
current_home_wins += 1
|
||||
elif hm.away_goals == hm.home_goals:
|
||||
current_home_draws += 1
|
||||
else:
|
||||
current_home_losses += 1
|
||||
lines.append(f" {d}: {hm.home_team.name} {hm.home_goals}-{hm.away_goals} {hm.away_team.name}")
|
||||
else:
|
||||
lines.append(f" {d}: {hm.home_team.name} vs {hm.away_team.name} (无比分)")
|
||||
total = current_home_wins + current_home_draws + current_home_losses
|
||||
if total:
|
||||
lines.append(
|
||||
f" 总计 {total} 场(从当前主队 {header.home_name} 视角): "
|
||||
f"{current_home_wins}胜 {current_home_draws}平 {current_home_losses}负"
|
||||
)
|
||||
else:
|
||||
lines.append(" 无数据")
|
||||
# has_data 以「有比分的交锋」为准:仅有对阵无比分时不足以支撑分析
|
||||
return SliceResult(text="\n".join(lines), has_data=n_with_score > 0, n_records=n_with_score)
|
||||
|
||||
|
||||
async def _get_h2h(db, home_id: int, away_id: int, before, *, limit: int = 5) -> list[Match]:
|
||||
"""两队交锋史。需预加载 home_team / away_team(切片输出队名)。"""
|
||||
stmt = (
|
||||
select(Match)
|
||||
.options(
|
||||
selectinload(Match.home_team),
|
||||
selectinload(Match.away_team),
|
||||
)
|
||||
.where(Match.match_status == "finished")
|
||||
.where(Match.home_goals.is_not(None))
|
||||
.where(
|
||||
((Match.home_team_id == home_id) & (Match.away_team_id == away_id))
|
||||
| ((Match.home_team_id == away_id) & (Match.away_team_id == home_id))
|
||||
)
|
||||
.order_by(Match.match_date.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
if before is not None:
|
||||
stmt = stmt.where(Match.match_date < before)
|
||||
result = await db.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
Reference in New Issue
Block a user