"""Handoff endpoints — unified park/escalate. POST /ai-sessions/{id}/handoff — Create handoff GET /ai-sessions/{id}/handoffs — Handoff history POST /ai-sessions/{id}/handoffs/{hid}/claim — Claim session GET /ai-sessions/queue — Team queue """ import logging from typing import Annotated from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_active_user, get_db from app.models.user import User from app.models.ai_session import AISession from app.models.session_handoff import SessionHandoff from app.services.handoff_manager import HandoffManager from app.schemas.session_handoff import ( HandoffCreateRequest, HandoffResponse, ) logger = logging.getLogger(__name__) # Queue endpoint needs its own router (no session_id prefix) queue_router = APIRouter(prefix="/ai-sessions", tags=["session-handoffs"]) # Session-scoped endpoints router = APIRouter(prefix="/ai-sessions/{session_id}", tags=["session-handoffs"]) @router.post("/handoff", response_model=HandoffResponse, status_code=status.HTTP_201_CREATED) async def create_handoff( session_id: UUID, body: HandoffCreateRequest, current_user: Annotated[User, Depends(get_current_active_user)], db: Annotated[AsyncSession, Depends(get_db)], ) -> HandoffResponse: """Create a handoff (park or escalate).""" result = await db.execute( select(AISession).where( AISession.id == session_id, AISession.user_id == current_user.id, ) ) session = result.scalar_one_or_none() if not session: raise HTTPException(status_code=404, detail="Session not found") manager = HandoffManager(db) try: handoff = await manager.create_handoff( session_id=session_id, intent=body.intent, engineer_notes=body.engineer_notes, user_id=current_user.id, priority=body.priority, ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) await db.commit() return HandoffResponse.model_validate(handoff) @router.get("/handoffs", response_model=list[HandoffResponse]) async def list_handoffs( session_id: UUID, current_user: Annotated[User, Depends(get_current_active_user)], db: Annotated[AsyncSession, Depends(get_db)], ) -> list[HandoffResponse]: """Get handoff history for a session.""" result = await db.execute( select(SessionHandoff) .where(SessionHandoff.session_id == session_id) .order_by(SessionHandoff.created_at.desc()) ) handoffs = result.scalars().all() return [HandoffResponse.model_validate(h) for h in handoffs] @router.post("/handoffs/{handoff_id}/claim", response_model=HandoffResponse) async def claim_handoff( session_id: UUID, handoff_id: UUID, current_user: Annotated[User, Depends(get_current_active_user)], db: Annotated[AsyncSession, Depends(get_db)], ) -> HandoffResponse: """Claim a handed-off session.""" manager = HandoffManager(db) try: handoff = await manager.claim_session( handoff_id=handoff_id, claiming_user_id=current_user.id, ) except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) await db.commit() return HandoffResponse.model_validate(handoff) @queue_router.get("/queue") async def get_queue( current_user: Annotated[User, Depends(get_current_active_user)], db: Annotated[AsyncSession, Depends(get_db)], ) -> list[dict]: """Get team queue of parked + escalated sessions.""" manager = HandoffManager(db) return await manager.get_queue( team_id=current_user.team_id, account_id=current_user.account_id, )