""" Call History API — Read-only access to persisted call records, transcript chunks, and recording files for the dashboard. Thin HTTP layer over the shared data functions in call_persistence. """ import os from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession from db.database import get_db from services import call_persistence as store router = APIRouter() @router.get("/history") async def list_history( limit: int = Query(50, ge=1, le=500), offset: int = Query(0, ge=0), number: str | None = None, status: str | None = None, since: datetime | None = None, until: datetime | None = None, db: AsyncSession = Depends(get_db), ): """Paged list of past calls, newest first.""" rows = await store.search_history( db, number=number, status=status, since=since, until=until, limit=limit, offset=offset, ) return [ { "id": r.id, "direction": r.direction, "remote_number": r.remote_number, "status": r.status, "mode": r.mode, "intent": r.intent, "started_at": r.started_at.isoformat() if r.started_at else None, "ended_at": r.ended_at.isoformat() if r.ended_at else None, "duration": r.duration, "hold_time": r.hold_time, "device_used": r.device_used, "summary": r.summary, } for r in rows ] @router.get("/{call_id}/record") async def get_record(call_id: str, db: AsyncSession = Depends(get_db)): """Full CallRecord with classification_timeline.""" row = await store.get_record(db, call_id) if not row: raise HTTPException(status_code=404, detail=f"Call {call_id} not found") return { "id": row.id, "direction": row.direction, "remote_number": row.remote_number, "status": row.status, "mode": row.mode, "intent": row.intent, "started_at": row.started_at.isoformat() if row.started_at else None, "ended_at": row.ended_at.isoformat() if row.ended_at else None, "duration": row.duration, "hold_time": row.hold_time, "device_used": row.device_used, "summary": row.summary, "action_items": row.action_items, "sentiment": row.sentiment, "call_flow_id": row.call_flow_id, "classification_timeline": row.classification_timeline, } @router.get("/{call_id}/transcript") async def get_transcript(call_id: str, db: AsyncSession = Depends(get_db)): """Ordered transcript chunks for a call.""" rows = await store.get_transcript_chunks(db, call_id) return [ { "seq": c.seq, "t_offset_ms": c.t_offset_ms, "speaker": c.speaker, "text": c.text, "confidence": c.confidence, } for c in rows ] @router.get("/{call_id}/recording") async def get_recording(call_id: str, db: AsyncSession = Depends(get_db)): """Stream the WAV recording for a call.""" row = await store.latest_recording(db, call_id) if not row or not row.path: raise HTTPException(status_code=404, detail="Recording not found") if not os.path.exists(row.path): raise HTTPException(status_code=404, detail="Recording file missing on disk") return FileResponse(row.path, media_type="audio/wav", filename=os.path.basename(row.path))