"""Pydantic schemas for session suggested fixes (Phase 3). See FLOWPILOT-MIGRATION.md Section 5.2. """ from __future__ import annotations from datetime import datetime from typing import Any, Literal from uuid import UUID from pydantic import BaseModel, Field UserDecision = Literal["one_off", "draft_template", "build_template", "dismissed"] class SessionSuggestedFixResponse(BaseModel): id: UUID session_id: UUID title: str description: str confidence_pct: int script_template_id: UUID | None ai_drafted_script: str | None ai_drafted_parameters: dict[str, Any] | None user_decision: UserDecision | None superseded_at: datetime | None created_at: datetime model_config = {"from_attributes": True} class SessionSuggestedFixDecisionRequest(BaseModel): """Engineer's path choice on a suggested fix. Server-side side effects per Section 5.2: - one_off: render the script (Phase 5), no template created. - draft_template: render + queue a draft_templates row (Phase 5/6). - build_template: redirect to full template creation (Phase 5). - dismissed: mark the fix superseded so a fresh suggestion can take over. """ decision: UserDecision class SessionSuggestedFixDecisionResponse(BaseModel): """Returned after recording a decision; richer payloads land in Phase 5.""" id: UUID user_decision: UserDecision # Set when the decision triggered side effects (e.g. a script generation). # Phase 3 only records the choice; this stays None until Phase 5 wires it. rendered_script: str | None = None redirect_path: str | None = Field( None, description="Where to send the engineer next (e.g. /scripts/builder?... for build_template)", ) # ── Resolution note preview ──────────────────────────────────────────────── class ResolutionNotePreviewResponse(BaseModel): markdown: str target_ticket_ref: str | None state_version: int from_cache: bool # ── Phase 4: Resolve + Escalate post ─────────────────────────────────────── class ResolutionNotePostRequest(BaseModel): """Engineer-edited resolution markdown. Server posts to PSA + marks resolved.""" markdown: str = Field(..., min_length=1, max_length=20_000) # Optional override for resolution summary shown on the session listing; # defaults to the first line of the markdown if omitted. resolution_summary: str | None = Field(None, max_length=500) class EscalationPackagePostRequest(BaseModel): markdown: str = Field(..., min_length=1, max_length=20_000) # Free-text reason shown in session listings and escalation queue. escalation_reason: str | None = Field(None, max_length=500) class ResolutionPostResponse(BaseModel): """Response shape for both Resolve/Escalate POST endpoints.""" # "resolved" / "escalated" / "resolved_local" / "escalated_local" # The _local variants indicate the session has no linked PSA ticket — # markdown is stored, session state is updated, nothing was posted externally. outcome: str session_status: str external_id: str | None = None posted_at: datetime | None = None # Populated when a status transition was attempted and verified. None # when no target status is configured in account_settings.preferences. verified_status_id: int | None = None verified_status_name: str | None = None status_transition_skipped_reason: str | None = None