- /intake now runs match_or_build (matched/suggest/out_of_scope/build); build
seeds the classified category as a hidden meta walked_path entry, matched starts
a flow session, suggest/out_of_scope return prompt data with no session.
- New POST /sessions/{id}/next-node (threads node_text to advance_ai_build) and
GET /escalations (engineer-or-above) for the handoff queue.
- New IntakeResponse(outcome=...)/NextNodeRequest/NextNodeResponse schemas and
require_account_owner_or_admin dep.
- Reconcile Phase-1 intake tests to the new contract (mock match_or_build); add
test_l1_api_ai_build.py covering build/out_of_scope/suggest/next-node/escalations.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
91 lines
2.5 KiB
Python
91 lines
2.5 KiB
Python
"""Pydantic schemas for the /l1/* endpoint surface."""
|
|
from datetime import datetime
|
|
from typing import Any, Literal, Optional
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class IntakeRequest(BaseModel):
|
|
problem_statement: str = Field(..., min_length=1)
|
|
customer_name: Optional[str] = None
|
|
customer_contact: Optional[str] = None
|
|
flow_id: Optional[UUID] = None
|
|
force_build: bool = False
|
|
|
|
|
|
class IntakeResponse(BaseModel):
|
|
outcome: Literal["matched", "suggest", "out_of_scope", "build"]
|
|
session_id: Optional[UUID] = None
|
|
session_kind: Optional[Literal["flow", "proposal", "adhoc", "ai_build"]] = None
|
|
ticket_id: Optional[str] = None
|
|
ticket_kind: Optional[str] = None
|
|
flow_id: Optional[UUID] = None # for 'matched'
|
|
near_miss: Optional[dict] = None # for 'suggest'
|
|
category: Optional[str] = None # for 'out_of_scope'
|
|
|
|
|
|
class NextNodeRequest(BaseModel):
|
|
node_id: Optional[str] = None
|
|
node_text: Optional[str] = None # rendered text of the node being answered (carry-forward Task 8)
|
|
answer: Optional[str] = None # 'yes' | 'no' for questions
|
|
acknowledged: Optional[bool] = None
|
|
note: Optional[str] = None
|
|
|
|
|
|
class NextNodeResponse(BaseModel):
|
|
node: dict
|
|
session_status: str
|
|
|
|
|
|
class StepRequest(BaseModel):
|
|
node_id: str
|
|
question: str
|
|
answer: str
|
|
note: Optional[str] = None
|
|
|
|
|
|
class NotesRequest(BaseModel):
|
|
notes: list[dict[str, Any]]
|
|
|
|
|
|
class ResolveRequest(BaseModel):
|
|
helpful: bool
|
|
resolution_notes: str
|
|
|
|
|
|
class EscalateRequest(BaseModel):
|
|
reason: Optional[str] = None
|
|
reason_category: str = Field(..., min_length=1)
|
|
|
|
|
|
class EscalateWithoutWalkRequest(BaseModel):
|
|
problem_statement: str = Field(..., min_length=1)
|
|
customer_name: Optional[str] = None
|
|
customer_contact: Optional[str] = None
|
|
reason_category: str = Field(..., min_length=1)
|
|
reason: Optional[str] = None
|
|
|
|
|
|
class WalkSessionResponse(BaseModel):
|
|
id: UUID
|
|
session_kind: str
|
|
flow_id: Optional[UUID]
|
|
flow_proposal_id: Optional[UUID]
|
|
current_node_id: Optional[str]
|
|
walked_path: list[dict[str, Any]]
|
|
walk_notes: list[dict[str, Any]]
|
|
status: str
|
|
started_at: datetime
|
|
last_step_at: datetime
|
|
resolved_at: Optional[datetime]
|
|
|
|
|
|
class QueueRow(BaseModel):
|
|
ticket_id: str
|
|
ticket_kind: Literal["psa", "internal"]
|
|
problem_statement: Optional[str] = None
|
|
customer_name: Optional[str] = None
|
|
status: str
|
|
created_at: Optional[datetime] = None
|