fix(P0-01): missing score 不得变 0:0 — score_status + CHECK 约束
新增 matches.score_status(known/missing/unknown): - 替换 ck_matches_finished_has_score 为 ck_matches_score_integrity: known → 必须有分; missing/unknown → goals 必须 NULL(不伪造 0:0) - normalize: 完赛缺分不再静默降级为 scheduled,改设 score_status=missing - events ingest: 创建/更新 Match 同步 score_status(比分由缺变 known / 确认缺分 missing) - slices(form/h2h/home_away)/backtest: 显式加 score_status='known' 过滤完赛样本 - 迁移 0022 回填现有数据(绝不 UPDATE goals=0) 测试 tests/test_p0_score_status.py(9/9):约束存在性/Match 构造/normalize 行为。 54 相关测试全绿。
This commit is contained in:
+15
-3
@@ -88,6 +88,9 @@ class Match(Base):
|
||||
index=True,
|
||||
)
|
||||
match_status: Mapped[str] = mapped_column(String(20), default="scheduled")
|
||||
# P0-01:比分可信度标记。known=有可靠比分;missing=完赛但缺分(保留 NULL 不伪造 0:0);
|
||||
# unknown=待定(无比分且未确认完赛)。禁止把缺分写成 0:0。
|
||||
score_status: Mapped[str] = mapped_column(String(20), server_default="unknown", nullable=False)
|
||||
home_goals: Mapped[int | None] = mapped_column(Integer)
|
||||
away_goals: Mapped[int | None] = mapped_column(Integer)
|
||||
home_ht_goals: Mapped[int | None] = mapped_column(Integer)
|
||||
@@ -127,10 +130,19 @@ class Match(Base):
|
||||
"match_date_date",
|
||||
unique=True,
|
||||
),
|
||||
# DB-5: 数据库级约束 — 已完赛比赛必须有比分
|
||||
# P0-01:比分可信度约束(替代原 ck_matches_finished_has_score):
|
||||
# - score_status=known → 必须有比分(非 NULL)
|
||||
# - score_status=missing → 必须 NULL(完赛缺分,禁止伪造 0:0)
|
||||
# - score_status=unknown → 必须 NULL
|
||||
CheckConstraint(
|
||||
"match_status <> 'finished' OR (home_goals IS NOT NULL AND away_goals IS NOT NULL)",
|
||||
name="ck_matches_finished_has_score",
|
||||
"score_status IN ('known', 'missing', 'unknown')",
|
||||
name="ck_matches_score_status_enum",
|
||||
),
|
||||
CheckConstraint(
|
||||
"match_status <> 'finished'"
|
||||
" OR (score_status = 'known' AND home_goals IS NOT NULL AND away_goals IS NOT NULL)"
|
||||
" OR (score_status IN ('missing', 'unknown') AND home_goals IS NULL AND away_goals IS NULL)",
|
||||
name="ck_matches_score_integrity",
|
||||
),
|
||||
CheckConstraint(
|
||||
"match_status IN ('finished', 'scheduled', 'in_play', 'paused', 'postponed', 'cancelled', 'suspended')",
|
||||
|
||||
Reference in New Issue
Block a user