feat(l1): intake dispatch via match_or_build + next-node + escalations endpoints

- /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>
This commit is contained in:
2026-05-30 03:54:23 -04:00
parent af3b1c0123
commit 633a208742
4 changed files with 245 additions and 13 deletions

View File

@@ -11,13 +11,31 @@ class IntakeRequest(BaseModel):
customer_name: Optional[str] = None
customer_contact: Optional[str] = None
flow_id: Optional[UUID] = None
force_build: bool = False
class IntakeResponse(BaseModel):
session_id: UUID
session_kind: Literal["flow", "proposal", "adhoc"]
ticket_id: str
ticket_kind: Literal["psa", "internal"]
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):