/** * AI Insights Service * * Provides access to AI-generated insights from the AI Insights microservice. * Replaces mock data with real API integration. * * Backend endpoints: * - GET /tenants/{tenant_id}/insights * - GET /tenants/{tenant_id}/insights/{insight_id} * - POST /tenants/{tenant_id}/insights/feedback * - GET /tenants/{tenant_id}/insights/stats * - GET /tenants/{tenant_id}/insights/orchestration-ready * * Last Updated: 2025-11-03 * Status: ✅ Complete - Real API Integration */ import { apiClient } from '../client'; export interface AIInsight { id: string; tenant_id: string; type: 'forecast' | 'warning' | 'opportunity' | 'positive' | 'optimization' | 'rule'; priority: 'urgent' | 'high' | 'medium' | 'low'; category: 'demand' | 'procurement' | 'inventory' | 'production' | 'sales' | 'system' | 'business'; title: string; description: string; reasoning_data?: { type: string; parameters: Record; [key: string]: any; }; impact_type: 'cost_savings' | 'waste_reduction' | 'yield_improvement' | 'revenue' | 'system_health' | 'process_improvement'; impact_value?: number; impact_unit?: string; confidence: number; metrics_json: Record; actionable: boolean; recommendation_actions?: Array<{ label: string; action: string; params: Record; }>; source_service: string; source_model: string; detected_at: string; resolved_at?: string; resolved_by?: string; status: 'active' | 'applied' | 'dismissed' | 'resolved'; feedback_count?: number; avg_feedback_rating?: number; created_at: string; updated_at: string; } export interface AIInsightFilters { type?: string; priority?: string; category?: string; source_model?: string; status?: string; min_confidence?: number; actionable_only?: boolean; start_date?: string; end_date?: string; search?: string; limit?: number; offset?: number; } export interface AIInsightListResponse { items: AIInsight[]; total: number; limit: number; offset: number; has_more: boolean; } export interface AIInsightStatsResponse { total_insights: number; insights_by_type: Record; insights_by_priority: Record; insights_by_category: Record; insights_by_status: Record; avg_confidence: number; total_impact_value: number; actionable_insights: number; resolved_insights: number; } export interface FeedbackRequest { applied: boolean; applied_at?: string; outcome_date?: string; outcome_metrics?: Record; user_rating?: number; user_comment?: string; } export interface FeedbackResponse { insight_id: string; feedback_recorded: boolean; feedback_id: string; recorded_at: string; } export interface OrchestrationReadyInsightsRequest { target_date: string; min_confidence?: number; } export interface OrchestrationReadyInsightsResponse { target_date: string; insights: AIInsight[]; categorized_insights: { demand_forecasts: AIInsight[]; supplier_alerts: AIInsight[]; inventory_optimizations: AIInsight[]; price_opportunities: AIInsight[]; yield_predictions: AIInsight[]; business_rules: AIInsight[]; other: AIInsight[]; }; total_insights: number; } export class AIInsightsService { private readonly baseUrl = '/tenants'; /** * Get all AI insights for a tenant with optional filters */ async getInsights( tenantId: string, filters?: AIInsightFilters ): Promise { const queryParams = new URLSearchParams(); if (filters?.type) queryParams.append('type', filters.type); if (filters?.priority) queryParams.append('priority', filters.priority); if (filters?.category) queryParams.append('category', filters.category); if (filters?.source_model) queryParams.append('source_model', filters.source_model); if (filters?.status) queryParams.append('status', filters.status); if (filters?.min_confidence) queryParams.append('min_confidence', filters.min_confidence.toString()); if (filters?.actionable_only) queryParams.append('actionable_only', 'true'); if (filters?.start_date) queryParams.append('start_date', filters.start_date); if (filters?.end_date) queryParams.append('end_date', filters.end_date); if (filters?.search) queryParams.append('search', filters.search); if (filters?.limit) queryParams.append('limit', filters.limit.toString()); if (filters?.offset) queryParams.append('offset', filters.offset.toString()); const url = `${this.baseUrl}/${tenantId}/insights${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return apiClient.get(url); } /** * Get a single insight by ID */ async getInsight( tenantId: string, insightId: string ): Promise { const url = `${this.baseUrl}/${tenantId}/insights/${insightId}`; return apiClient.get(url); } /** * Get insight statistics */ async getInsightStats( tenantId: string, filters?: { start_date?: string; end_date?: string; } ): Promise { const queryParams = new URLSearchParams(); if (filters?.start_date) queryParams.append('start_date', filters.start_date); if (filters?.end_date) queryParams.append('end_date', filters.end_date); const url = `${this.baseUrl}/${tenantId}/insights/metrics/summary${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return apiClient.get(url); } /** * Get orchestration-ready insights for a specific date */ async getOrchestrationReadyInsights( tenantId: string, request: OrchestrationReadyInsightsRequest ): Promise { const url = `${this.baseUrl}/${tenantId}/insights/orchestration-ready`; const queryParams = new URLSearchParams(); queryParams.append('target_date', request.target_date); if (request.min_confidence) { queryParams.append('min_confidence', request.min_confidence.toString()); } return apiClient.get( `${url}?${queryParams.toString()}` ); } /** * Record feedback for an applied insight */ async recordFeedback( tenantId: string, insightId: string, feedback: FeedbackRequest ): Promise { const url = `${this.baseUrl}/${tenantId}/insights/${insightId}/feedback`; return apiClient.post(url, feedback); } /** * Apply an insight (mark as applied) */ async applyInsight( tenantId: string, insightId: string ): Promise { const url = `${this.baseUrl}/${tenantId}/insights/${insightId}/apply`; return apiClient.post(url, { applied_at: new Date().toISOString(), }); } /** * Dismiss an insight */ async dismissInsight( tenantId: string, insightId: string, reason?: string ): Promise { const url = `${this.baseUrl}/${tenantId}/insights/${insightId}/dismiss`; return apiClient.post(url, { reason }); } /** * Resolve an insight */ async resolveInsight( tenantId: string, insightId: string, resolution?: string ): Promise { const url = `${this.baseUrl}/${tenantId}/insights/${insightId}/resolve`; return apiClient.post(url, { resolution }); } /** * Get insights by priority (for dashboard widgets) */ async getHighPriorityInsights( tenantId: string, limit: number = 10 ): Promise { const response = await this.getInsights(tenantId, { priority: 'urgent', status: 'active', limit, }); if (response.items.length < limit) { // Add high priority if not enough urgent const highPriorityResponse = await this.getInsights(tenantId, { priority: 'high', status: 'active', limit: limit - response.items.length, }); return [...response.items, ...highPriorityResponse.items]; } return response.items; } /** * Get actionable insights (for recommendations panel) */ async getActionableInsights( tenantId: string, limit: number = 20 ): Promise { const response = await this.getInsights(tenantId, { actionable_only: true, status: 'active', limit, }); return response.items; } /** * Get insights by category */ async getInsightsByCategory( tenantId: string, category: string, limit: number = 20 ): Promise { const response = await this.getInsights(tenantId, { category, status: 'active', limit, }); return response.items; } /** * Search insights */ async searchInsights( tenantId: string, query: string, filters?: Partial ): Promise { const response = await this.getInsights(tenantId, { ...filters, search: query, limit: filters?.limit || 50, }); return response.items; } /** * Get recent insights (for activity feed) */ async getRecentInsights( tenantId: string, days: number = 7, limit: number = 50 ): Promise { const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - days); const response = await this.getInsights(tenantId, { start_date: startDate.toISOString(), end_date: endDate.toISOString(), limit, }); return response.items; } /** * Get insights summary for dashboard */ async getDashboardSummary( tenantId: string ): Promise<{ stats: AIInsightStatsResponse; highPriority: AIInsight[]; recent: AIInsight[]; }> { const [stats, highPriority, recent] = await Promise.all([ this.getInsightStats(tenantId), this.getHighPriorityInsights(tenantId, 5), this.getRecentInsights(tenantId, 7, 10), ]); return { stats, highPriority, recent, }; } /** * Format impact value for display */ formatImpactValue(insight: AIInsight): string { if (!insight.impact_value) return 'N/A'; const value = insight.impact_value; const unit = insight.impact_unit || 'units'; if (unit === 'euros_per_year' || unit === 'eur') { return `€${value.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}/year`; } else if (unit === 'euros') { return `€${value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } else if (unit === 'percentage' || unit === 'percentage_points') { return `${value.toFixed(1)}%`; } else if (unit === 'units') { return `${value.toFixed(0)} units`; } else { return `${value.toFixed(2)} ${unit}`; } } /** * Get priority badge color */ getPriorityColor(priority: string): string { switch (priority) { case 'urgent': return 'red'; case 'high': return 'orange'; case 'medium': return 'yellow'; case 'low': return 'blue'; default: return 'gray'; } } /** * Get type icon */ getTypeIcon(type: string): string { switch (type) { case 'forecast': return '📈'; case 'warning': return '⚠️'; case 'opportunity': return '💡'; case 'positive': return '✅'; case 'optimization': return '🎯'; case 'rule': return '📋'; default: return '📊'; } } /** * Calculate confidence color */ getConfidenceColor(confidence: number): string { if (confidence >= 90) return 'green'; if (confidence >= 75) return 'blue'; if (confidence >= 60) return 'yellow'; return 'red'; } } // Export singleton instance export const aiInsightsService = new AIInsightsService();