""" AI Insights Service HTTP Client Posts insights from forecasting service to AI Insights Service """ import httpx from typing import Dict, List, Any, Optional from uuid import UUID import structlog from datetime import datetime logger = structlog.get_logger() class AIInsightsClient: """ HTTP client for AI Insights Service. Allows forecasting service to post detected patterns and insights. """ def __init__(self, base_url: str, timeout: int = 30): """ Initialize AI Insights client. Args: base_url: Base URL of AI Insights Service (e.g., http://ai-insights-service:8000) timeout: Request timeout in seconds """ self.base_url = base_url.rstrip('/') self.timeout = timeout self.client = httpx.AsyncClient(timeout=self.timeout) async def close(self): """Close the HTTP client.""" await self.client.aclose() async def create_insight( self, tenant_id: UUID, insight_data: Dict[str, Any] ) -> Optional[Dict[str, Any]]: """ Create a new insight in AI Insights Service. Args: tenant_id: Tenant UUID insight_data: Insight data dictionary Returns: Created insight dict or None if failed """ url = f"{self.base_url}/api/v1/ai-insights/tenants/{tenant_id}/insights" try: # Ensure tenant_id is in the data insight_data['tenant_id'] = str(tenant_id) response = await self.client.post(url, json=insight_data) if response.status_code == 201: logger.info( "Insight created successfully", tenant_id=str(tenant_id), insight_title=insight_data.get('title') ) return response.json() else: logger.error( "Failed to create insight", status_code=response.status_code, response=response.text, insight_title=insight_data.get('title') ) return None except Exception as e: logger.error( "Error creating insight", error=str(e), tenant_id=str(tenant_id) ) return None async def create_insights_bulk( self, tenant_id: UUID, insights: List[Dict[str, Any]] ) -> Dict[str, Any]: """ Create multiple insights in bulk. Args: tenant_id: Tenant UUID insights: List of insight data dictionaries Returns: Dictionary with success/failure counts """ results = { 'total': len(insights), 'successful': 0, 'failed': 0, 'created_insights': [] } for insight_data in insights: result = await self.create_insight(tenant_id, insight_data) if result: results['successful'] += 1 results['created_insights'].append(result) else: results['failed'] += 1 logger.info( "Bulk insight creation complete", total=results['total'], successful=results['successful'], failed=results['failed'] ) return results async def get_insights( self, tenant_id: UUID, filters: Optional[Dict[str, Any]] = None ) -> Optional[Dict[str, Any]]: """ Get insights for a tenant. Args: tenant_id: Tenant UUID filters: Optional filters (category, priority, etc.) Returns: Paginated insights response or None if failed """ url = f"{self.base_url}/api/v1/ai-insights/tenants/{tenant_id}/insights" try: response = await self.client.get(url, params=filters or {}) if response.status_code == 200: return response.json() else: logger.error( "Failed to get insights", status_code=response.status_code ) return None except Exception as e: logger.error("Error getting insights", error=str(e)) return None async def get_orchestration_ready_insights( self, tenant_id: UUID, target_date: datetime, min_confidence: int = 70 ) -> Optional[Dict[str, List[Dict[str, Any]]]]: """ Get insights ready for orchestration workflow. Args: tenant_id: Tenant UUID target_date: Target date for orchestration min_confidence: Minimum confidence threshold Returns: Categorized insights or None if failed """ url = f"{self.base_url}/api/v1/ai-insights/tenants/{tenant_id}/insights/orchestration-ready" params = { 'target_date': target_date.isoformat(), 'min_confidence': min_confidence } try: response = await self.client.get(url, params=params) if response.status_code == 200: return response.json() else: logger.error( "Failed to get orchestration insights", status_code=response.status_code ) return None except Exception as e: logger.error("Error getting orchestration insights", error=str(e)) return None async def record_feedback( self, tenant_id: UUID, insight_id: UUID, feedback_data: Dict[str, Any] ) -> Optional[Dict[str, Any]]: """ Record feedback for an applied insight. Args: tenant_id: Tenant UUID insight_id: Insight UUID feedback_data: Feedback data Returns: Feedback response or None if failed """ url = f"{self.base_url}/api/v1/ai-insights/tenants/{tenant_id}/insights/{insight_id}/feedback" try: feedback_data['insight_id'] = str(insight_id) response = await self.client.post(url, json=feedback_data) if response.status_code in [200, 201]: logger.info( "Feedback recorded", insight_id=str(insight_id), success=feedback_data.get('success') ) return response.json() else: logger.error( "Failed to record feedback", status_code=response.status_code ) return None except Exception as e: logger.error("Error recording feedback", error=str(e)) return None async def health_check(self) -> bool: """ Check if AI Insights Service is healthy. Returns: True if healthy, False otherwise """ url = f"{self.base_url}/health" try: response = await self.client.get(url) return response.status_code == 200 except Exception as e: logger.error("AI Insights Service health check failed", error=str(e)) return False