Files
Profeto/alembic/versions/0018_match_checks.py
T
shangfangjianandnew-provider/LongCat-2.0 < 6a4994097a feat: 采集管线基础设施全面接线
后端:
- bzzoiro events 采集成功后写入 RawEvent(Bronze 层)
- 采集失败写入 IngestFailure(死信队列)
- 写入 DataLineage(ETL 血缘追踪)
- stats 回填成功后写入 RawEvent + DataLineage
- 新增 DataQualityScheduler(每小时自动质量检查)
- 新增 /api/v1/admin/data-quality 端点
- 新增 /api/v1/admin/ingest-failures 端点(失败记录 + 重试)
- 新增 /api/v1/admin/llm/ping 端点(LLM 连通性测试)
- lifespan 启动 quality_scheduler
- Match 表补充 CHECK 约束(完赛必须有比分、状态枚举、半场≤全场)
- Prediction FK 加 ondelete="CASCADE" 与迁移对齐
- PredictRequest mode 加 pattern 校验

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

Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
2026-09-21 10:28:02 +08:00

53 lines
2.0 KiB
Python

"""Match 表补充 CHECK 约束:完赛必须有比分 + 状态枚举 + 半场≤全场
Revision ID: 0018_match_checks
Revises: 0017_mode_baseline
Create Date: 2026-09-21
Code Review DB-5:
- 已完赛比赛必须有比分(数据库级兜底)
- match_status 枚举约束
- 半场进球 ≤ 全场进球
"""
from typing import Sequence, Union
from alembic import op
# revision identifiers, used by Alembic.
revision: str = '0018_match_checks'
down_revision: Union[str, None] = '0017_mode_baseline'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# 先清理可能违反新约束的数据
op.execute("UPDATE matches SET match_status = 'scheduled' WHERE match_status NOT IN ('finished', 'scheduled', 'in_play', 'paused', 'postponed', 'cancelled', 'suspended')")
op.execute("UPDATE matches SET home_goals = 0, away_goals = 0 WHERE match_status = 'finished' AND (home_goals IS NULL OR away_goals IS NULL)")
# 添加 CHECK 约束
op.create_check_constraint(
'ck_matches_finished_has_score', 'matches',
"match_status <> 'finished' OR (home_goals IS NOT NULL AND away_goals IS NOT NULL)",
)
op.create_check_constraint(
'ck_matches_status_enum', 'matches',
"match_status IN ('finished', 'scheduled', 'in_play', 'paused', 'postponed', 'cancelled', 'suspended')",
)
op.create_check_constraint(
'ck_matches_home_ht_le_full', 'matches',
"home_ht_goals IS NULL OR home_goals IS NULL OR home_ht_goals <= home_goals",
)
op.create_check_constraint(
'ck_matches_away_ht_le_full', 'matches',
"away_ht_goals IS NULL OR away_goals IS NULL OR away_ht_goals <= away_goals",
)
def downgrade() -> None:
op.drop_constraint('ck_matches_away_ht_le_full', 'matches', type_='check')
op.drop_constraint('ck_matches_home_ht_le_full', 'matches', type_='check')
op.drop_constraint('ck_matches_status_enum', 'matches', type_='check')
op.drop_constraint('ck_matches_finished_has_score', 'matches', type_='check')