"""Report tables for the Sprint-3 (M3) parallel batch (架构文档 §6.2 report 域). 三张表一次落齐,T15(编排)不再写迁移: rp_report — result JSONB 承载流水线中间态与最终产物(按 stage 键增量更新) rp_input — questionnaire / photo_meta / derived_features(视觉中间态,T-P3-09) rp_share — V1 只建结构(路由属后续任务) Reversible: 是。 """ from __future__ import annotations from collections.abc import Sequence import sqlalchemy as sa from alembic import op from sqlalchemy.dialects import postgresql revision: str = "0006" down_revision: str | None = "0005" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.create_table( "rp_report", sa.Column("report_id", sa.BigInteger(), nullable=False), sa.Column("user_id", sa.BigInteger(), nullable=False), sa.Column("tier", sa.String(length=8), nullable=False, server_default=sa.text("'basic'")), sa.Column("status", sa.String(length=16), nullable=False, server_default=sa.text("'pending'")), sa.Column("version", sa.Integer(), nullable=False, server_default=sa.text("1")), sa.Column("result", postgresql.JSONB(), nullable=False), sa.Column("model_info", postgresql.JSONB(), nullable=False), sa.Column("content_code", sa.String(length=32), nullable=True), sa.Column("rendered_asset_key", sa.String(length=255), nullable=True), sa.Column("cost_cents", sa.Numeric(14, 6), nullable=False, server_default=sa.text("0")), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True), sa.PrimaryKeyConstraint("report_id"), ) op.create_index("ix_rp_report_user_created", "rp_report", ["user_id", "created_at"]) op.create_table( "rp_input", sa.Column("input_id", sa.BigInteger(), nullable=False), sa.Column("report_id", sa.BigInteger(), nullable=False), sa.Column("questionnaire", postgresql.JSONB(), nullable=False), sa.Column("photo_meta", postgresql.JSONB(), nullable=False), sa.Column("derived_features", postgresql.JSONB(), nullable=False), sa.PrimaryKeyConstraint("input_id"), sa.UniqueConstraint("report_id", name="uq_rp_input_report"), sa.ForeignKeyConstraint(["report_id"], ["rp_report.report_id"], ondelete="CASCADE"), ) op.create_table( "rp_share", sa.Column("share_id", sa.BigInteger(), nullable=False), sa.Column("report_id", sa.BigInteger(), nullable=False), sa.Column("share_token", sa.String(length=64), nullable=False), sa.Column("expire_at", sa.DateTime(timezone=True), nullable=False), sa.Column("view_count", sa.Integer(), nullable=False, server_default=sa.text("0")), sa.Column("revoked", sa.Boolean(), nullable=False, server_default=sa.text("false")), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.PrimaryKeyConstraint("share_id"), sa.UniqueConstraint("share_token", name="uq_rp_share_token"), sa.ForeignKeyConstraint(["report_id"], ["rp_report.report_id"], ondelete="CASCADE"), ) def downgrade() -> None: op.drop_table("rp_share") op.drop_table("rp_input") op.drop_index("ix_rp_report_user_created", table_name="rp_report") op.drop_table("rp_report")