feat: P0-02/P0-03/P0-05 数据库时间语义与约束

P0-02: MatchStats 增加 source/source_record_id/retrieved_at/available_at
        context_builder 过滤统计数据时检查 available_at <= cutoff
P0-03: Prediction 增加 match_kickoff_at/prediction_created_at/prediction_cutoff_at
        明确区分比赛时间/预测创建时间/数据截止时间
P0-05: Prediction 增加 status 字段(success/failed/degraded)
        数据库 CHECK 约束

Alembic: 0005_prediction_status_and_stats_provenance

P1-02: injuries as_of 不再截断为 date,保持 datetime 精度
This commit is contained in:
shangfangjian
2026-09-15 02:04:25 +08:00
parent c657e04679
commit 0980c2242a
9 changed files with 122 additions and 13 deletions
+12 -2
View File
@@ -115,6 +115,11 @@ class MatchStats(Base):
home_red_cards: Mapped[int | None] = mapped_column(Integer)
away_red_cards: Mapped[int | None] = mapped_column(Integer)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow, onupdate=_utcnow)
# 数据血缘:追踪统计数据的来源和可用时间
source: Mapped[str | None] = mapped_column(String(30)) # bzzoiro / understat
source_record_id: Mapped[str | None] = mapped_column(String(100))
retrieved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
available_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
match: Mapped[Match] = relationship(back_populates="stats")
@@ -163,8 +168,12 @@ class Prediction(Base):
# multi-agent 模式: 各专家报告
mode: Mapped[str] = mapped_column(String(20), nullable=False, default="single")
agent_outputs: Mapped[dict | None] = mapped_column(JSONB)
# 回测/可复现性
cutoff_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
# 预测状态: success / failed / degraded
status: Mapped[str] = mapped_column(String(20), nullable=False, default="success")
# 时间语义:区分比赛时间、预测创建时间、数据截止时间
match_kickoff_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
prediction_created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
prediction_cutoff_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
input_hash: Mapped[str | None] = mapped_column(String(64))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
actual_home_goals: Mapped[int | None] = mapped_column(Integer)
@@ -182,4 +191,5 @@ class Prediction(Base):
CheckConstraint("subjective_confidence >= 0 AND subjective_confidence <= 1", name="ck_confidence_range"),
CheckConstraint("pred_1x2 IN ('1', 'X', '2')", name="ck_pred_1x2_enum"),
CheckConstraint("mode IN ('single', 'multi')", name="ck_mode_enum"),
CheckConstraint("status IN ('success', 'failed', 'degraded')", name="ck_status_enum"),
)