# shared/clients/alert_processor_client.py """ Alert Processor Service Client - Inter-service communication Handles communication with the alert processor service for alert lifecycle management """ import structlog from typing import Dict, Any, List, Optional from uuid import UUID from shared.clients.base_service_client import BaseServiceClient from shared.config.base import BaseServiceSettings logger = structlog.get_logger() class AlertProcessorClient(BaseServiceClient): """Client for communicating with the alert processor service via gateway""" def __init__(self, config: BaseServiceSettings, calling_service_name: str = "unknown"): super().__init__(calling_service_name, config) def get_service_base_path(self) -> str: """Return the base path for alert processor service APIs""" return "/api/v1" # ================================================================ # ALERT LIFECYCLE MANAGEMENT # ================================================================ async def acknowledge_alerts_by_metadata( self, tenant_id: UUID, alert_type: str, metadata_filter: Dict[str, Any], acknowledged_by: Optional[str] = None ) -> Dict[str, Any]: """ Acknowledge all active alerts matching alert type and metadata. Used when user actions trigger alert acknowledgment (e.g., approving a PO). Args: tenant_id: Tenant UUID alert_type: Alert type to filter (e.g., 'po_approval_needed') metadata_filter: Metadata fields to match (e.g., {'po_id': 'uuid'}) acknowledged_by: Optional user ID who acknowledged Returns: { "success": true, "acknowledged_count": 2, "alert_ids": ["uuid1", "uuid2"] } """ try: payload = { "alert_type": alert_type, "metadata_filter": metadata_filter } if acknowledged_by: payload["acknowledged_by"] = acknowledged_by result = await self.post( f"tenants/{tenant_id}/alerts/acknowledge-by-metadata", tenant_id=str(tenant_id), json=payload ) if result and result.get("success"): logger.info( "Acknowledged alerts by metadata", tenant_id=str(tenant_id), alert_type=alert_type, count=result.get("acknowledged_count", 0), calling_service=self.calling_service_name ) return result or {"success": False, "acknowledged_count": 0, "alert_ids": []} except Exception as e: logger.error( "Error acknowledging alerts by metadata", error=str(e), tenant_id=str(tenant_id), alert_type=alert_type, metadata_filter=metadata_filter, calling_service=self.calling_service_name ) return {"success": False, "acknowledged_count": 0, "alert_ids": [], "error": str(e)} async def resolve_alerts_by_metadata( self, tenant_id: UUID, alert_type: str, metadata_filter: Dict[str, Any], resolved_by: Optional[str] = None ) -> Dict[str, Any]: """ Resolve all active alerts matching alert type and metadata. Used when user actions complete an alert's underlying issue (e.g., marking delivery received). Args: tenant_id: Tenant UUID alert_type: Alert type to filter (e.g., 'delivery_overdue') metadata_filter: Metadata fields to match (e.g., {'po_id': 'uuid'}) resolved_by: Optional user ID who resolved Returns: { "success": true, "resolved_count": 1, "alert_ids": ["uuid1"] } """ try: payload = { "alert_type": alert_type, "metadata_filter": metadata_filter } if resolved_by: payload["resolved_by"] = resolved_by result = await self.post( f"tenants/{tenant_id}/alerts/resolve-by-metadata", tenant_id=str(tenant_id), json=payload ) if result and result.get("success"): logger.info( "Resolved alerts by metadata", tenant_id=str(tenant_id), alert_type=alert_type, count=result.get("resolved_count", 0), calling_service=self.calling_service_name ) return result or {"success": False, "resolved_count": 0, "alert_ids": []} except Exception as e: logger.error( "Error resolving alerts by metadata", error=str(e), tenant_id=str(tenant_id), alert_type=alert_type, metadata_filter=metadata_filter, calling_service=self.calling_service_name ) return {"success": False, "resolved_count": 0, "alert_ids": [], "error": str(e)} async def get_active_alerts( self, tenant_id: UUID, priority_level: Optional[str] = None, limit: int = 100 ) -> List[Dict[str, Any]]: """ Get active alerts for a tenant. Args: tenant_id: Tenant UUID priority_level: Optional priority filter (critical, important, standard, info) limit: Maximum number of alerts to return Returns: List of alert dictionaries """ try: params = { "status": "active", "limit": limit } if priority_level: params["priority_level"] = priority_level result = await self.get( f"tenants/{tenant_id}/alerts", tenant_id=str(tenant_id), params=params ) alerts = result.get("alerts", []) if isinstance(result, dict) else [] logger.info( "Retrieved active alerts", tenant_id=str(tenant_id), count=len(alerts), calling_service=self.calling_service_name ) return alerts except Exception as e: logger.error( "Error fetching active alerts", error=str(e), tenant_id=str(tenant_id), calling_service=self.calling_service_name ) return [] # Factory function for easy import def get_alert_processor_client(config: BaseServiceSettings, calling_service_name: str) -> AlertProcessorClient: """ Factory function to create an AlertProcessorClient instance. Args: config: Service configuration with gateway URL calling_service_name: Name of the service making the call (for logging) Returns: AlertProcessorClient instance """ return AlertProcessorClient(config, calling_service_name)