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.
85 lines
3.8 KiB
Python
85 lines
3.8 KiB
Python
from datetime import datetime
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
from sqlalchemy import (
|
|
JSON,
|
|
Boolean,
|
|
DateTime,
|
|
ForeignKey,
|
|
Integer,
|
|
String,
|
|
UniqueConstraint,
|
|
false,
|
|
func,
|
|
)
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from ai_platform.infrastructure.persistence.database import Base
|
|
|
|
|
|
class AppCredential(Base):
|
|
__tablename__ = "app_credentials"
|
|
app_id: Mapped[str] = mapped_column(String(128), primary_key=True)
|
|
verifier: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
|
|
active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False
|
|
)
|
|
|
|
|
|
class AdminUser(Base):
|
|
__tablename__ = "admin_users"
|
|
email: Mapped[str] = mapped_column(String(254), primary_key=True)
|
|
password_hash: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
|
|
|
|
class AdminSession(Base):
|
|
__tablename__ = "admin_sessions"
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
admin_email: Mapped[str] = mapped_column(ForeignKey("admin_users.email"), nullable=False)
|
|
csrf_hash: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
|
|
|
|
class AIProfile(Base):
|
|
__tablename__ = "ai_profiles"
|
|
revision: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
provider: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
base_url: Mapped[str] = mapped_column(String(2048), nullable=False)
|
|
model: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
timeout_seconds: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
reasoning_effort: Mapped[str | None] = mapped_column(String(16))
|
|
instructions: Mapped[str] = mapped_column(String(8000), nullable=False)
|
|
include_reference_date: Mapped[bool] = mapped_column(Boolean, nullable=False)
|
|
timezone: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
suggested_action_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False)
|
|
created_by: Mapped[str] = mapped_column(String(254), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
active: Mapped[bool] = mapped_column(
|
|
Boolean, default=False, server_default=false(), nullable=False
|
|
)
|
|
|
|
|
|
class ScoringRun(Base):
|
|
__tablename__ = "scoring_runs"
|
|
__table_args__ = (UniqueConstraint("app_id", "idempotency_key", name="uq_run_app_key"),)
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
|
|
app_id: Mapped[str] = mapped_column(ForeignKey("app_credentials.app_id"), index=True)
|
|
idempotency_key: Mapped[str] = mapped_column(String(36), nullable=False)
|
|
fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), index=True
|
|
)
|
|
request: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
|
|
rule_score: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
ai_score: Mapped[int | None] = mapped_column(Integer)
|
|
final_score: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
result_mode: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
error_code: Mapped[str | None] = mapped_column(String(64))
|
|
breakdown: Mapped[list[dict[str, Any]]] = mapped_column(JSON, nullable=False)
|
|
ai: Mapped[dict[str, Any] | None] = mapped_column(JSON)
|
|
profile_revision: Mapped[int | None] = mapped_column(ForeignKey("ai_profiles.revision"))
|
|
duration_ms: Mapped[int] = mapped_column(Integer, nullable=False)
|