Phase 2 and 3 of the platform rework. - Neutral request/response contract: external_id, metadata, X-AI-Key, run_id in the response; client-specific fields removed. - AI profile is a revision table: saving creates an inactive revision, activation is separate, try-run does not store a run. One chat-completions gateway serves AIHub and OpenAI-compatible profiles. - Add between and in rule operators. - Contracts moved to contracts/lead-scoring/ with a score-response fixture. - Admin Web updated for revisions, runs and the neutral fixture. - Consolidate tests; remove rewrite_architect.md and the stale handoff doc; rewrite spec, CONTEXT and ARCHITECTURE for the current design. Not verified: live AIHub/OpenAI behavior, Omni/EMS integration.
88 lines
3.6 KiB
Python
88 lines
3.6 KiB
Python
"""Initial schema for the unreleased AI Platform."""
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision = "0001_initial"
|
|
down_revision = None
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"app_credentials",
|
|
sa.Column("app_id", sa.String(128), primary_key=True),
|
|
sa.Column("verifier", sa.String(64), nullable=False, unique=True),
|
|
sa.Column("active", sa.Boolean(), nullable=False),
|
|
sa.Column(
|
|
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
|
),
|
|
sa.Column(
|
|
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
|
),
|
|
)
|
|
op.create_table(
|
|
"admin_users",
|
|
sa.Column("email", sa.String(254), primary_key=True),
|
|
sa.Column("password_hash", sa.String(512), nullable=False),
|
|
)
|
|
op.create_table(
|
|
"admin_sessions",
|
|
sa.Column("id", sa.String(64), primary_key=True),
|
|
sa.Column(
|
|
"admin_email", sa.String(254), sa.ForeignKey("admin_users.email"), nullable=False
|
|
),
|
|
sa.Column("csrf_hash", sa.String(64), nullable=False),
|
|
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
|
)
|
|
op.create_table(
|
|
"ai_profiles",
|
|
sa.Column("revision", sa.Integer(), primary_key=True, autoincrement=True),
|
|
sa.Column("provider", sa.String(32), nullable=False),
|
|
sa.Column("base_url", sa.String(2048), nullable=False),
|
|
sa.Column("model", sa.String(128), nullable=False),
|
|
sa.Column("timeout_seconds", sa.Integer(), nullable=False),
|
|
sa.Column("reasoning_effort", sa.String(16)),
|
|
sa.Column("instructions", sa.String(8000), nullable=False),
|
|
sa.Column("include_reference_date", sa.Boolean(), nullable=False),
|
|
sa.Column("timezone", sa.String(64), nullable=False),
|
|
sa.Column("suggested_action_enabled", sa.Boolean(), nullable=False),
|
|
sa.Column("created_by", sa.String(254), nullable=False),
|
|
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
|
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.false()),
|
|
)
|
|
op.create_table(
|
|
"scoring_runs",
|
|
sa.Column("id", sa.String(36), primary_key=True),
|
|
sa.Column(
|
|
"app_id", sa.String(128), sa.ForeignKey("app_credentials.app_id"), nullable=False
|
|
),
|
|
sa.Column("idempotency_key", sa.String(36), nullable=False),
|
|
sa.Column("fingerprint", sa.String(64), nullable=False),
|
|
sa.Column(
|
|
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
|
),
|
|
sa.Column("request", sa.JSON(), nullable=False),
|
|
sa.Column("rule_score", sa.Integer(), nullable=False),
|
|
sa.Column("ai_score", sa.Integer()),
|
|
sa.Column("final_score", sa.Integer(), nullable=False),
|
|
sa.Column("result_mode", sa.String(32), nullable=False),
|
|
sa.Column("error_code", sa.String(64)),
|
|
sa.Column("breakdown", sa.JSON(), nullable=False),
|
|
sa.Column("ai", sa.JSON()),
|
|
sa.Column("profile_revision", sa.Integer(), sa.ForeignKey("ai_profiles.revision")),
|
|
sa.Column("duration_ms", sa.Integer(), nullable=False),
|
|
sa.UniqueConstraint("app_id", "idempotency_key", name="uq_run_app_key"),
|
|
)
|
|
for column in ("app_id", "created_at"):
|
|
op.create_index(f"ix_scoring_runs_{column}", "scoring_runs", [column])
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_table("scoring_runs")
|
|
op.drop_table("ai_profiles")
|
|
op.drop_table("admin_sessions")
|
|
op.drop_table("admin_users")
|
|
op.drop_table("app_credentials")
|