PMFlow/backend/alembic/versions/20260415_add_semantic_facts.py

139 lines
6.2 KiB
Python

"""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")