"""Add review API fields and review audits table. Revision ID: 0005_review_api_and_audit Revises: 0004_medium_ai_pipeline Create Date: 2026-08-28 """ import sqlalchemy as sa from alembic import op revision: str = "0005_review_api_and_audit" down_revision: str | None = "0004_medium_ai_pipeline" branch_labels: str | None = None depends_on: str | None = None def upgrade() -> None: op.add_column( "scoring_runs", sa.Column("version", sa.Integer(), nullable=False, server_default="1"), ) op.add_column("scoring_runs", sa.Column("effective_score", sa.Integer(), nullable=True)) op.add_column("scoring_runs", sa.Column("review_decision", sa.String(length=32), nullable=True)) op.add_column("scoring_runs", sa.Column("reviewed_by", sa.String(length=255), nullable=True)) op.add_column( "scoring_runs", sa.Column("reviewed_at", sa.DateTime(timezone=True), nullable=True) ) op.add_column("scoring_runs", sa.Column("review_note", sa.String(length=2000), nullable=True)) op.drop_constraint("uq_outbox_run_event_type", "outbox_events", type_="unique") op.create_index( "uq_outbox_terminal_run", "outbox_events", ["run_id", "event_type"], unique=True, postgresql_where=sa.text("event_type = 'lead_scoring.run.terminal'"), ) op.create_table( "review_audits", sa.Column("id", sa.String(length=36), nullable=False), sa.Column("run_id", sa.String(length=36), nullable=False), sa.Column("tenant_id", sa.String(length=255), nullable=False), sa.Column("decision", sa.String(length=32), nullable=False), sa.Column("actor_id", sa.String(length=255), nullable=False), sa.Column("actor_role", sa.String(length=32), nullable=False), sa.Column("idempotency_key", sa.String(length=255), nullable=False), sa.Column("payload_hash", sa.String(length=64), nullable=False), sa.Column("previous_version", sa.Integer(), nullable=False), sa.Column("new_version", sa.Integer(), nullable=False), sa.Column("calculated_score", sa.Integer(), nullable=True), sa.Column("effective_score", sa.Integer(), nullable=False), sa.Column("note", sa.String(length=2000), nullable=True), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint( "tenant_id", "idempotency_key", name="uq_review_audits_tenant_idempotency", ), ) def downgrade() -> None: op.drop_table("review_audits") op.drop_index( "uq_outbox_terminal_run", "outbox_events", postgresql_where=sa.text("event_type = 'lead_scoring.run.terminal'"), ) # Clean up non-terminal review outbox events before restoring pre-0005 unique constraint op.execute(sa.text("DELETE FROM outbox_events WHERE event_type = 'lead_scoring.run.reviewed'")) op.create_unique_constraint( "uq_outbox_run_event_type", "outbox_events", ["run_id", "event_type"] ) op.drop_column("scoring_runs", "review_note") op.drop_column("scoring_runs", "reviewed_at") op.drop_column("scoring_runs", "reviewed_by") op.drop_column("scoring_runs", "review_decision") op.drop_column("scoring_runs", "effective_score") op.drop_column("scoring_runs", "version")