"""AI content hook aggregates — Phase 11.10. Experience stores hook registrations and dispatches only. Model inference remains on the Enterprise AI Platform via AIProvider contracts. """ from __future__ import annotations import uuid from sqlalchemy import Index, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.types import JSON from app.core.database import Base from app.models.base import ( ActorAuditMixin, OptimisticLockMixin, SoftDeleteMixin, TenantMixin, TimestampMixin, UUIDPrimaryKeyMixin, ) from app.models.types import ( GUID, AiContentDispatchStatus, AiContentHookStatus, AiContentTaskKind, ) class ExperienceAiContentHookRegistration( Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, SoftDeleteMixin, ActorAuditMixin, OptimisticLockMixin, ): __tablename__ = "experience_ai_content_hook_registrations" workspace_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) site_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) page_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) task_kind: Mapped[AiContentTaskKind] = mapped_column(nullable=False) external_ai_ref: Mapped[str | None] = mapped_column(String(255), nullable=True) status: Mapped[AiContentHookStatus] = mapped_column( default=AiContentHookStatus.DRAFT, nullable=False ) config_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) __table_args__ = ( UniqueConstraint( "tenant_id", "code", name="uq_experience_ai_content_hook_registrations_tenant_code", ), Index( "ix_experience_ai_content_hook_registrations_workspace", "tenant_id", "workspace_id", ), Index( "ix_experience_ai_content_hook_registrations_status", "tenant_id", "status", ), Index( "ix_experience_ai_content_hook_registrations_tenant_deleted", "tenant_id", "is_deleted", ), ) class ExperienceAiContentDispatch( Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, SoftDeleteMixin, ActorAuditMixin, OptimisticLockMixin, ): __tablename__ = "experience_ai_content_dispatches" registration_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) workspace_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) site_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) page_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) task: Mapped[str] = mapped_column(String(100), nullable=False) context_ref: Mapped[str | None] = mapped_column(String(255), nullable=True) payload_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) status: Mapped[AiContentDispatchStatus] = mapped_column( default=AiContentDispatchStatus.PENDING, nullable=False ) response_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) generated_content_ref: Mapped[str | None] = mapped_column(String(255), nullable=True) __table_args__ = ( Index( "ix_experience_ai_content_dispatches_registration", "tenant_id", "registration_id", ), Index( "ix_experience_ai_content_dispatches_status", "tenant_id", "status", ), Index( "ix_experience_ai_content_dispatches_tenant_deleted", "tenant_id", "is_deleted", ), )