feat: add branching columns to ai_sessions, ai_session_steps, file_uploads

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-24 08:27:17 +00:00
parent c9798514a9
commit e96c94efbd
3 changed files with 87 additions and 2 deletions

View File

@@ -20,6 +20,10 @@ if TYPE_CHECKING:
from app.models.account import Account
from app.models.tree import Tree
from app.models.psa_connection import PsaConnection
from app.models.session_branch import SessionBranch
from app.models.fork_point import ForkPoint
from app.models.session_handoff import SessionHandoff
from app.models.session_resolution_output import SessionResolutionOutput
class AISession(Base):
@@ -206,6 +210,28 @@ class AISession(Base):
comment="Full LLM message history for context continuity",
)
# ── Branching ──
is_branching: Mapped[bool] = mapped_column(
default=False,
comment="Whether conversational branching is active for this session",
)
active_branch_id: Mapped[Optional[uuid.UUID]] = mapped_column(
UUID(as_uuid=True), nullable=True,
comment="Currently viewed branch. No FK — soft pointer to avoid circular FK with session_branches",
)
handoff_count: Mapped[int] = mapped_column(
Integer, nullable=False, default=0,
comment="Number of times this session has been handed off",
)
total_active_seconds: Mapped[int] = mapped_column(
Integer, nullable=False, default=0,
comment="Cumulative active time in seconds",
)
total_parked_seconds: Mapped[int] = mapped_column(
Integer, nullable=False, default=0,
comment="Cumulative parked time in seconds",
)
# ── Relationships ──
user: Mapped["User"] = relationship("User", foreign_keys=[user_id])
account: Mapped["Account"] = relationship("Account")
@@ -218,3 +244,18 @@ class AISession(Base):
cascade="all, delete-orphan",
order_by="AISessionStep.step_order",
)
branches: Mapped[list["SessionBranch"]] = relationship(
"SessionBranch",
foreign_keys="SessionBranch.session_id",
cascade="all, delete-orphan",
order_by="SessionBranch.branch_order",
)
handoffs: Mapped[list["SessionHandoff"]] = relationship(
"SessionHandoff",
cascade="all, delete-orphan",
order_by="SessionHandoff.created_at",
)
resolution_outputs: Mapped[list["SessionResolutionOutput"]] = relationship(
"SessionResolutionOutput",
cascade="all, delete-orphan",
)

View File

@@ -16,6 +16,8 @@ from app.core.database import Base
if TYPE_CHECKING:
from app.models.ai_session import AISession
from app.models.script_template import ScriptGeneration
from app.models.session_branch import SessionBranch
from app.models.fork_point import ForkPoint
class AISessionStep(Base):
@@ -34,7 +36,7 @@ class AISessionStep(Base):
__table_args__ = (
CheckConstraint(
"step_type IN ('question', 'action', 'script_generation', 'verification', "
"'info_request', 'note', 'intake_analysis')",
"'info_request', 'note', 'intake_analysis', 'fork')",
name="ck_ai_session_steps_step_type",
),
)
@@ -119,6 +121,24 @@ class AISessionStep(Base):
Integer, nullable=False, default=0,
)
# ── Branching ──
branch_id: Mapped[Optional[uuid.UUID]] = mapped_column(
UUID(as_uuid=True),
ForeignKey("session_branches.id", ondelete="SET NULL"),
nullable=True,
index=True,
comment="NULL = pre-branching/root messages",
)
is_fork_point: Mapped[bool] = mapped_column(
default=False,
comment="Whether this step triggered a fork",
)
fork_point_id: Mapped[Optional[uuid.UUID]] = mapped_column(
UUID(as_uuid=True),
ForeignKey("fork_points.id", ondelete="SET NULL"),
nullable=True,
)
# ── Timestamps ──
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)

View File

@@ -3,7 +3,7 @@ import uuid
from datetime import datetime, timezone
from typing import Optional
from sqlalchemy import String, Integer, DateTime, ForeignKey
from sqlalchemy import String, Integer, DateTime, ForeignKey, Text
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.dialects.postgresql import UUID
@@ -30,3 +30,27 @@ class FileUpload(Base):
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
# ── AI description + branching context ──
ai_description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True,
comment="AI-generated one-sentence description of the file",
)
extracted_content: Mapped[Optional[str]] = mapped_column(
Text, nullable=True,
comment="Extracted text from logs/configs",
)
content_summary: Mapped[Optional[str]] = mapped_column(
Text, nullable=True,
comment="AI summary for long text files (>2000 tokens)",
)
uploaded_on_branch_id: Mapped[Optional[uuid.UUID]] = mapped_column(
UUID(as_uuid=True),
ForeignKey("session_branches.id", ondelete="SET NULL"),
nullable=True,
)
uploaded_at_step_id: Mapped[Optional[uuid.UUID]] = mapped_column(
UUID(as_uuid=True),
ForeignKey("ai_session_steps.id", ondelete="SET NULL"),
nullable=True,
)