feat(ai): add tenant-safe hierarchical expense learning
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
"""add hierarchical expense memory governance fields
|
||||
|
||||
Revision ID: 20260716_0007
|
||||
Revises: 20260716_0006
|
||||
Create Date: 2026-07-16 12:05:00
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260716_0007"
|
||||
down_revision: str | None = "20260716_0006"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"memory_entries",
|
||||
sa.Column(
|
||||
"origin_type",
|
||||
sa.String(length=24),
|
||||
server_default="learned",
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"memory_entries",
|
||||
sa.Column("managed_by", sa.String(length=255), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"memory_entries",
|
||||
sa.Column("managed_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"memory_entries",
|
||||
sa.Column("management_reason", sa.String(length=255), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"memory_entries",
|
||||
sa.Column("policy_version", sa.String(length=64), nullable=True),
|
||||
)
|
||||
|
||||
op.drop_constraint(
|
||||
"ck_memory_entries_scope_type",
|
||||
"memory_entries",
|
||||
type_="check",
|
||||
)
|
||||
op.create_check_constraint(
|
||||
"ck_memory_entries_scope_type",
|
||||
"memory_entries",
|
||||
"scope_type IN ('user', 'department', 'enterprise')",
|
||||
)
|
||||
op.create_check_constraint(
|
||||
"ck_memory_entries_origin_type",
|
||||
"memory_entries",
|
||||
"origin_type IN ('learned', 'admin_managed')",
|
||||
)
|
||||
op.create_check_constraint(
|
||||
"ck_memory_entries_scope_origin",
|
||||
"memory_entries",
|
||||
"(scope_type = 'user' AND origin_type = 'learned') OR "
|
||||
"(scope_type IN ('department', 'enterprise') "
|
||||
"AND origin_type = 'admin_managed')",
|
||||
)
|
||||
op.create_check_constraint(
|
||||
"ck_memory_entries_enterprise_scope",
|
||||
"memory_entries",
|
||||
"scope_type != 'enterprise' OR scope_id = tenant_id",
|
||||
)
|
||||
op.create_check_constraint(
|
||||
"ck_memory_entries_management_audit",
|
||||
"memory_entries",
|
||||
"(origin_type = 'learned' AND managed_by IS NULL "
|
||||
"AND managed_at IS NULL AND management_reason IS NULL) OR "
|
||||
"(origin_type = 'admin_managed' AND managed_by IS NOT NULL "
|
||||
"AND length(trim(managed_by)) > 0 AND managed_at IS NOT NULL "
|
||||
"AND management_reason IS NOT NULL "
|
||||
"AND length(trim(management_reason)) > 0 "
|
||||
"AND policy_version IS NOT NULL AND length(trim(policy_version)) > 0)",
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_constraint(
|
||||
"ck_memory_entries_management_audit",
|
||||
"memory_entries",
|
||||
type_="check",
|
||||
)
|
||||
op.drop_constraint(
|
||||
"ck_memory_entries_enterprise_scope",
|
||||
"memory_entries",
|
||||
type_="check",
|
||||
)
|
||||
op.drop_constraint(
|
||||
"ck_memory_entries_scope_origin",
|
||||
"memory_entries",
|
||||
type_="check",
|
||||
)
|
||||
op.drop_constraint(
|
||||
"ck_memory_entries_origin_type",
|
||||
"memory_entries",
|
||||
type_="check",
|
||||
)
|
||||
op.drop_constraint(
|
||||
"ck_memory_entries_scope_type",
|
||||
"memory_entries",
|
||||
type_="check",
|
||||
)
|
||||
# 0006 只认识个人记忆。组织记忆无法无损映射回旧结构,因此降级时
|
||||
# 先解除组织版本间的自引用并删除组织作用域记录,个人记忆完整保留。
|
||||
op.execute(
|
||||
"UPDATE memory_entries SET superseded_by_id = NULL "
|
||||
"WHERE scope_type IN ('department', 'enterprise')"
|
||||
)
|
||||
op.execute(
|
||||
"DELETE FROM memory_entries "
|
||||
"WHERE scope_type IN ('department', 'enterprise')"
|
||||
)
|
||||
op.create_check_constraint(
|
||||
"ck_memory_entries_scope_type",
|
||||
"memory_entries",
|
||||
"scope_type = 'user'",
|
||||
)
|
||||
|
||||
op.drop_column("memory_entries", "policy_version")
|
||||
op.drop_column("memory_entries", "management_reason")
|
||||
op.drop_column("memory_entries", "managed_at")
|
||||
op.drop_column("memory_entries", "managed_by")
|
||||
op.drop_column("memory_entries", "origin_type")
|
||||
Reference in New Issue
Block a user