数据源统一为 bzzoiro,移除 Understat 与 injuries:
- 删除 src/data/understat.py / injuries.py 及相关测试
- 删除 injuries 模型与表;扩展 match_stats(xG 之外增加 big_chances/fouls)
- 新增 standings 表(联赛积分榜:位置/积分/xG/走势/分区)
- matches 表增加 source_event_id 血缘列,支撑统计回填
采集管线(bzzoiro 三条管线):
- events:赛程/比分(/events/),记录 source_event_id
- standings:积分榜快照(/leagues/{id}/standings/)
- stats:已完赛比赛详细统计回填(/events/{id}/stats/)
预测增强:
- standings_slice 替代 injuries_slice;积分榜专家替代阵容完整性专家
- AGENT_META runtime_config 同步更新
管理后台:
- ingest 路由重写为单一 bzzoiro 入口 + task 参数(events/standings/stats/all)
- 新增 /admin/data-completeness 数据完整性分析 API
- 数据源状态页简化为 bzzoiro 单源
前端:
- 采集页重构为任务驱动(比赛/积分榜/统计回填/全量)
- 新增「数据完整性」可视化页(覆盖率矩阵/字段完整率/健康摘要)
- 新增主站积分榜页(/standings)与比赛详情完整统计面板
- agent 名称同步更新(injuries→standings)
迁移 0015_bzzoiro_single_source 已在容器内验证通过,后端测试全部通过。
Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
69 lines
1.8 KiB
Python
69 lines
1.8 KiB
Python
"""数据源协议 + 注册表。
|
|
|
|
定义 DataSource 契约,并提供全局注册表供路由层分发。
|
|
每个比赛数据源实现该协议,注册后即可通过统一入口调度。
|
|
|
|
当前只有 bzzoiro 一个数据源(Understat / injuries 已移除),
|
|
保留协议与注册表是为了统一 ingest 调度入口的结构。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Protocol
|
|
|
|
from src.db.base import AsyncSession
|
|
|
|
|
|
class DataSource(Protocol):
|
|
"""比赛数据源契约:抓取 → 规范化 → 入库。"""
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
"""数据源标识名(用于路由/日志)。"""
|
|
...
|
|
|
|
async def ingest(self, db: AsyncSession, **kwargs) -> dict:
|
|
"""执行完整采集流程,返回统计。"""
|
|
...
|
|
|
|
|
|
# ── 注册表 ──
|
|
_SOURCES: dict[str, DataSource] = {}
|
|
|
|
|
|
def register(source):
|
|
"""装饰器:将数据源注册到全局注册表。
|
|
|
|
兼容类注册与实例注册:类会被实例化后存入(保证 get_source 返回实例)。
|
|
"""
|
|
obj = source() if isinstance(source, type) else source
|
|
_SOURCES[obj.name] = obj
|
|
return source
|
|
|
|
|
|
def get_source(name: str) -> DataSource:
|
|
"""按名获取数据源。"""
|
|
if not _SOURCES:
|
|
_load_sources()
|
|
if name not in _SOURCES:
|
|
raise ValueError(f"未知数据源: {name}")
|
|
return _SOURCES[name]
|
|
|
|
|
|
def list_sources() -> list[str]:
|
|
"""列出所有已注册数据源名。"""
|
|
if not _SOURCES:
|
|
_load_sources()
|
|
return list(_SOURCES.keys())
|
|
|
|
|
|
def _load_sources() -> None:
|
|
"""延迟导入数据源触发 @register(避免循环导入)。"""
|
|
from src.data.bzzoiro import BzzoiroSource # noqa: F811
|
|
|
|
|
|
# 保持向后兼容:模块加载时尝试加载(但不再强制)
|
|
try:
|
|
_load_sources()
|
|
except Exception:
|
|
pass
|