refactor: 以 bzzoiro 为唯一数据源的全面重构
数据源统一为 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>>
This commit is contained in:
co-authored by
new-provider/LongCat-2.0 <
parent
ec8f36abb2
commit
f05dc1ae15
+68
-26
@@ -61,33 +61,16 @@ export async function fetchDashboard(): Promise<DashboardStats> {
|
||||
// ── 数据采集 ────────────────────────────────────────────────────
|
||||
|
||||
export async function triggerCollection(req: CollectionRequest): Promise<any> {
|
||||
const sourceMap: Record<string, { path: string; body: any }> = {
|
||||
bzzoiro: {
|
||||
path: `${API_BASE}/ingest/bzzoiro`,
|
||||
body: {
|
||||
leagues: req.leagues,
|
||||
date_from: req.date_from,
|
||||
date_to: req.date_to,
|
||||
status: req.status || undefined, // 空 = 已完赛 + 未开赛都采集
|
||||
},
|
||||
},
|
||||
understat: {
|
||||
path: `${API_BASE}/ingest/understat`,
|
||||
body: {
|
||||
league: req.league,
|
||||
season: req.season ? parseInt(req.season) : new Date().getFullYear(),
|
||||
},
|
||||
},
|
||||
injuries: {
|
||||
path: `${API_BASE}/ingest/injuries`,
|
||||
body: {
|
||||
date: req.date_from || new Date().toLocaleDateString('sv-SE'),
|
||||
},
|
||||
},
|
||||
const body: Record<string, any> = {
|
||||
leagues: req.leagues,
|
||||
date_from: req.date_from,
|
||||
date_to: req.date_to,
|
||||
status: req.status || undefined,
|
||||
task: req.task || 'events',
|
||||
limit: req.limit || 100,
|
||||
season: req.season || undefined,
|
||||
}
|
||||
const cfg = sourceMap[req.source]
|
||||
if (!cfg) throw new Error(`未知数据源: ${req.source}`)
|
||||
return api.post(cfg.path, cfg.body)
|
||||
return api.post(`${API_BASE}/ingest/bzzoiro`, body)
|
||||
}
|
||||
|
||||
// ── 预测管理 ────────────────────────────────────────────────────
|
||||
@@ -183,6 +166,65 @@ export async function fetchHealth(): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 数据完整性 ──────────────────────────────────────────────────
|
||||
|
||||
export interface DataCompletenessResponse {
|
||||
generated_at: string
|
||||
totals: { finished_matches: number; stats_rows: number; stats_coverage_pct: number }
|
||||
issues: string[]
|
||||
leagues: Array<{
|
||||
code: string
|
||||
name: string
|
||||
country?: string
|
||||
matches: { total: number; finished: number; scheduled: number; with_source_id: number; earliest_match?: string; latest_match?: string }
|
||||
stats: {
|
||||
rows: number
|
||||
fields: Record<string, { count: number; pct: number }>
|
||||
}
|
||||
standings: { rows: number; latest_retrieved?: string }
|
||||
}>
|
||||
}
|
||||
|
||||
export async function fetchDataCompleteness(): Promise<DataCompletenessResponse> {
|
||||
return api.get<DataCompletenessResponse>(`${API_BASE}/admin/data-completeness`)
|
||||
}
|
||||
|
||||
// ── 积分榜(主站 + 管理后台共用) ─────────────────────────────────
|
||||
|
||||
export interface StandingRow {
|
||||
position: number
|
||||
team: string
|
||||
team_en: string
|
||||
played: number
|
||||
won: number
|
||||
drawn: number
|
||||
lost: number
|
||||
goals_for: number
|
||||
goals_against: number
|
||||
goal_diff: number
|
||||
points: number
|
||||
xg_for: number | null
|
||||
xg_against: number | null
|
||||
form: string | null
|
||||
zone: string | null
|
||||
}
|
||||
|
||||
export interface StandingsLeague {
|
||||
league_code: string
|
||||
league_name: string
|
||||
season: string
|
||||
retrieved_at: string | null
|
||||
rows: StandingRow[]
|
||||
}
|
||||
|
||||
export async function fetchStandings(league?: string, season?: string): Promise<{ leagues: StandingsLeague[] }> {
|
||||
const sp = new URLSearchParams()
|
||||
if (league) sp.set('league', league)
|
||||
if (season) sp.set('season', season)
|
||||
const qs = sp.toString()
|
||||
return api.get<{ leagues: StandingsLeague[] }>(`${API_BASE}/standings${qs ? `?${qs}` : ''}`)
|
||||
}
|
||||
|
||||
// ── 数据源管理 ──────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user