# shared/clients/alerts_client.py """ Alerts Service Client for Inter-Service Communication Provides access to alert processor service from other services """ import structlog from typing import Dict, Any, Optional, List from uuid import UUID from shared.clients.base_service_client import BaseServiceClient from shared.config.base import BaseServiceSettings logger = structlog.get_logger() class AlertsServiceClient(BaseServiceClient): """Client for communicating with the Alert Processor Service""" def __init__(self, config: BaseServiceSettings, calling_service_name: str = "unknown"): super().__init__(calling_service_name, config) def get_service_base_path(self) -> str: return "/api/v1" # ================================================================ # DASHBOARD METHODS # ================================================================ async def get_alerts_summary( self, tenant_id: str ) -> Optional[Dict[str, Any]]: """ Get alerts summary for dashboard health status Args: tenant_id: Tenant ID Returns: Dict with counts by severity: { "total_count": int, "active_count": int, "critical_count": int, # Maps to "urgent" severity "high_count": int, "medium_count": int, "low_count": int, "resolved_count": int, "acknowledged_count": int } """ try: # Gateway routes /tenants/{tenant_id}/alerts/... to alert_processor service return await self.get( "/alerts/summary", tenant_id=tenant_id ) except Exception as e: logger.error("Error fetching alerts summary", error=str(e), tenant_id=tenant_id) return None async def get_critical_alerts( self, tenant_id: str, limit: int = 20 ) -> Optional[Dict[str, Any]]: """ Get critical/urgent alerts for dashboard Note: "critical" in dashboard context maps to "urgent" severity in alert_processor Args: tenant_id: Tenant ID limit: Maximum number of alerts to return Returns: Dict with: { "alerts": [...], "total": int, "limit": int, "offset": int } """ try: # Gateway routes /tenants/{tenant_id}/alerts/... to alert_processor service # "critical" in dashboard = "urgent" severity in alert_processor return await self.get( "/alerts", tenant_id=tenant_id, params={"severity": "urgent", "resolved": False, "limit": limit} ) except Exception as e: logger.error("Error fetching critical alerts", error=str(e), tenant_id=tenant_id) return None async def get_alerts( self, tenant_id: str, priority_level: Optional[str] = None, status: Optional[str] = None, resolved: Optional[bool] = None, limit: int = 100, offset: int = 0 ) -> Optional[Dict[str, Any]]: """ Get alerts with optional filters Args: tenant_id: Tenant ID priority_level: Filter by priority level (critical, important, standard, info) status: Filter by status (active, resolved, acknowledged, ignored) resolved: Filter by resolved status (None = all, True = resolved only, False = unresolved only) limit: Maximum number of alerts offset: Pagination offset Returns: Dict with: { "alerts": [...], "total": int, "limit": int, "offset": int } """ try: params = {"limit": limit, "offset": offset} if priority_level: params["priority_level"] = priority_level if status: params["status"] = status if resolved is not None: params["resolved"] = resolved return await self.get( "/alerts", tenant_id=tenant_id, params=params ) except Exception as e: logger.error("Error fetching alerts", error=str(e), tenant_id=tenant_id) return None async def get_alerts_by_severity( self, tenant_id: str, severity: str, limit: int = 100, resolved: Optional[bool] = None ) -> Optional[Dict[str, Any]]: """ Get alerts filtered by severity Args: tenant_id: Tenant ID severity: Severity level (low, medium, high, urgent) limit: Maximum number of alerts resolved: Filter by resolved status (None = all, True = resolved only, False = unresolved only) Returns: Dict with alerts list and metadata """ try: params = {"severity": severity, "limit": limit} if resolved is not None: params["resolved"] = resolved return await self.get( "/alerts", tenant_id=tenant_id, params=params ) except Exception as e: logger.error("Error fetching alerts by severity", error=str(e), severity=severity, tenant_id=tenant_id) return None async def get_alert_by_id( self, tenant_id: str, alert_id: str ) -> Optional[Dict[str, Any]]: """ Get a specific alert by ID Args: tenant_id: Tenant ID alert_id: Alert UUID Returns: Dict with alert details """ try: return await self.get( f"/alerts/{alert_id}", tenant_id=tenant_id ) except Exception as e: logger.error("Error fetching alert", error=str(e), alert_id=alert_id, tenant_id=tenant_id) return None async def get_dashboard_analytics( self, tenant_id: str, days: int = 7 ) -> Optional[Dict[str, Any]]: """ Get dashboard analytics including prevented issues and estimated savings Args: tenant_id: Tenant ID days: Number of days to analyze (default: 7) Returns: Dict with analytics data: { "period_days": int, "total_alerts": int, "active_alerts": int, "ai_handling_rate": float, "prevented_issues_count": int, "estimated_savings_eur": float, "total_financial_impact_at_risk_eur": float, "priority_distribution": {...}, "type_class_distribution": {...}, "active_by_type_class": {...}, "period_comparison": {...} } """ try: return await self.get( "/alerts/analytics/dashboard", tenant_id=tenant_id, params={"days": days} ) except Exception as e: logger.error("Error fetching dashboard analytics", error=str(e), tenant_id=tenant_id) return None # ================================================================ # UTILITY METHODS # ================================================================ async def health_check(self) -> bool: """Check if alerts service is healthy""" try: result = await self.get("../health") # Health endpoint is not tenant-scoped return result is not None except Exception as e: logger.error("Alerts service health check failed", error=str(e)) return False # Factory function for dependency injection def create_alerts_client(config: BaseServiceSettings, calling_service_name: str = "unknown") -> AlertsServiceClient: """Create alerts service client instance""" return AlertsServiceClient(config, calling_service_name)