""" SLA Tasks - ServiceManagerWeb Workers Tareas para monitoreo y gestión de SLAs """ from celery import current_task from datetime import datetime, timedelta from typing import List, Dict, Any, Optional import structlog from app.celery import celery_app from app.core.config import get_settings from app.core.logging import get_logger from app.tasks.email_tasks import send_templated_email_task settings = get_settings() logger = get_logger(__name__) @celery_app.task( bind=True, time_limit=300, soft_time_limit=240 ) def check_sla_violations(self) -> Dict[str, Any]: """ Check for SLA violations and send alerts. This task runs every 5 minutes to check for: - Response SLA violations - Resolution SLA violations - SLA warnings (approaching deadline) Returns: Dict with check results """ task_logger = logger.bind( task_id=self.request.id, task_name=self.name ) task_logger.info("Starting SLA violations check") if not settings.SLA_CHECK_ENABLED: task_logger.info("SLA check disabled, skipping") return {"status": "disabled"} try: current_time = datetime.utcnow() results = { "checked_at": current_time.isoformat(), "response_violations": [], "resolution_violations": [], "warnings": [], "notifications_sent": 0 } # TODO: Implement actual database queries # For now, simulate some checks # Mock violations for development mock_violations = [ { "ticket_id": "mock-ticket-1", "ticket_number": "TKT-2024-000001", "subject": "Problema urgente de conexión", "priority": "HIGH", "sla_type": "response", "due_at": (current_time - timedelta(minutes=30)).isoformat(), "assigned_to_email": "agent@example.com", "created_by_email": "cliente@example.com", "tenant_id": "mock-tenant-1" } ] # Process violations for violation in mock_violations: task_logger.info( "Processing SLA violation", ticket_id=violation["ticket_id"], sla_type=violation["sla_type"] ) if violation["sla_type"] == "response": results["response_violations"].append(violation) # Send notification to assigned agent if violation["assigned_to_email"]: send_templated_email_task.apply_async(kwargs={ "to_email": violation["assigned_to_email"], "template_name": "sla_response_violation", "context": { "ticket_number": violation["ticket_number"], "subject": violation["subject"], "priority": violation["priority"], "due_at": violation["due_at"], "ticket_url": f"https://admin.servicemanager.local/tickets/{violation['ticket_id']}" }, "tenant_id": violation["tenant_id"], "correlation_id": self.request.id }) results["notifications_sent"] += 1 elif violation["sla_type"] == "resolution": results["resolution_violations"].append(violation) # Send escalation notification send_templated_email_task.apply_async(kwargs={ "to_email": "manager@example.com", # TODO: Get from tenant config "template_name": "sla_resolution_violation", "context": { "ticket_number": violation["ticket_number"], "subject": violation["subject"], "priority": violation["priority"], "assigned_to": violation["assigned_to_email"], "ticket_url": f"https://admin.servicemanager.local/tickets/{violation['ticket_id']}" }, "tenant_id": violation["tenant_id"], "correlation_id": self.request.id }) results["notifications_sent"] += 1 # TODO: Check for SLA warnings (approaching deadline) task_logger.info( "SLA violations check completed", response_violations=len(results["response_violations"]), resolution_violations=len(results["resolution_violations"]), warnings=len(results["warnings"]), notifications_sent=results["notifications_sent"] ) return results except Exception as exc: task_logger.error( "SLA violations check failed", error=str(exc), exc_info=True ) raise @celery_app.task( bind=True, time_limit=600, soft_time_limit=540 ) def calculate_sla_metrics(self, tenant_id: str, date_from: str, date_to: str) -> Dict[str, Any]: """ Calculate SLA metrics for a tenant and date range. Args: tenant_id: Tenant ID date_from: Start date (ISO format) date_to: End date (ISO format) Returns: Dict with SLA metrics """ task_logger = logger.bind( task_id=self.request.id, task_name=self.name, tenant_id=tenant_id ) task_logger.info( "Starting SLA metrics calculation", date_from=date_from, date_to=date_to ) try: # TODO: Implement actual database queries # For now, return mock metrics metrics = { "tenant_id": tenant_id, "date_from": date_from, "date_to": date_to, "calculated_at": datetime.utcnow().isoformat(), "response_sla": { "target_hours": 2, "met_count": 45, "total_count": 50, "percentage": 90.0, "avg_response_time_hours": 1.8 }, "resolution_sla": { "target_hours": 24, "met_count": 42, "total_count": 48, "percentage": 87.5, "avg_resolution_time_hours": 22.5 }, "by_priority": { "LOW": { "response_sla_percentage": 95.0, "resolution_sla_percentage": 90.0 }, "MEDIUM": { "response_sla_percentage": 88.0, "resolution_sla_percentage": 85.0 }, "HIGH": { "response_sla_percentage": 92.0, "resolution_sla_percentage": 88.0 }, "URGENT": { "response_sla_percentage": 85.0, "resolution_sla_percentage": 80.0 } }, "trends": { "response_sla_trend": "+2.5%", "resolution_sla_trend": "-1.2%" } } task_logger.info( "SLA metrics calculation completed", response_sla_percentage=metrics["response_sla"]["percentage"], resolution_sla_percentage=metrics["resolution_sla"]["percentage"] ) return metrics except Exception as exc: task_logger.error( "SLA metrics calculation failed", error=str(exc), exc_info=True ) raise @celery_app.task( bind=True, time_limit=300, soft_time_limit=240 ) def send_sla_warnings(self, tenant_id: Optional[str] = None) -> Dict[str, Any]: """ Send SLA warning notifications for tickets approaching deadline. Args: tenant_id: Optional tenant ID to filter by Returns: Dict with warning results """ task_logger = logger.bind( task_id=self.request.id, task_name=self.name, tenant_id=tenant_id ) task_logger.info("Starting SLA warnings check") try: current_time = datetime.utcnow() warning_threshold = settings.SLA_WARNING_THRESHOLD # 80% of SLA time # TODO: Query database for tickets approaching SLA deadlines # Mock warnings warnings = [ { "ticket_id": "mock-ticket-2", "ticket_number": "TKT-2024-000002", "subject": "Consulta técnica", "priority": "MEDIUM", "sla_type": "response", "due_at": (current_time + timedelta(minutes=30)).isoformat(), "time_remaining_percent": 15.0, "assigned_to_email": "agent@example.com", "tenant_id": "mock-tenant-1" } ] notifications_sent = 0 for warning in warnings: if warning["time_remaining_percent"] <= (1 - warning_threshold) * 100: task_logger.info( "Sending SLA warning", ticket_id=warning["ticket_id"], time_remaining_percent=warning["time_remaining_percent"] ) send_templated_email_task.apply_async(kwargs={ "to_email": warning["assigned_to_email"], "template_name": "sla_warning", "context": { "ticket_number": warning["ticket_number"], "subject": warning["subject"], "priority": warning["priority"], "sla_type": warning["sla_type"], "due_at": warning["due_at"], "time_remaining_percent": warning["time_remaining_percent"], "ticket_url": f"https://admin.servicemanager.local/tickets/{warning['ticket_id']}" }, "tenant_id": warning["tenant_id"], "correlation_id": self.request.id }) notifications_sent += 1 task_logger.info( "SLA warnings check completed", warnings_found=len(warnings), notifications_sent=notifications_sent ) return { "warnings_found": len(warnings), "notifications_sent": notifications_sent, "warnings": warnings } except Exception as exc: task_logger.error( "SLA warnings check failed", error=str(exc), exc_info=True ) raise