From c9798514a9cbec6a55f1c4cb962bceaf456be121 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Tue, 24 Mar 2026 08:23:48 +0000 Subject: [PATCH] feat: add SessionResolutionOutput model and register all 4 branching models Introduces the session_resolution_outputs table for the three resolve deliverables (psa_ticket_notes, knowledge_base, client_summary) with UNIQUE(session_id, output_type) for safe upsert on regeneration. Also registers SessionBranch, ForkPoint, SessionHandoff, and SessionResolutionOutput in models/__init__.py so Alembic and the app pick them up automatically. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/models/__init__.py | 8 ++++ .../app/models/session_resolution_output.py | 39 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 backend/app/models/session_resolution_output.py diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 07db1ba8..fd3a754a 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -50,6 +50,10 @@ from .psa_activity_log import PsaActivityLog from .file_upload import FileUpload from .ai_session_embedding import AISessionEmbedding from .beta_feedback import BetaFeedback +from .session_branch import SessionBranch +from .fork_point import ForkPoint +from .session_handoff import SessionHandoff +from .session_resolution_output import SessionResolutionOutput __all__ = [ "User", @@ -114,4 +118,8 @@ __all__ = [ "FileUpload", "AISessionEmbedding", "BetaFeedback", + "SessionBranch", + "ForkPoint", + "SessionHandoff", + "SessionResolutionOutput", ] diff --git a/backend/app/models/session_resolution_output.py b/backend/app/models/session_resolution_output.py new file mode 100644 index 00000000..f00c2772 --- /dev/null +++ b/backend/app/models/session_resolution_output.py @@ -0,0 +1,39 @@ +"""Session resolution output model — three deliverables generated on resolve.""" +import uuid +from datetime import datetime, timezone +from typing import Optional, Any + +from sqlalchemy import String, Text, DateTime, ForeignKey, CheckConstraint, UniqueConstraint +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.dialects.postgresql import UUID, JSONB + +from app.core.database import Base + + +class SessionResolutionOutput(Base): + """One of three resolution deliverables: PSA ticket notes, KB article, client summary. + UNIQUE(session_id, output_type) + upsert so outputs can be regenerated. + """ + __tablename__ = "session_resolution_outputs" + __table_args__ = ( + CheckConstraint("output_type IN ('psa_ticket_notes', 'knowledge_base', 'client_summary')", name="ck_session_resolution_outputs_output_type"), + CheckConstraint("status IN ('draft', 'approved', 'pushed', 'rejected')", name="ck_session_resolution_outputs_status"), + UniqueConstraint("session_id", "output_type", name="uq_session_resolution_session_type"), + ) + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + session_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("ai_sessions.id", ondelete="CASCADE"), nullable=False, index=True) + output_type: Mapped[str] = mapped_column(String(30), nullable=False) + generated_content: Mapped[str] = mapped_column(Text, nullable=False) + structured_data: Mapped[Optional[dict[str, Any]]] = mapped_column(JSONB, nullable=True, comment="For KB: {symptoms, root_cause, steps, tags}") + edited_content: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + status: Mapped[str] = mapped_column(String(20), nullable=False, default="draft") + pushed_to: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) + pushed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) + pushed_reference: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) + generated_by_model: Mapped[str] = mapped_column(String(50), nullable=False) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) + + # Relationships + session = relationship("AISession")