"""initial schema: proposals, audit_log, mail_messages, content_items, daemon_runs Revision ID: 0001 Revises: Create Date: 2026-10-19 """ import sqlalchemy as sa from alembic import op revision = "0001" down_revision = None branch_labels = None depends_on = None def upgrade() -> None: op.create_table( "proposals", sa.Column("id", sa.Integer(), primary_key=True), sa.Column( "source", sa.Enum("SCANNER", "MANUAL", name="proposalsource"), nullable=False, ), sa.Column("kind", sa.String(length=64), nullable=False), sa.Column("title", sa.String(length=512), nullable=False), sa.Column("url", sa.String(length=1024), nullable=True), sa.Column("detail", sa.JSON(), nullable=False), sa.Column("risk_score", sa.Float(), nullable=False), sa.Column( "status", sa.Enum( "PROPOSED", "APPROVED", "REJECTED", "RUNNING", "DONE", "FAILED", name="proposalstatus", ), nullable=False, ), sa.Column("attempts", sa.Integer(), nullable=False), sa.Column("dedupe_key", sa.String(length=256), nullable=True), sa.Column("approved_by", sa.String(length=128), nullable=True), sa.Column("approved_at", sa.DateTime(timezone=True), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), sa.UniqueConstraint("dedupe_key"), ) op.create_index("ix_proposals_status", "proposals", ["status"]) op.create_table( "audit_log", sa.Column("id", sa.Integer(), primary_key=True), sa.Column("ts", sa.DateTime(timezone=True), nullable=False), sa.Column("actor", sa.String(length=128), nullable=False), sa.Column("proposal_id", sa.Integer(), nullable=True), sa.Column("action", sa.String(length=64), nullable=False), sa.Column("detail", sa.JSON(), nullable=False), sa.Column("result", sa.String(length=32), nullable=False), sa.ForeignKeyConstraint(["proposal_id"], ["proposals.id"], ondelete="SET NULL"), ) op.create_index("ix_audit_log_ts", "audit_log", ["ts"]) op.create_index("ix_audit_log_proposal_id", "audit_log", ["proposal_id"]) op.create_table( "mail_messages", sa.Column("id", sa.Integer(), primary_key=True), sa.Column( "direction", sa.Enum("INBOUND", "OUTBOUND", name="maildirection"), nullable=False, ), sa.Column("message_id", sa.String(length=998), nullable=True), sa.Column("in_reply_to", sa.String(length=998), nullable=True), sa.Column("from_addr", sa.String(length=320), nullable=True), sa.Column("to_addr", sa.String(length=320), nullable=True), sa.Column("subject", sa.String(length=998), nullable=True), sa.Column("body", sa.Text(), nullable=True), sa.Column( "classification", sa.Enum( "APPROVAL", "REJECTION", "QUESTION", "OTHER", "NEEDS_REVIEW", name="mailclassification", ), nullable=True, ), sa.Column("confidence", sa.Float(), nullable=False), sa.Column("proposal_id", sa.Integer(), nullable=True), sa.Column( "draft_status", sa.Enum( "NONE", "DRAFTED", "APPROVED", "SENT", "SKIPPED", name="draftstatus" ), nullable=False, ), sa.Column("draft_reply", sa.Text(), nullable=True), sa.Column("processed_at", sa.DateTime(timezone=True), nullable=True), sa.ForeignKeyConstraint(["proposal_id"], ["proposals.id"], ondelete="SET NULL"), ) op.create_index("ix_mail_messages_message_id", "mail_messages", ["message_id"]) op.create_index("ix_mail_messages_proposal_id", "mail_messages", ["proposal_id"]) op.create_table( "content_items", sa.Column("id", sa.Integer(), primary_key=True), sa.Column("theme", sa.String(length=128), nullable=False), sa.Column("channel", sa.String(length=32), nullable=False), sa.Column("caption", sa.Text(), nullable=False), sa.Column("media_path", sa.String(length=1024), nullable=True), sa.Column("link", sa.String(length=1024), nullable=True), sa.Column( "status", sa.Enum("DRAFT", "APPROVED", "PUBLISHED", name="contentstatus"), nullable=False, ), sa.Column("scheduled_for", sa.DateTime(timezone=True), nullable=True), sa.Column("published_at", sa.DateTime(timezone=True), nullable=True), sa.Column("post_ref", sa.String(length=512), nullable=True), sa.Column("error", sa.Text(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), ) op.create_index("ix_content_items_status", "content_items", ["status"]) op.create_table( "daemon_runs", sa.Column("id", sa.Integer(), primary_key=True), sa.Column("daemon", sa.String(length=32), nullable=False), sa.Column("started_at", sa.DateTime(timezone=True), nullable=False), sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True), sa.Column("status", sa.String(length=16), nullable=False), sa.Column("detail", sa.JSON(), nullable=False), ) op.create_index("ix_daemon_runs_daemon", "daemon_runs", ["daemon"]) def downgrade() -> None: op.drop_table("daemon_runs") op.drop_table("content_items") op.drop_table("mail_messages") op.drop_table("audit_log") op.drop_table("proposals") for enum_name in ( "proposalsource", "proposalstatus", "maildirection", "mailclassification", "draftstatus", "contentstatus", ): op.execute(f"DROP TYPE IF EXISTS {enum_name}")