hungtran24 6577bb74be refactor: simplify scoring service, AI profile revisions and test suite
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.
2026-10-09 08:23:21 +07:00

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)