"""报告结论反馈 + 全面报告预约留资(T-P3-21 最小可用版 / T-P3-22)。 rp_feedback — 维度级结论反馈(准/不准 + 可选修正文本); UniqueConstraint(report_id, dimension_key) 承载 upsert 语义: 同维度重复反馈 = 覆盖更新,不是堆行。 有意裁剪(T-P3-21 原文的「修正后置信度置 1.0 并即时写 L1 记忆」): L1 记忆系统未建,本迭代只落表,保证认同率/修正率两项指标 可从 SQL 直接算出,不回写报告结论。 rp_reservation — 预约留资;topic 预留通用性(首期只有 full_report)。 UniqueConstraint(user_id, topic) 承载幂等:重复预约返回已有 记录(created=false),不堆行。 指标口径:预约率 = rp_reservation(topic='full_report') 行数 ÷ 埋点 full_report.card_view 去重曝光数;决策门槛 ≥8% (产品方案 §2.2.3)。 Reversible: 是。 """ from __future__ import annotations from collections.abc import Sequence import sqlalchemy as sa from alembic import op revision: str = "0008" down_revision: str | None = "0007" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.create_table( "rp_feedback", sa.Column("feedback_id", sa.BigInteger(), nullable=False), sa.Column("report_id", sa.BigInteger(), nullable=False), sa.Column("user_id", sa.BigInteger(), nullable=False), sa.Column("dimension_key", sa.String(length=64), nullable=False), sa.Column("verdict", sa.String(length=16), nullable=False), sa.Column("correction", sa.Text(), nullable=True), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now() ), sa.Column( "updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now() ), sa.PrimaryKeyConstraint("feedback_id"), sa.UniqueConstraint("report_id", "dimension_key", name="uq_rp_feedback_report_dimension"), ) op.create_index("ix_rp_feedback_user_created", "rp_feedback", ["user_id", "created_at"]) op.create_table( "rp_reservation", sa.Column("reservation_id", sa.BigInteger(), nullable=False), sa.Column("user_id", sa.BigInteger(), nullable=False), sa.Column("topic", sa.String(length=32), nullable=False), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now() ), sa.PrimaryKeyConstraint("reservation_id"), sa.UniqueConstraint("user_id", "topic", name="uq_rp_reservation_user_topic"), ) def downgrade() -> None: op.drop_table("rp_reservation") op.drop_index("ix_rp_feedback_user_created", table_name="rp_feedback") op.drop_table("rp_feedback")