"""B - 攻防数据切片: 进球/射门/控球/xG 聚合(stats)。""" from __future__ import annotations from typing import TYPE_CHECKING from src.db.base import AsyncSessionLocal from src.llm.slices.common import MatchHeader, SliceResult, _is_stats_available from src.llm.slices.form import _get_form if TYPE_CHECKING: from sqlalchemy.ext.asyncio import AsyncSession async def stats_slice(header: MatchHeader, *, limit: int = 10, before=None, db: AsyncSession | None = None) -> SliceResult: """B - 攻防数据切片: 进球、射门、控球,评估攻防强度。before=match_date 用于回测。 db: 可选共享 session,避免每个切片独立建连(见 context_builder 模块 docstring)。 """ if db is not None: home_form = await _get_form(db, header.home_team_id, before=before, limit=limit) away_form = await _get_form(db, header.away_team_id, before=before, limit=limit) else: async with AsyncSessionLocal() as new_db: home_form = await _get_form(new_db, header.home_team_id, before=before, limit=limit) away_form = await _get_form(new_db, header.away_team_id, before=before, limit=limit) lines = [f"── 攻防数据(近 {limit} 场) ──"] n_total = 0 # P0-1 修复:每场历史比赛必须根据「该队当时是主是客」判断 side, # 不能用本场 side 硬套 —— 否则进球/失球/xG 全部算反。 for label, name, form, team_id in ( ("主队", header.home_name, home_form, header.home_team_id), ("客队", header.away_name, away_form, header.away_team_id), ): if form: gf = ga = shots = sot = poss = xg = xga = 0 n = n_shots = n_poss = n_xg = 0 for fm in form: if fm.home_goals is None: continue is_home = (fm.home_team_id == team_id) gf += fm.home_goals if is_home else fm.away_goals ga += fm.away_goals if is_home else fm.home_goals n += 1 # 只使用 cutoff 之前已可用的统计数据 if fm.stats and _is_stats_available(fm.stats, before): if fm.stats.home_shots is not None: shots += fm.stats.home_shots if is_home else fm.stats.away_shots sot += fm.stats.home_shots_on_target if is_home else fm.stats.away_shots_on_target n_shots += 1 if fm.stats.home_possession is not None: poss += fm.stats.home_possession if is_home else (100 - fm.stats.home_possession) n_poss += 1 if fm.stats.home_xg is not None: xg += fm.stats.home_xg if is_home else fm.stats.away_xg xga += fm.stats.away_xg if is_home else fm.stats.home_xg n_xg += 1 n_total += n if n > 0: lines.append(f" {label} {name}:") lines.append(f" 场均进球 {gf/n:.2f}, 场均失球 {ga/n:.2f}") if n_shots: lines.append(f" 场均射门 {shots/n_shots:.1f}, 射正 {sot/n_shots:.1f}") if n_poss: lines.append(f" 平均控球 {poss/n_poss:.1f}%") if n_xg: lines.append(f" 场均 xG {xg/n_xg:.2f}, 场均被 xG {xga/n_xg:.2f}") else: lines.append(f" {label} {name}: 无比分数据") else: lines.append(f" {label} {name}: 无数据") return SliceResult(text="\n".join(lines), has_data=n_total > 0, n_records=n_total)