497 lines
30 KiB
Python
497 lines
30 KiB
Python
"""add ai project control tables
|
|
|
|
Revision ID: 20260421_add_ai_project_control_tables
|
|
Revises: 20260415_add_semantic_facts
|
|
Create Date: 2026-04-21
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
try:
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
except Exception: # pragma: no cover
|
|
JSONB = sa.JSON
|
|
|
|
|
|
revision = "20260421_add_ai_project_control_tables"
|
|
down_revision = "20260415_add_semantic_facts"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def _json_type(bind):
|
|
return JSONB() if bind.dialect.name == "postgresql" else sa.JSON()
|
|
|
|
|
|
def _table_exists(bind, table_name: str) -> bool:
|
|
return sa.inspect(bind).has_table(table_name)
|
|
|
|
|
|
def _seed_model_catalog(bind) -> None:
|
|
table = sa.table(
|
|
"model_catalog_items",
|
|
sa.column("tenant_id", sa.BigInteger()),
|
|
sa.column("provider", sa.String()),
|
|
sa.column("model_key", sa.String()),
|
|
sa.column("display_name", sa.String()),
|
|
sa.column("version", sa.String()),
|
|
sa.column("modalities", _json_type(bind)),
|
|
sa.column("capability_tags", _json_type(bind)),
|
|
sa.column("strengths", _json_type(bind)),
|
|
sa.column("limitations", _json_type(bind)),
|
|
sa.column("compliance_status", sa.String()),
|
|
sa.column("filing_status", sa.String()),
|
|
sa.column("input_cost_per_mtok", sa.Numeric(12, 4)),
|
|
sa.column("output_cost_per_mtok", sa.Numeric(12, 4)),
|
|
sa.column("official_docs_url", sa.Text()),
|
|
sa.column("is_active", sa.Boolean()),
|
|
)
|
|
op.bulk_insert(
|
|
table,
|
|
[
|
|
{
|
|
"tenant_id": 1,
|
|
"provider": "阿里云",
|
|
"model_key": "qwen-plus",
|
|
"display_name": "通义千问 Plus",
|
|
"version": "latest",
|
|
"modalities": ["text", "image"],
|
|
"capability_tags": ["general", "rag", "reasoning", "agent", "code"],
|
|
"strengths": ["中文通用能力强", "RAG 适配稳定", "工具调用成熟"],
|
|
"limitations": ["不适合视频生成"],
|
|
"compliance_status": "ready",
|
|
"filing_status": "filed",
|
|
"input_cost_per_mtok": 0.8,
|
|
"output_cost_per_mtok": 2.0,
|
|
"official_docs_url": "https://help.aliyun.com/zh/model-studio/",
|
|
"is_active": True,
|
|
},
|
|
{
|
|
"tenant_id": 1,
|
|
"provider": "火山引擎",
|
|
"model_key": "doubao-pro",
|
|
"display_name": "豆包 Pro",
|
|
"version": "latest",
|
|
"modalities": ["text", "image"],
|
|
"capability_tags": ["general", "rag", "reasoning", "multimodal"],
|
|
"strengths": ["长文本稳", "中文体验好", "成本适中"],
|
|
"limitations": ["代码生成弱于专用模型"],
|
|
"compliance_status": "ready",
|
|
"filing_status": "filed",
|
|
"input_cost_per_mtok": 0.8,
|
|
"output_cost_per_mtok": 2.0,
|
|
"official_docs_url": "https://www.volcengine.com/product/doubao",
|
|
"is_active": True,
|
|
},
|
|
{
|
|
"tenant_id": 1,
|
|
"provider": "百度智能云",
|
|
"model_key": "ernie-4.0-turbo",
|
|
"display_name": "文心一言 4.0 Turbo",
|
|
"version": "latest",
|
|
"modalities": ["text", "image"],
|
|
"capability_tags": ["general", "rag", "reasoning", "multimodal"],
|
|
"strengths": ["企业落地常见", "多模态均衡"],
|
|
"limitations": ["综合成本偏高"],
|
|
"compliance_status": "ready",
|
|
"filing_status": "filed",
|
|
"input_cost_per_mtok": 1.2,
|
|
"output_cost_per_mtok": 3.2,
|
|
"official_docs_url": "https://cloud.baidu.com/product/wenxinworkshop",
|
|
"is_active": True,
|
|
},
|
|
{
|
|
"tenant_id": 1,
|
|
"provider": "DeepSeek",
|
|
"model_key": "deepseek-chat",
|
|
"display_name": "DeepSeek Chat",
|
|
"version": "latest",
|
|
"modalities": ["text"],
|
|
"capability_tags": ["general", "reasoning", "code", "agent"],
|
|
"strengths": ["代码与推理性价比高", "适合技术团队"],
|
|
"limitations": ["需要留意合规和交付边界"],
|
|
"compliance_status": "review_required",
|
|
"filing_status": "unknown",
|
|
"input_cost_per_mtok": 0.27,
|
|
"output_cost_per_mtok": 1.1,
|
|
"official_docs_url": "https://platform.deepseek.com/",
|
|
"is_active": True,
|
|
},
|
|
{
|
|
"tenant_id": 1,
|
|
"provider": "Moonshot AI",
|
|
"model_key": "kimi-k2",
|
|
"display_name": "Kimi K2",
|
|
"version": "latest",
|
|
"modalities": ["text"],
|
|
"capability_tags": ["general", "long_context", "rag", "reasoning"],
|
|
"strengths": ["长上下文强", "资料理解稳定"],
|
|
"limitations": ["对多模态场景支持有限"],
|
|
"compliance_status": "ready",
|
|
"filing_status": "filed",
|
|
"input_cost_per_mtok": 1.0,
|
|
"output_cost_per_mtok": 3.0,
|
|
"official_docs_url": "https://platform.moonshot.cn/",
|
|
"is_active": True,
|
|
},
|
|
],
|
|
)
|
|
|
|
|
|
def upgrade() -> None:
|
|
bind = op.get_bind()
|
|
json_type = _json_type(bind)
|
|
|
|
if not _table_exists(bind, "model_catalog_items"):
|
|
op.create_table(
|
|
"model_catalog_items",
|
|
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("provider", sa.String(length=120), nullable=False),
|
|
sa.Column("model_key", sa.String(length=120), nullable=False),
|
|
sa.Column("display_name", sa.String(length=255), nullable=False),
|
|
sa.Column("version", sa.String(length=120), nullable=False),
|
|
sa.Column("modalities", json_type, nullable=False),
|
|
sa.Column("capability_tags", json_type, nullable=False),
|
|
sa.Column("strengths", json_type, nullable=False),
|
|
sa.Column("limitations", json_type, nullable=False),
|
|
sa.Column("compliance_status", sa.String(length=50), nullable=False, server_default="review_required"),
|
|
sa.Column("filing_status", sa.String(length=50), nullable=False, server_default="unknown"),
|
|
sa.Column("input_cost_per_mtok", sa.DECIMAL(12, 4), nullable=True),
|
|
sa.Column("output_cost_per_mtok", sa.DECIMAL(12, 4), nullable=True),
|
|
sa.Column("official_docs_url", sa.Text(), nullable=True),
|
|
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
|
|
sa.UniqueConstraint(
|
|
"tenant_id",
|
|
"provider",
|
|
"model_key",
|
|
"version",
|
|
name="uq_model_catalog_items_tenant_provider_key_version",
|
|
),
|
|
)
|
|
op.create_index("ix_model_catalog_items_tenant_id", "model_catalog_items", ["tenant_id"], unique=False)
|
|
op.create_index("ix_model_catalog_items_provider", "model_catalog_items", ["provider"], unique=False)
|
|
op.create_index("ix_model_catalog_items_model_key", "model_catalog_items", ["model_key"], unique=False)
|
|
op.create_index("ix_model_catalog_items_is_active", "model_catalog_items", ["is_active"], unique=False)
|
|
_seed_model_catalog(bind)
|
|
|
|
table_specs = {
|
|
"project_model_bindings": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("charter_id", sa.BigInteger(), sa.ForeignKey("truth_nodes.id"), nullable=True),
|
|
sa.Column("model_catalog_item_id", sa.BigInteger(), sa.ForeignKey("model_catalog_items.id"), nullable=False),
|
|
sa.Column("selected_by_user_id", sa.BigInteger(), sa.ForeignKey("users.id"), nullable=True),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="active"),
|
|
sa.Column("selection_reason", json_type, nullable=False),
|
|
sa.Column("estimated_daily_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("estimated_monthly_cost_usd", sa.DECIMAL(12, 2), nullable=True),
|
|
sa.Column("active_from", sa.TIMESTAMP(), nullable=True),
|
|
sa.Column("archived_at", sa.TIMESTAMP(), nullable=True),
|
|
],
|
|
"model_recommendation_snapshots": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("charter_id", sa.BigInteger(), sa.ForeignKey("truth_nodes.id"), nullable=True),
|
|
sa.Column("recommendation_scope", sa.String(length=32), nullable=False, server_default="charter"),
|
|
sa.Column("snapshot_input", json_type, nullable=False),
|
|
sa.Column("recommendations", json_type, nullable=False),
|
|
sa.Column("top_model_catalog_item_id", sa.BigInteger(), sa.ForeignKey("model_catalog_items.id"), nullable=True),
|
|
sa.Column("generated_by", sa.String(length=32), nullable=False, server_default="rule_engine"),
|
|
],
|
|
"model_usage_events": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("project_model_binding_id", sa.BigInteger(), sa.ForeignKey("project_model_bindings.id"), nullable=True),
|
|
sa.Column("source_type", sa.String(length=32), nullable=False, server_default="manual"),
|
|
sa.Column("event_key", sa.String(length=255), nullable=True),
|
|
sa.Column("input_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("output_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("cost_usd", sa.DECIMAL(12, 4), nullable=False, server_default="0"),
|
|
sa.Column("event_payload", json_type, nullable=False),
|
|
sa.Column("occurred_at", sa.TIMESTAMP(), nullable=True),
|
|
sa.UniqueConstraint("tenant_id", "event_key", name="uq_model_usage_events_tenant_event_key"),
|
|
],
|
|
"model_eval_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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("charter_id", sa.BigInteger(), sa.ForeignKey("truth_nodes.id"), nullable=True),
|
|
sa.Column("project_model_binding_id", sa.BigInteger(), sa.ForeignKey("project_model_bindings.id"), nullable=True),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="pending"),
|
|
sa.Column("success_criteria", json_type, nullable=False),
|
|
sa.Column("test_cases", json_type, nullable=False),
|
|
sa.Column("summary", json_type, nullable=False),
|
|
sa.Column("score", sa.DECIMAL(5, 2), nullable=True),
|
|
],
|
|
"prompt_packages": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("charter_id", sa.BigInteger(), sa.ForeignKey("truth_nodes.id"), nullable=False),
|
|
sa.Column("task_id", sa.BigInteger(), sa.ForeignKey("tasks.id"), nullable=True),
|
|
sa.Column("project_model_binding_id", sa.BigInteger(), sa.ForeignKey("project_model_bindings.id"), nullable=True),
|
|
sa.Column("package_kind", sa.String(length=32), nullable=False, server_default="development"),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="active"),
|
|
sa.Column("package_text", sa.Text(), nullable=False),
|
|
sa.Column("package_payload", json_type, nullable=False),
|
|
sa.Column("generated_from", json_type, nullable=False),
|
|
],
|
|
"dev_workspace_bindings": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("user_id", sa.BigInteger(), sa.ForeignKey("users.id"), nullable=False),
|
|
sa.Column("workspace_key", sa.String(length=255), nullable=False),
|
|
sa.Column("repo_root", sa.Text(), nullable=True),
|
|
sa.Column("editor_type", sa.String(length=32), nullable=False, server_default="vscode"),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="active"),
|
|
sa.Column("last_seen_at", sa.TIMESTAMP(), nullable=True),
|
|
sa.UniqueConstraint("tenant_id", "workspace_key", name="uq_dev_workspace_bindings_tenant_workspace_key"),
|
|
],
|
|
"repo_bindings": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("provider", sa.String(length=32), nullable=False, server_default="github"),
|
|
sa.Column("external_repo_id", sa.String(length=255), nullable=True),
|
|
sa.Column("owner", sa.String(length=255), nullable=False),
|
|
sa.Column("repo_name", sa.String(length=255), nullable=False),
|
|
sa.Column("default_branch", sa.String(length=255), nullable=False, server_default="main"),
|
|
sa.Column("installation_id", sa.String(length=255), nullable=True),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="active"),
|
|
sa.Column("webhook_secret_hint", sa.String(length=255), nullable=True),
|
|
sa.UniqueConstraint("tenant_id", "provider", "owner", "repo_name", name="uq_repo_bindings_tenant_provider_repo"),
|
|
],
|
|
"task_code_links": [
|
|
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("task_id", sa.BigInteger(), sa.ForeignKey("tasks.id"), nullable=False),
|
|
sa.Column("repo_binding_id", sa.BigInteger(), sa.ForeignKey("repo_bindings.id"), nullable=False),
|
|
sa.Column("branch_name", sa.String(length=255), nullable=True),
|
|
sa.Column("pr_number", sa.Integer(), nullable=True),
|
|
sa.Column("last_commit_sha", sa.String(length=255), nullable=True),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="linked"),
|
|
sa.UniqueConstraint(
|
|
"tenant_id",
|
|
"task_id",
|
|
"repo_binding_id",
|
|
"branch_name",
|
|
name="uq_task_code_links_tenant_task_repo_branch",
|
|
),
|
|
],
|
|
"progress_events": [
|
|
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("task_id", sa.BigInteger(), sa.ForeignKey("tasks.id"), nullable=False),
|
|
sa.Column("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("user_id", sa.BigInteger(), sa.ForeignKey("users.id"), nullable=True),
|
|
sa.Column("repo_binding_id", sa.BigInteger(), sa.ForeignKey("repo_bindings.id"), nullable=True),
|
|
sa.Column("workspace_binding_id", sa.BigInteger(), sa.ForeignKey("dev_workspace_bindings.id"), nullable=True),
|
|
sa.Column("event_type", sa.String(length=32), nullable=False),
|
|
sa.Column("source", sa.String(length=32), nullable=False, server_default="manual"),
|
|
sa.Column("derived_task_status", sa.String(length=32), nullable=True),
|
|
sa.Column("summary", sa.Text(), nullable=True),
|
|
sa.Column("payload", json_type, nullable=False),
|
|
sa.Column("occurred_at", sa.TIMESTAMP(), nullable=True),
|
|
],
|
|
"dev_sessions": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("user_id", sa.BigInteger(), sa.ForeignKey("users.id"), nullable=False),
|
|
sa.Column("workspace_binding_id", sa.BigInteger(), sa.ForeignKey("dev_workspace_bindings.id"), nullable=True),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="active"),
|
|
sa.Column("started_at", sa.TIMESTAMP(), nullable=True),
|
|
sa.Column("heartbeat_at", sa.TIMESTAMP(), nullable=True),
|
|
sa.Column("ended_at", sa.TIMESTAMP(), nullable=True),
|
|
sa.Column("session_payload", json_type, nullable=False),
|
|
],
|
|
"release_validation_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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("charter_id", sa.BigInteger(), sa.ForeignKey("truth_nodes.id"), nullable=True),
|
|
sa.Column("release_node_id", sa.BigInteger(), sa.ForeignKey("truth_nodes.id"), nullable=True),
|
|
sa.Column("project_model_binding_id", sa.BigInteger(), sa.ForeignKey("project_model_bindings.id"), nullable=True),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="pending"),
|
|
sa.Column("score", sa.DECIMAL(5, 2), nullable=True),
|
|
sa.Column("key_metrics", json_type, nullable=False),
|
|
sa.Column("risk_items", json_type, nullable=False),
|
|
sa.Column("rollback_plan", json_type, nullable=False),
|
|
sa.Column("validation_summary", json_type, nullable=False),
|
|
],
|
|
"feedback_ingests": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("charter_id", sa.BigInteger(), sa.ForeignKey("truth_nodes.id"), nullable=True),
|
|
sa.Column("ingested_by_user_id", sa.BigInteger(), sa.ForeignKey("users.id"), nullable=True),
|
|
sa.Column("source_type", sa.String(length=32), nullable=False, server_default="manual"),
|
|
sa.Column("title", sa.String(length=255), nullable=True),
|
|
sa.Column("content", sa.Text(), nullable=False),
|
|
sa.Column("metrics", json_type, nullable=False),
|
|
sa.Column("samples", json_type, nullable=False),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="active"),
|
|
],
|
|
"iteration_recommendations": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("charter_id", sa.BigInteger(), sa.ForeignKey("truth_nodes.id"), nullable=True),
|
|
sa.Column("feedback_ingest_id", sa.BigInteger(), sa.ForeignKey("feedback_ingests.id"), nullable=True),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="draft"),
|
|
sa.Column("priority", sa.String(length=32), nullable=False, server_default="medium"),
|
|
sa.Column("recommendation_summary", json_type, nullable=False),
|
|
sa.Column("proposed_charter_patch", json_type, nullable=False),
|
|
sa.Column("proposed_tasks", json_type, nullable=False),
|
|
],
|
|
"revenue_events": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("source_type", sa.String(length=32), nullable=False, server_default="manual"),
|
|
sa.Column("currency", sa.String(length=16), nullable=False, server_default="USD"),
|
|
sa.Column("amount_usd", sa.DECIMAL(12, 2), nullable=False, server_default="0"),
|
|
sa.Column("external_ref", sa.String(length=255), nullable=True),
|
|
sa.Column("event_payload", json_type, nullable=False),
|
|
sa.Column("occurred_at", sa.TIMESTAMP(), nullable=True),
|
|
],
|
|
"roi_snapshots": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("range_key", sa.String(length=32), nullable=False, server_default="monthly"),
|
|
sa.Column("window_start", sa.TIMESTAMP(), nullable=True),
|
|
sa.Column("window_end", sa.TIMESTAMP(), nullable=True),
|
|
sa.Column("total_revenue_usd", sa.DECIMAL(12, 2), nullable=False, server_default="0"),
|
|
sa.Column("total_cost_usd", sa.DECIMAL(12, 2), nullable=False, server_default="0"),
|
|
sa.Column("roi_ratio", sa.DECIMAL(12, 4), nullable=True),
|
|
sa.Column("snapshot_payload", json_type, nullable=False),
|
|
],
|
|
"compliance_check_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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("charter_id", sa.BigInteger(), sa.ForeignKey("truth_nodes.id"), nullable=True),
|
|
sa.Column("release_node_id", sa.BigInteger(), sa.ForeignKey("truth_nodes.id"), nullable=True),
|
|
sa.Column("project_model_binding_id", sa.BigInteger(), sa.ForeignKey("project_model_bindings.id"), nullable=True),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="pending"),
|
|
sa.Column("score", sa.DECIMAL(5, 2), nullable=True),
|
|
sa.Column("checklist", json_type, nullable=False),
|
|
sa.Column("issues", json_type, nullable=False),
|
|
sa.Column("summary", json_type, nullable=False),
|
|
],
|
|
"evidence_packs": [
|
|
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("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=False),
|
|
sa.Column("compliance_check_run_id", sa.BigInteger(), sa.ForeignKey("compliance_check_runs.id"), nullable=True),
|
|
sa.Column("package_kind", sa.String(length=32), nullable=False, server_default="compliance_release"),
|
|
sa.Column("status", sa.String(length=32), nullable=False, server_default="draft"),
|
|
sa.Column("content", json_type, nullable=False),
|
|
sa.Column("export_payload", json_type, nullable=False),
|
|
],
|
|
}
|
|
|
|
for table_name, columns in table_specs.items():
|
|
if _table_exists(bind, table_name):
|
|
continue
|
|
op.create_table(table_name, *columns)
|
|
op.create_index(f"ix_{table_name}_tenant_id", table_name, ["tenant_id"], unique=False)
|
|
|
|
extra_indexes = {
|
|
"project_model_bindings": ["project_id", "charter_id", "model_catalog_item_id", "status"],
|
|
"model_recommendation_snapshots": ["project_id", "charter_id"],
|
|
"model_usage_events": ["project_id", "project_model_binding_id", "source_type", "occurred_at"],
|
|
"model_eval_runs": ["project_id", "charter_id", "project_model_binding_id", "status"],
|
|
"prompt_packages": ["project_id", "charter_id", "task_id", "project_model_binding_id", "status"],
|
|
"dev_workspace_bindings": ["project_id", "user_id", "status"],
|
|
"repo_bindings": ["project_id", "provider", "status"],
|
|
"task_code_links": ["task_id", "repo_binding_id", "pr_number", "status"],
|
|
"progress_events": ["task_id", "project_id", "user_id", "repo_binding_id", "workspace_binding_id", "event_type", "source", "occurred_at"],
|
|
"dev_sessions": ["project_id", "user_id", "workspace_binding_id", "status"],
|
|
"release_validation_runs": ["project_id", "charter_id", "release_node_id", "project_model_binding_id", "status"],
|
|
"feedback_ingests": ["project_id", "charter_id", "ingested_by_user_id", "source_type", "status"],
|
|
"iteration_recommendations": ["project_id", "charter_id", "feedback_ingest_id", "status", "priority"],
|
|
"revenue_events": ["project_id", "source_type", "external_ref", "occurred_at"],
|
|
"roi_snapshots": ["project_id", "range_key"],
|
|
"compliance_check_runs": ["project_id", "charter_id", "release_node_id", "project_model_binding_id", "status"],
|
|
"evidence_packs": ["project_id", "compliance_check_run_id", "package_kind", "status"],
|
|
}
|
|
for table_name, columns in extra_indexes.items():
|
|
for column_name in columns:
|
|
op.create_index(f"ix_{table_name}_{column_name}", table_name, [column_name], unique=False)
|
|
|
|
|
|
def downgrade() -> None:
|
|
bind = op.get_bind()
|
|
tables = [
|
|
"evidence_packs",
|
|
"compliance_check_runs",
|
|
"roi_snapshots",
|
|
"revenue_events",
|
|
"iteration_recommendations",
|
|
"feedback_ingests",
|
|
"release_validation_runs",
|
|
"dev_sessions",
|
|
"progress_events",
|
|
"task_code_links",
|
|
"repo_bindings",
|
|
"dev_workspace_bindings",
|
|
"prompt_packages",
|
|
"model_eval_runs",
|
|
"model_usage_events",
|
|
"model_recommendation_snapshots",
|
|
"project_model_bindings",
|
|
"model_catalog_items",
|
|
]
|
|
for table_name in tables:
|
|
if not _table_exists(bind, table_name):
|
|
continue
|
|
for index in sa.inspect(bind).get_indexes(table_name):
|
|
op.drop_index(index["name"], table_name=table_name)
|
|
op.drop_table(table_name)
|