feat: 数据采集管线基础设施接线 + 数据管线管理页

后端:
- bzzoiro 采集成功后写入 RawEvent(Bronze 层原始事件存档)
- 采集失败写入 IngestFailure(死信队列,支持重试)
- 写入 DataLineage(ETL 血缘追踪)
- 新增 DataQualityScheduler(每小时自动质量检查)
- 新增 /api/v1/admin/data-quality 端点(质量检查 + 手动触发)
- 新增 /api/v1/admin/ingest-failures 端点(失败记录 + 重试)
- 新增 /admin/llm/ping 端点(LLM 连通性测试,不依赖比赛)
- lifespan 启动 quality_scheduler

前端:
- 新增「数据管线」管理页(/admin/data-pipeline)
- 采集失败记录列表(状态/重试次数/错误类型)
- 数据质量检查结果(通过/未通过/严重度)
- 手动触发质量检查按钮
- 失败记录重试按钮
- 预测历史:比赛信息内嵌(日期/队名/主客徽标/赛果自动填充)
- 导航统一为 React Router Link
- AdminStats 类型扩展(matches/stats/standings 真实计数)

Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
This commit is contained in:
shangfangjian
2026-09-21 10:04:26 +08:00
co-authored by new-provider/LongCat-2.0 <
parent b6e367a640
commit ae89d0f04f
9 changed files with 567 additions and 4 deletions
+46
View File
@@ -441,3 +441,49 @@ export async function deleteSchedule(id: string): Promise<{ ok: boolean }> {
export async function runScheduleNow(id: string): Promise<{ ok: boolean; message: string }> {
return api.post<{ ok: boolean; message: string }>(`${API_BASE}/admin/schedules/${id}/run`)
}
// ── 数据管线(质量检查 + 失败重试) ──────────────────────────────
export interface IngestFailureItem {
id: number
source: string
entity_type: string
source_record_id?: string
error_type: string
error_detail?: string
retry_count: number
status: string
next_retry_at?: string | null
created_at?: string
}
export interface DataQualityCheckItem {
id: number
check_name: string
entity_type: string
passed: boolean
severity: string
detail?: Record<string, unknown> | null
checked_at?: string
}
export interface DataQualityResponse {
failures: IngestFailureItem[]
checks: DataQualityCheckItem[]
}
export async function fetchDataQuality(): Promise<DataQualityResponse> {
return api.get<DataQualityResponse>(`${API_BASE}/admin/data-quality`)
}
export async function runDataQualityCheck(): Promise<{ ok: boolean; checks: Array<{ name: string; passed: boolean }> }> {
return api.post<{ ok: boolean; checks: Array<{ name: string; passed: boolean }> }>(`${API_BASE}/admin/data-quality/run`)
}
export async function fetchIngestFailures(): Promise<IngestFailureItem[]> {
return api.get<IngestFailureItem[]>(`${API_BASE}/admin/ingest-failures`)
}
export async function retryIngestFailure(id: number): Promise<{ ok: boolean; message: string }> {
return api.post<{ ok: boolean; message: string }>(`${API_BASE}/admin/ingest-failures/${id}/retry`)
}