"""Chapter evidence snapshots + normalized links (Phase C) Revision ID: 0010_ce_snapshots (short id: alembic_version.version_num is VARCHAR(32)) Revises: 0009_ce_bundle_mem_trace """ from typing import Sequence, Union import sqlalchemy as sa from sqlalchemy.dialects import postgresql from alembic import op revision: str = "0010_ce_snapshots" down_revision: Union[str, None] = "0009_ce_bundle_mem_trace" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def _has_table(name: str) -> bool: bind = op.get_bind() return sa.inspect(bind).has_table(name) def _has_column(table: str, column: str) -> bool: bind = op.get_bind() return any(c["name"] == column for c in sa.inspect(bind).get_columns(table)) def upgrade() -> None: if not _has_table("chapter_evidence_snapshots"): op.create_table( "chapter_evidence_snapshots", sa.Column("id", sa.String(), nullable=False), sa.Column("chapter_id", sa.String(), nullable=False), sa.Column("user_id", sa.String(), nullable=False), sa.Column("version_no", sa.Integer(), nullable=False), sa.Column("schema_version", sa.Integer(), nullable=False, server_default="1"), sa.Column("segment_ids", postgresql.JSON(astext_type=sa.Text()), nullable=True), sa.Column("conversation_ids", postgresql.JSON(astext_type=sa.Text()), nullable=True), sa.Column("story_ids", postgresql.JSON(astext_type=sa.Text()), nullable=True), sa.Column("memory_chunk_ids", postgresql.JSON(astext_type=sa.Text()), nullable=True), sa.Column("memory_fact_ids", postgresql.JSON(astext_type=sa.Text()), nullable=True), sa.Column("timeline_event_ids", postgresql.JSON(astext_type=sa.Text()), nullable=True), sa.Column("summary_ids", postgresql.JSON(astext_type=sa.Text()), nullable=True), sa.Column("notes", postgresql.JSON(astext_type=sa.Text()), nullable=True), sa.Column( "captured_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()"), ), sa.ForeignKeyConstraint( ["chapter_id"], ["chapters.id"], name="chapter_evidence_snapshots_chapter_id_fkey", ondelete="CASCADE", ), sa.ForeignKeyConstraint( ["user_id"], ["users.id"], name="chapter_evidence_snapshots_user_id_fkey", ), sa.PrimaryKeyConstraint("id", name="chapter_evidence_snapshots_pkey"), sa.UniqueConstraint( "chapter_id", "version_no", name="uq_chapter_evidence_snapshots_chapter_version", ), ) op.create_index( "ix_chapter_evidence_snapshots_chapter_id", "chapter_evidence_snapshots", ["chapter_id"], ) op.create_index( "ix_chapter_evidence_snapshots_user_id", "chapter_evidence_snapshots", ["user_id"], ) if not _has_table("chapter_evidence_links"): op.create_table( "chapter_evidence_links", sa.Column("id", sa.String(), nullable=False), sa.Column("chapter_id", sa.String(), nullable=False), sa.Column("evidence_type", sa.String(), nullable=False), sa.Column("evidence_id", sa.String(), nullable=False), sa.Column("role", sa.String(), nullable=True), sa.Column("weight", sa.Float(), nullable=True), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.ForeignKeyConstraint( ["chapter_id"], ["chapters.id"], name="chapter_evidence_links_chapter_id_fkey", ondelete="CASCADE", ), sa.PrimaryKeyConstraint("id", name="chapter_evidence_links_pkey"), ) op.create_index( "ix_chapter_evidence_links_chapter_id", "chapter_evidence_links", ["chapter_id"], ) if not _has_column("chapters", "current_evidence_snapshot_id"): op.add_column( "chapters", sa.Column("current_evidence_snapshot_id", sa.String(), nullable=True), ) op.create_foreign_key( "fk_chapters_current_evidence_snapshot_id", "chapters", "chapter_evidence_snapshots", ["current_evidence_snapshot_id"], ["id"], ondelete="SET NULL", ) def downgrade() -> None: if _has_column("chapters", "current_evidence_snapshot_id"): op.drop_constraint( "fk_chapters_current_evidence_snapshot_id", "chapters", type_="foreignkey", ) op.drop_column("chapters", "current_evidence_snapshot_id") if _has_table("chapter_evidence_links"): op.drop_index("ix_chapter_evidence_links_chapter_id", table_name="chapter_evidence_links") op.drop_table("chapter_evidence_links") if _has_table("chapter_evidence_snapshots"): op.drop_index("ix_chapter_evidence_snapshots_user_id", table_name="chapter_evidence_snapshots") op.drop_index("ix_chapter_evidence_snapshots_chapter_id", table_name="chapter_evidence_snapshots") op.drop_table("chapter_evidence_snapshots")