from __future__ import annotations import asyncio import logging import uuid from typing import List, Optional from fastapi import APIRouter, File, Form, HTTPException, Request, UploadFile, status from ..config import get_settings from .dispatch import DispatchResponse, _check_rate_limit, _verify_webhook_secret from ..tools.receipt_parser import parse_upload logger = logging.getLogger(__name__) router = APIRouter(prefix='/upload', tags=['upload']) @router.post('', response_model=DispatchResponse) async def upload( request: Request, user_id: str = Form(...), message: str = Form(default='Create an employee expense report from these receipts.'), session_id: Optional[str] = Form(default=None), files: List[UploadFile] = File(default=[]), ): _verify_webhook_secret(request) _check_rate_limit(user_id) from ..app_state import get_master_agent master = get_master_agent() if master is None: raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='Agent service not ready') receipts: list[dict] = [] for f in files: data = await f.read() filename = f.filename or 'receipt' try: parsed = parse_upload(filename, data) receipts.extend(parsed) logger.info('upload: parsed %s → %d receipt(s)', filename, len(parsed)) except Exception as exc: logger.warning('upload: parse failed for %s: %s', filename, exc) if not receipts: logger.warning('upload: no parseable receipts found in upload from user_id=%s', user_id) directive_id = session_id or uuid.uuid4().hex extra_context = {'receipts': receipts, 'user_id': user_id} settings = get_settings() timeout = settings.directive_timeout_minutes * 60 try: response = await asyncio.wait_for( master.handle_message( user_id=user_id, channel_id=None, message=message, directive_id=directive_id, extra_context=extra_context, ), timeout=timeout, ) except asyncio.TimeoutError: raise HTTPException( status_code=status.HTTP_504_GATEWAY_TIMEOUT, detail=f'Directive timed out after {settings.directive_timeout_minutes}m', ) except Exception as exc: logger.exception('upload error user=%s: %s', user_id, exc) raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) return DispatchResponse( directive_id=response.directive_id, reply=response.response, escalations=response.escalations, actions_taken=response.actions_taken, session_id=session_id, )