"""add semantic facts Revision ID: 20260415_add_semantic_facts Revises: 20260415_add_query_route_memories Create Date: 2026-04-15 """ from __future__ import annotations from alembic import op import sqlalchemy as sa revision = "20260415_add_semantic_facts" down_revision = "20260415_add_query_route_memories" branch_labels = None depends_on = None def _table_exists(bind, table_name: str) -> bool: return sa.inspect(bind).has_table(table_name) def upgrade() -> None: bind = op.get_bind() if not _table_exists(bind, "semantic_facts"): op.create_table( "semantic_facts", sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True), sa.Column("tenant_id", sa.BigInteger(), nullable=False), sa.Column("created_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False), sa.Column("updated_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False), sa.Column("entity_type", sa.String(length=32), nullable=False), sa.Column("entity_id", sa.BigInteger(), nullable=False), sa.Column("fact_namespace", sa.String(length=64), nullable=False), sa.Column("fact_key", sa.String(length=64), nullable=False), sa.Column("fact_value_text", sa.Text(), nullable=False), sa.Column("fact_value_norm", sa.String(length=255), nullable=False), sa.Column("value_type", sa.String(length=32), nullable=False), sa.Column("confidence", sa.Float(), nullable=False, server_default="0"), sa.Column("evidence_source_type", sa.String(length=32), nullable=True), sa.Column("evidence_source_id", sa.BigInteger(), nullable=True), sa.Column("evidence_excerpt", sa.Text(), nullable=True), sa.Column("extract_version", sa.String(length=64), nullable=True), sa.Column("freshness_at", sa.String(length=64), nullable=True), sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()), sa.UniqueConstraint( "tenant_id", "entity_type", "entity_id", "fact_key", "fact_value_norm", name="uq_semantic_facts_tenant_entity_key_norm", ), ) op.create_index("ix_semantic_facts_tenant_id", "semantic_facts", ["tenant_id"], unique=False) op.create_index("ix_semantic_facts_entity_type", "semantic_facts", ["entity_type"], unique=False) op.create_index("ix_semantic_facts_entity_id", "semantic_facts", ["entity_id"], unique=False) op.create_index("ix_semantic_facts_fact_namespace", "semantic_facts", ["fact_namespace"], unique=False) op.create_index("ix_semantic_facts_fact_key", "semantic_facts", ["fact_key"], unique=False) op.create_index("ix_semantic_facts_fact_value_norm", "semantic_facts", ["fact_value_norm"], unique=False) op.create_index("ix_semantic_facts_is_active", "semantic_facts", ["is_active"], unique=False) if not _table_exists(bind, "semantic_fact_extraction_runs"): op.create_table( "semantic_fact_extraction_runs", sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True), sa.Column("tenant_id", sa.BigInteger(), nullable=False), sa.Column("created_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False), sa.Column("updated_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False), sa.Column("entity_type", sa.String(length=32), nullable=False), sa.Column("entity_id", sa.BigInteger(), nullable=False), sa.Column("trigger_type", sa.String(length=32), nullable=False), sa.Column("status", sa.String(length=32), nullable=False), sa.Column("input_fingerprint", sa.String(length=64), nullable=True), sa.Column("facts_written", sa.Integer(), nullable=False, server_default="0"), sa.Column("failure_reason", sa.Text(), nullable=True), sa.Column("extract_version", sa.String(length=64), nullable=True), sa.Column("started_at", sa.String(length=64), nullable=True), sa.Column("finished_at", sa.String(length=64), nullable=True), ) op.create_index( "ix_semantic_fact_extraction_runs_tenant_id", "semantic_fact_extraction_runs", ["tenant_id"], unique=False, ) op.create_index( "ix_semantic_fact_extraction_runs_entity_type", "semantic_fact_extraction_runs", ["entity_type"], unique=False, ) op.create_index( "ix_semantic_fact_extraction_runs_entity_id", "semantic_fact_extraction_runs", ["entity_id"], unique=False, ) op.create_index( "ix_semantic_fact_extraction_runs_status", "semantic_fact_extraction_runs", ["status"], unique=False, ) op.create_index( "ix_semantic_fact_extraction_runs_input_fingerprint", "semantic_fact_extraction_runs", ["input_fingerprint"], unique=False, ) def downgrade() -> None: bind = op.get_bind() if _table_exists(bind, "semantic_fact_extraction_runs"): for index_name in ( "ix_semantic_fact_extraction_runs_input_fingerprint", "ix_semantic_fact_extraction_runs_status", "ix_semantic_fact_extraction_runs_entity_id", "ix_semantic_fact_extraction_runs_entity_type", "ix_semantic_fact_extraction_runs_tenant_id", ): op.drop_index(index_name, table_name="semantic_fact_extraction_runs") op.drop_table("semantic_fact_extraction_runs") if _table_exists(bind, "semantic_facts"): for index_name in ( "ix_semantic_facts_is_active", "ix_semantic_facts_fact_value_norm", "ix_semantic_facts_fact_key", "ix_semantic_facts_fact_namespace", "ix_semantic_facts_entity_id", "ix_semantic_facts_entity_type", "ix_semantic_facts_tenant_id", ): op.drop_index(index_name, table_name="semantic_facts") op.drop_table("semantic_facts")