Files
Claude AI 5746ad42e5 feat(fase3): add supervisor dashboard API
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 10:54:51 +00:00

202 lines
6.3 KiB
Python

from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.security import get_current_user
from app.models.queue import Queue
from app.models.user import User, UserRole, UserStatus
from app.models.whatsapp import Conversation, ConversationStatus, Message
router = APIRouter(prefix="/api/supervisor", tags=["supervisor"])
def require_supervisor(current_user: User = Depends(get_current_user)) -> User:
if current_user.role not in [UserRole.ADMIN, UserRole.SUPERVISOR]:
raise HTTPException(status_code=403, detail="Supervisor or Admin required")
return current_user
@router.get("/dashboard")
def get_dashboard(
db: Session = Depends(get_db),
current_user: User = Depends(require_supervisor),
) -> dict:
"""Get supervisor dashboard data"""
now = datetime.utcnow()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
status_counts = {}
for status in ConversationStatus:
count = db.query(Conversation).filter(Conversation.status == status).count()
status_counts[status.value] = count
waiting_conversations = db.query(Conversation).filter(
Conversation.status == ConversationStatus.WAITING
).count()
active_agents = db.query(User).filter(
User.status == UserStatus.ONLINE,
User.is_active == True,
).count()
total_agents = db.query(User).filter(
User.role.in_([UserRole.AGENT, UserRole.SUPERVISOR]),
User.is_active == True,
).count()
resolved_today = db.query(Conversation).filter(
Conversation.resolved_at >= today_start
).count()
messages_today = db.query(Message).filter(
Message.created_at >= today_start
).count()
csat_result = db.query(func.avg(Conversation.csat_score)).filter(
Conversation.csat_score != None,
Conversation.resolved_at >= today_start,
).scalar()
avg_csat = round(float(csat_result), 2) if csat_result else None
return {
"conversations": {
"by_status": status_counts,
"waiting": waiting_conversations,
"resolved_today": resolved_today,
},
"agents": {
"online": active_agents,
"total": total_agents,
},
"messages_today": messages_today,
"avg_csat": avg_csat,
}
@router.get("/agents")
def get_agents_status(
db: Session = Depends(get_db),
current_user: User = Depends(require_supervisor),
) -> list[dict]:
"""Get detailed agent status for supervisor view"""
agents = db.query(User).filter(
User.role.in_([UserRole.AGENT, UserRole.SUPERVISOR]),
User.is_active == True,
).all()
today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
result = []
for agent in agents:
active = db.query(Conversation).filter(
Conversation.assigned_to == agent.id,
Conversation.status == ConversationStatus.ACTIVE,
).count()
waiting = db.query(Conversation).filter(
Conversation.assigned_to == agent.id,
Conversation.status == ConversationStatus.WAITING,
).count()
resolved_today = db.query(Conversation).filter(
Conversation.assigned_to == agent.id,
Conversation.resolved_at >= today_start,
).count()
result.append({
"id": str(agent.id),
"name": agent.name,
"email": agent.email,
"role": agent.role.value,
"status": agent.status.value,
"active_conversations": active,
"waiting_conversations": waiting,
"resolved_today": resolved_today,
})
return result
@router.get("/queues")
def get_queues_status(
db: Session = Depends(get_db),
current_user: User = Depends(require_supervisor),
) -> list[dict]:
"""Get queue status for supervisor view"""
queues = db.query(Queue).filter(Queue.is_active == True).all()
result = []
for queue in queues:
waiting = db.query(Conversation).filter(
Conversation.queue_id == queue.id,
Conversation.status == ConversationStatus.WAITING,
).count()
active = db.query(Conversation).filter(
Conversation.queue_id == queue.id,
Conversation.status == ConversationStatus.ACTIVE,
).count()
online_agents = sum(
1 for qa in queue.agents
if qa.user and qa.user.status == UserStatus.ONLINE
)
result.append({
"id": str(queue.id),
"name": queue.name,
"waiting_conversations": waiting,
"active_conversations": active,
"online_agents": online_agents,
"total_agents": len(queue.agents),
"sla_first_response": queue.sla_first_response,
})
return result
@router.get("/conversations/critical")
def get_critical_conversations(
db: Session = Depends(get_db),
current_user: User = Depends(require_supervisor),
) -> list[dict]:
"""Get conversations that need attention"""
now = datetime.utcnow()
long_wait = db.query(Conversation).filter(
Conversation.status == ConversationStatus.WAITING,
Conversation.last_message_at < now - timedelta(minutes=5),
).all()
high_priority = db.query(Conversation).filter(
Conversation.priority.in_(["high", "urgent"]),
Conversation.status.in_([ConversationStatus.WAITING, ConversationStatus.ACTIVE]),
).all()
long_wait_ids = {conv.id for conv in long_wait}
result = []
seen_ids = set()
for conv in long_wait + high_priority:
if conv.id in seen_ids:
continue
seen_ids.add(conv.id)
reason = "long_wait" if conv.id in long_wait_ids else "high_priority"
result.append({
"id": str(conv.id),
"contact_name": conv.contact.name if conv.contact else "Unknown",
"contact_phone": conv.contact.phone_number if conv.contact else "",
"status": conv.status.value,
"priority": conv.priority,
"assigned_to": str(conv.assigned_to) if conv.assigned_to else None,
"last_message_at": conv.last_message_at.isoformat() if conv.last_message_at else None,
"reason": reason,
})
return result