Add onboarding_dismissed and branding columns (logo_data, logo_content_type, company_display_name) to users and teams models. Create SessionSupportingData model for attaching text snippets and screenshots to sessions. Add Pydantic schemas for onboarding status, branding responses, and supporting data CRUD. Update SessionExport to accept pdf format. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
183 lines
6.2 KiB
Python
183 lines
6.2 KiB
Python
from datetime import datetime
|
|
from typing import Optional, Any, Literal
|
|
from uuid import UUID
|
|
from pydantic import BaseModel, Field, validator
|
|
|
|
SessionOutcome = Literal["resolved", "escalated", "workaround", "unresolved", "cancelled", "resolved_externally"]
|
|
|
|
|
|
class CustomStepSchema(BaseModel):
|
|
"""Enhanced custom step with source tracking.
|
|
|
|
Backward compatible: old sessions without new fields load with defaults.
|
|
"""
|
|
type: str # "decision" | "action" | "solution"
|
|
content: str
|
|
notes: Optional[str] = None
|
|
|
|
# Source tracking (new fields, optional for backward compatibility)
|
|
source: Literal["ad-hoc", "step-library", "forked-tree"] = "ad-hoc"
|
|
source_step_id: Optional[UUID] = None
|
|
inserted_at: Optional[datetime] = None
|
|
inserted_after_node_id: Optional[str] = None
|
|
|
|
|
|
class DecisionRecord(BaseModel):
|
|
node_id: str
|
|
question: Optional[str] = None
|
|
answer: Optional[str] = None
|
|
action_performed: Optional[str] = None
|
|
notes: Optional[str] = None
|
|
command_output: Optional[str] = Field(None, max_length=10000)
|
|
automation_used: Optional[bool] = False
|
|
timestamp: datetime
|
|
entered_at: Optional[datetime] = None
|
|
exited_at: Optional[datetime] = None
|
|
duration_seconds: Optional[int] = Field(None, ge=0)
|
|
attachments: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class SessionCreate(BaseModel):
|
|
tree_id: UUID
|
|
ticket_number: Optional[str] = Field(None, max_length=100)
|
|
client_name: Optional[str] = Field(None, max_length=255)
|
|
session_variables: Optional[dict[str, str]] = Field(None, description="Intake form values for procedural flows")
|
|
|
|
|
|
class SessionUpdate(BaseModel):
|
|
path_taken: Optional[list[str]] = None
|
|
decisions: Optional[list[DecisionRecord]] = None
|
|
custom_steps: Optional[list[CustomStepSchema]] = None
|
|
ticket_number: Optional[str] = Field(None, max_length=100)
|
|
client_name: Optional[str] = Field(None, max_length=255)
|
|
scratchpad: Optional[str] = None
|
|
next_steps: Optional[str] = None
|
|
session_variables: Optional[dict[str, str]] = None
|
|
|
|
|
|
class PrepareSessionRequest(BaseModel):
|
|
"""Create a prepared session with pre-filled variables and optional assignee."""
|
|
tree_id: UUID
|
|
session_variables: Optional[dict[str, str]] = Field(None, description="Pre-filled intake form values")
|
|
assigned_to_id: Optional[UUID] = Field(None, description="User ID of the engineer to assign this session to")
|
|
ticket_number: Optional[str] = Field(None, max_length=100)
|
|
client_name: Optional[str] = Field(None, max_length=255)
|
|
|
|
|
|
class SessionResponse(BaseModel):
|
|
id: UUID
|
|
tree_id: UUID
|
|
user_id: UUID
|
|
tree_snapshot: dict[str, Any]
|
|
path_taken: list[str]
|
|
decisions: list[dict[str, Any]]
|
|
custom_steps: list[dict[str, Any]] = Field(default_factory=list)
|
|
started_at: Optional[datetime] = None
|
|
completed_at: Optional[datetime] = None
|
|
outcome: Optional[SessionOutcome] = None
|
|
outcome_notes: Optional[str] = None
|
|
next_steps: str = ""
|
|
ticket_number: Optional[str] = None
|
|
client_name: Optional[str] = None
|
|
exported: bool
|
|
scratchpad: str = ""
|
|
session_variables: dict[str, str] = Field(default_factory=dict)
|
|
|
|
# Prepared session fields
|
|
prepared_by_id: Optional[UUID] = None
|
|
assigned_to_id: Optional[UUID] = None
|
|
|
|
@validator('scratchpad', 'next_steps', pre=True, always=True)
|
|
def normalize_text_fields(cls, v):
|
|
return v or ""
|
|
|
|
batch_id: Optional[UUID] = None
|
|
target_label: Optional[str] = None
|
|
|
|
# PSA ticket link
|
|
psa_ticket_id: Optional[str] = None
|
|
psa_connection_id: Optional[UUID] = None
|
|
|
|
# Fallback step decisions
|
|
fallback_decisions: list[dict[str, Any]] = Field(default_factory=list)
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class SessionExport(BaseModel):
|
|
format: str = Field(default="markdown", pattern="^(text|markdown|html|psa|pdf)$")
|
|
include_timestamps: bool = True
|
|
include_tree_info: bool = True
|
|
# Phase A
|
|
include_outcome_notes: bool = True
|
|
include_next_steps: bool = True
|
|
max_step_index: Optional[int] = Field(None, ge=1, description="1-based inclusive step cutoff")
|
|
# Phase B
|
|
include_summary: bool = False
|
|
detail_level: Literal["standard", "full"] = "standard"
|
|
# Phase C
|
|
redaction_mode: Literal["none", "mask"] = "none"
|
|
|
|
|
|
class SessionComplete(BaseModel):
|
|
outcome: SessionOutcome
|
|
outcome_notes: Optional[str] = None
|
|
next_steps: Optional[str] = None
|
|
|
|
|
|
class FallbackStepRecord(BaseModel):
|
|
parent_step_id: str
|
|
fallback_step_id: str
|
|
completed_at: str | None = None
|
|
notes: str | None = None
|
|
outcome: Literal['resolved', 'not_resolved', 'skipped']
|
|
|
|
|
|
class SessionVariablesUpdate(BaseModel):
|
|
"""Partial update to session variables (dict merge)."""
|
|
variables: dict[str, str] = Field(..., description="Key-value pairs to merge into session_variables")
|
|
|
|
|
|
class ScratchpadUpdate(BaseModel):
|
|
scratchpad: str
|
|
|
|
|
|
class SaveAsTreeRequest(BaseModel):
|
|
"""Request to save a session as a tree."""
|
|
tree_name: Optional[str] = Field(None, max_length=255, description="Custom name for the saved tree (auto-generated if not provided)")
|
|
description: Optional[str] = Field(None, description="Description for the saved tree")
|
|
status: Literal["draft", "published"] = Field("draft", description="Status of the saved tree")
|
|
|
|
|
|
class SaveAsTreeResponse(BaseModel):
|
|
"""Response after saving a session as a tree."""
|
|
tree_id: UUID
|
|
tree_name: str
|
|
message: str
|
|
|
|
|
|
# ── PSA ticket link ──────────────────────────────────────────────────
|
|
|
|
|
|
class TicketLinkRequest(BaseModel):
|
|
"""Link or unlink a PSA ticket to a session."""
|
|
psa_ticket_id: Optional[str] = None # null to unlink
|
|
|
|
|
|
class PSATicketResponse(BaseModel):
|
|
"""PSA ticket details returned when linking."""
|
|
id: str
|
|
summary: str
|
|
company_name: Optional[str] = None
|
|
board_name: Optional[str] = None
|
|
status_name: Optional[str] = None
|
|
priority_name: Optional[str] = None
|
|
|
|
|
|
class TicketLinkResponse(BaseModel):
|
|
"""Response after linking/unlinking a ticket."""
|
|
session_id: str
|
|
psa_ticket_id: Optional[str] = None
|
|
ticket: Optional[PSATicketResponse] = None
|