Files
WhatsAppCentralizado/services/api-gateway/app/services/assignment.py
2026-01-29 10:54:12 +00:00

168 lines
6.0 KiB
Python

from sqlalchemy.orm import Session
from typing import Optional
from uuid import UUID
from datetime import datetime
from app.models.queue import Queue, QueueAgent, AssignmentMethod
from app.models.whatsapp import Conversation, ConversationStatus
from app.models.user import User, UserStatus
class AssignmentService:
def __init__(self, db: Session):
self.db = db
def assign_conversation(self, conversation_id: UUID, queue_id: UUID) -> Optional[User]:
"""Assign a conversation to an agent based on queue settings"""
queue = self.db.query(Queue).filter(Queue.id == queue_id).first()
if not queue or not queue.is_active:
return None
conversation = self.db.query(Conversation).filter(
Conversation.id == conversation_id
).first()
if not conversation:
return None
available_agents = self._get_available_agents(queue)
if not available_agents:
return None
if queue.assignment_method == AssignmentMethod.ROUND_ROBIN:
agent = self._round_robin_select(queue, available_agents)
elif queue.assignment_method == AssignmentMethod.LEAST_BUSY:
agent = self._least_busy_select(available_agents)
elif queue.assignment_method == AssignmentMethod.SKILL_BASED:
agent = self._skill_based_select(conversation, available_agents)
else:
agent = available_agents[0] if available_agents else None
if agent:
conversation.assigned_to = agent.id
conversation.queue_id = queue_id
conversation.status = ConversationStatus.ACTIVE
self.db.commit()
return agent
def _get_available_agents(self, queue: Queue) -> list:
"""Get agents who are online and have capacity"""
agents = []
for qa in queue.agents:
user = qa.user
if not user or user.status != UserStatus.ONLINE:
continue
active_count = self.db.query(Conversation).filter(
Conversation.assigned_to == user.id,
Conversation.status.in_([ConversationStatus.ACTIVE, ConversationStatus.WAITING])
).count()
if active_count < queue.max_per_agent:
agents.append(user)
return agents
def _round_robin_select(self, queue: Queue, agents: list) -> Optional[User]:
"""Select agent with round-robin: least recently assigned"""
if not agents:
return None
agent_last_assign = {}
for agent in agents:
last_conv = self.db.query(Conversation).filter(
Conversation.assigned_to == agent.id
).order_by(Conversation.created_at.desc()).first()
agent_last_assign[agent.id] = last_conv.created_at if last_conv else datetime.min
sorted_agents = sorted(agents, key=lambda a: agent_last_assign[a.id])
return sorted_agents[0] if sorted_agents else None
def _least_busy_select(self, agents: list) -> Optional[User]:
"""Select agent with fewest active conversations"""
if not agents:
return None
min_count = float('inf')
selected = None
for agent in agents:
count = self.db.query(Conversation).filter(
Conversation.assigned_to == agent.id,
Conversation.status.in_([ConversationStatus.ACTIVE, ConversationStatus.WAITING])
).count()
if count < min_count:
min_count = count
selected = agent
return selected
def _skill_based_select(self, conversation: Conversation, agents: list) -> Optional[User]:
"""Select agent based on skills (for now, just return least busy)"""
return self._least_busy_select(agents)
def transfer_to_queue(self, conversation_id: UUID, target_queue_id: UUID, keep_history: bool = True) -> bool:
"""Transfer conversation to another queue"""
conversation = self.db.query(Conversation).filter(
Conversation.id == conversation_id
).first()
if not conversation:
return False
conversation.queue_id = target_queue_id
conversation.assigned_to = None
conversation.status = ConversationStatus.WAITING
self.db.commit()
self.assign_conversation(conversation_id, target_queue_id)
return True
def transfer_to_agent(self, conversation_id: UUID, agent_id: UUID) -> bool:
"""Transfer conversation directly to a specific agent"""
conversation = self.db.query(Conversation).filter(
Conversation.id == conversation_id
).first()
if not conversation:
return False
agent = self.db.query(User).filter(User.id == agent_id).first()
if not agent:
return False
conversation.assigned_to = agent_id
conversation.status = ConversationStatus.ACTIVE
self.db.commit()
return True
def transfer_to_bot(self, conversation_id: UUID) -> bool:
"""Transfer conversation back to bot"""
conversation = self.db.query(Conversation).filter(
Conversation.id == conversation_id
).first()
if not conversation:
return False
conversation.assigned_to = None
conversation.status = ConversationStatus.BOT
self.db.commit()
return True
def resolve_conversation(self, conversation_id: UUID, csat_score: int = None, csat_feedback: str = None) -> bool:
"""Mark conversation as resolved"""
conversation = self.db.query(Conversation).filter(
Conversation.id == conversation_id
).first()
if not conversation:
return False
conversation.status = ConversationStatus.RESOLVED
conversation.resolved_at = datetime.utcnow()
if csat_score:
conversation.csat_score = csat_score
if csat_feedback:
conversation.csat_feedback = csat_feedback
self.db.commit()
return True