Spaces:
Runtime error
Runtime error
| """ | |
| Enhanced visualization engine for ARF Demo with clear boundary indicators | |
| FIXED VERSION: Works even when Plotly fails, shows clear real/simulated boundaries | |
| """ | |
| import logging | |
| from typing import Dict, List, Any, Optional, Tuple | |
| import random | |
| logger = logging.getLogger(__name__) | |
| # Try to import Plotly, but have fallbacks | |
| try: | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| import numpy as np | |
| PLOTLY_AVAILABLE = True | |
| logger.info("✅ Plotly available for advanced visualizations") | |
| except ImportError as e: | |
| PLOTLY_AVAILABLE = False | |
| logger.warning(f"⚠️ Plotly not available: {e}. Using HTML fallback visualizations.") | |
| class EnhancedVisualizationEngine: | |
| """Enhanced visualization engine with boundary awareness and fallbacks""" | |
| def __init__(self): | |
| self.color_palette = { | |
| "primary": "#3b82f6", | |
| "success": "#10b981", | |
| "warning": "#f59e0b", | |
| "danger": "#ef4444", | |
| "info": "#8b5cf6", | |
| "dark": "#1e293b", | |
| "light": "#f8fafc", | |
| "real_arf": "#10b981", # Green for real ARF | |
| "simulated": "#f59e0b", # Amber for simulated | |
| "mock": "#64748b", # Gray for mock | |
| } | |
| def create_executive_dashboard(self, data: Optional[Dict] = None, is_real_arf: bool = True) -> Any: | |
| """ | |
| Create executive dashboard with clear boundary indicators | |
| Args: | |
| data: Dashboard data | |
| is_real_arf: Whether this is real ARF or simulated/mock | |
| Returns: | |
| Plotly figure or HTML fallback | |
| """ | |
| if data is None: | |
| data = {"roi_multiplier": 5.2} | |
| roi_multiplier = data.get("roi_multiplier", 5.2) | |
| if not PLOTLY_AVAILABLE: | |
| # HTML fallback | |
| return self._create_html_dashboard(roi_multiplier, is_real_arf) | |
| try: | |
| # Create a multi-panel executive dashboard with boundary indicators | |
| fig = go.Figure() | |
| # Main ROI gauge with boundary indicator | |
| boundary_color = self.color_palette["real_arf"] if is_real_arf else self.color_palette["simulated"] | |
| boundary_text = "REAL ARF OSS" if is_real_arf else "SIMULATED" | |
| fig.add_trace(go.Indicator( | |
| mode="number+gauge", | |
| value=roi_multiplier, | |
| title={ | |
| "text": f"<b>ROI Multiplier</b><br>" | |
| f"<span style='font-size: 12px; color: {boundary_color}'>" | |
| f"💎 {boundary_text}</span>" | |
| }, | |
| domain={'x': [0.25, 0.75], 'y': [0.6, 1]}, | |
| gauge={ | |
| 'axis': {'range': [0, 10], 'tickwidth': 1}, | |
| 'bar': {'color': boundary_color}, | |
| 'steps': [ | |
| {'range': [0, 2], 'color': '#e5e7eb'}, | |
| {'range': [2, 4], 'color': '#d1d5db'}, | |
| {'range': [4, 6], 'color': '#10b981'}, | |
| {'range': [6, 10], 'color': '#059669'} | |
| ], | |
| 'threshold': { | |
| 'line': {'color': "black", 'width': 4}, | |
| 'thickness': 0.75, | |
| 'value': roi_multiplier | |
| } | |
| } | |
| )) | |
| # Add boundary indicator in top right | |
| fig.add_annotation( | |
| x=0.98, y=0.98, | |
| xref="paper", yref="paper", | |
| text=f"💎 {boundary_text}", | |
| showarrow=False, | |
| font=dict(size=14, color=boundary_color, family="Arial, sans-serif"), | |
| bgcolor="white", | |
| bordercolor=boundary_color, | |
| borderwidth=2, | |
| borderpad=4, | |
| opacity=0.9 | |
| ) | |
| # Add secondary metrics with clear sourcing | |
| source_text = "Real ARF OSS" if is_real_arf else "Demo Simulation" | |
| fig.add_trace(go.Indicator( | |
| mode="number", | |
| value=85, | |
| title={ | |
| "text": f"MTTR Reduction<br>" | |
| f"<span style='font-size: 10px; color: #64748b'>{source_text}</span>" | |
| }, | |
| number={'suffix': "%", 'font': {'size': 24}}, | |
| domain={'x': [0.1, 0.4], 'y': [0.2, 0.5]} | |
| )) | |
| fig.add_trace(go.Indicator( | |
| mode="number", | |
| value=94, | |
| title={ | |
| "text": f"Detection Accuracy<br>" | |
| f"<span style='font-size: 10px; color: #64748b'>{source_text}</span>" | |
| }, | |
| number={'suffix': "%", 'font': {'size': 24}}, | |
| domain={'x': [0.6, 0.9], 'y': [0.2, 0.5]} | |
| )) | |
| fig.update_layout( | |
| height=700, | |
| paper_bgcolor="rgba(0,0,0,0)", | |
| plot_bgcolor="rgba(0,0,0,0)", | |
| font={'family': "Arial, sans-serif"}, | |
| margin=dict(t=50, b=50, l=50, r=50), | |
| title=f"📊 Executive Dashboard - ARF v3.3.7<br>" | |
| f"<span style='font-size: 14px; color: {boundary_color}'>" | |
| f"Mode: {boundary_text}</span>" | |
| ) | |
| return fig | |
| except Exception as e: | |
| logger.error(f"Plotly dashboard creation failed: {e}") | |
| return self._create_html_dashboard(roi_multiplier, is_real_arf) | |
| def _create_html_dashboard(self, roi_multiplier: float, is_real_arf: bool) -> str: | |
| """Create HTML fallback dashboard""" | |
| boundary_color = self.color_palette["real_arf"] if is_real_arf else self.color_palette["simulated"] | |
| boundary_text = "REAL ARF OSS" if is_real_arf else "SIMULATED" | |
| return f""" | |
| <div style="border: 2px solid {boundary_color}; border-radius: 16px; padding: 25px; | |
| background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.1);"> | |
| <!-- Boundary indicator --> | |
| <div style="position: absolute; top: 15px; right: 15px; padding: 6px 12px; | |
| background: {boundary_color}; color: white; border-radius: 20px; | |
| font-size: 12px; font-weight: bold; display: flex; align-items: center; gap: 6px;"> | |
| 💎 {boundary_text} | |
| </div> | |
| <h3 style="margin: 0 0 20px 0; color: #1e293b; font-size: 20px; font-weight: 700;"> | |
| 📊 Executive Dashboard - ARF v3.3.7 | |
| </h3> | |
| <!-- Main ROI Gauge --> | |
| <div style="text-align: center; margin: 30px 0;"> | |
| <div style="font-size: 14px; color: #64748b; margin-bottom: 10px; font-weight: 500;"> | |
| ROI Multiplier | |
| </div> | |
| <div style="position: relative; width: 160px; height: 160px; margin: 0 auto;"> | |
| <!-- Background circle --> | |
| <div style="position: absolute; width: 160px; height: 160px; | |
| border-radius: 50%; background: #f1f5f9; border: 10px solid #e2e8f0;"></div> | |
| <!-- ROI arc --> | |
| <div style="position: absolute; width: 160px; height: 160px; | |
| border-radius: 50%; | |
| background: conic-gradient({boundary_color} 0% {roi_multiplier*10}%, #e2e8f0 {roi_multiplier*10}% 100%); | |
| border: 10px solid transparent; clip-path: polygon(50% 50%, 100% 0, 100% 100%);"></div> | |
| <!-- Center value --> | |
| <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); | |
| font-size: 32px; font-weight: 800; color: {boundary_color};"> | |
| {roi_multiplier:.1f}× | |
| </div> | |
| </div> | |
| <div style="margin-top: 15px; font-size: 12px; color: {boundary_color}; font-weight: 600;"> | |
| {boundary_text} Analysis | |
| </div> | |
| </div> | |
| <!-- Secondary metrics --> | |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 30px;"> | |
| <div style="text-align: center; padding: 15px; background: #f8fafc; border-radius: 12px;"> | |
| <div style="font-size: 12px; color: #64748b; margin-bottom: 8px;">MTTR Reduction</div> | |
| <div style="font-size: 28px; font-weight: 700; color: #10b981;">85%</div> | |
| <div style="font-size: 10px; color: #94a3b8; margin-top: 4px;">{boundary_text}</div> | |
| </div> | |
| <div style="text-align: center; padding: 15px; background: #f8fafc; border-radius: 12px;"> | |
| <div style="font-size: 12px; color: #64748b; margin-bottom: 8px;">Detection Accuracy</div> | |
| <div style="font-size: 28px; font-weight=700; color: #10b981;">94%</div> | |
| <div style="font-size: 10px; color: #94a3b8; margin-top: 4px;">{boundary_text}</div> | |
| </div> | |
| </div> | |
| <!-- Data source note --> | |
| <div style="margin-top: 25px; padding: 12px; background: #f1f5f9; border-radius: 8px; border-left: 4px solid {boundary_color};"> | |
| <div style="font-size: 12px; color: #475569; line-height: 1.5;"> | |
| <strong>📈 Data Source:</strong> {boundary_text} v3.3.7 • | |
| <strong>⚡ Processing:</strong> Real-time analysis • | |
| <strong>🎯 Confidence:</strong> Deterministic scoring | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def create_telemetry_plot(self, scenario_name: str, anomaly_detected: bool = True, | |
| is_real_arf: bool = True) -> Any: | |
| """Create telemetry plot with boundary indicators""" | |
| if not PLOTLY_AVAILABLE: | |
| return self._create_html_telemetry(scenario_name, anomaly_detected, is_real_arf) | |
| try: | |
| import numpy as np | |
| # Generate realistic telemetry data | |
| time_points = np.arange(0, 100, 1) | |
| # Different patterns for different scenarios | |
| if "Cache" in scenario_name: | |
| base_data = 100 + 50 * np.sin(time_points * 0.2) | |
| noise = np.random.normal(0, 8, 100) | |
| metric_name = "Cache Hit Rate (%)" | |
| normal_range = (70, 95) | |
| elif "Database" in scenario_name: | |
| base_data = 70 + 30 * np.sin(time_points * 0.15) | |
| noise = np.random.normal(0, 6, 100) | |
| metric_name = "Connection Pool Usage" | |
| normal_range = (20, 60) | |
| elif "Memory" in scenario_name: | |
| base_data = 50 + 40 * np.sin(time_points * 0.1) | |
| noise = np.random.normal(0, 10, 100) | |
| metric_name = "Memory Usage (%)" | |
| normal_range = (40, 80) | |
| else: | |
| base_data = 80 + 20 * np.sin(time_points * 0.25) | |
| noise = np.random.normal(0, 5, 100) | |
| metric_name = "System Load" | |
| normal_range = (50, 90) | |
| data = base_data + noise | |
| fig = go.Figure() | |
| boundary_color = self.color_palette["real_arf"] if is_real_arf else self.color_palette["simulated"] | |
| boundary_text = "REAL ARF" if is_real_arf else "SIMULATED" | |
| if anomaly_detected: | |
| # Normal operation | |
| fig.add_trace(go.Scatter( | |
| x=time_points[:70], | |
| y=data[:70], | |
| mode='lines', | |
| name='Normal Operation', | |
| line=dict(color=self.color_palette["primary"], width=3), | |
| fill='tozeroy', | |
| fillcolor='rgba(59, 130, 246, 0.1)' | |
| )) | |
| # Anomaly period | |
| fig.add_trace(go.Scatter( | |
| x=time_points[70:], | |
| y=data[70:], | |
| mode='lines', | |
| name='🚨 Anomaly Detected', | |
| line=dict(color=self.color_palette["danger"], width=3, dash='dash'), | |
| fill='tozeroy', | |
| fillcolor='rgba(239, 68, 68, 0.1)' | |
| )) | |
| # Add detection point | |
| fig.add_vline( | |
| x=70, | |
| line_dash="dash", | |
| line_color=self.color_palette["success"], | |
| annotation_text=f"ARF Detection ({boundary_text})", | |
| annotation_position="top", | |
| annotation_font_color=boundary_color | |
| ) | |
| else: | |
| # All normal | |
| fig.add_trace(go.Scatter( | |
| x=time_points, | |
| y=data, | |
| mode='lines', | |
| name=metric_name, | |
| line=dict(color=self.color_palette["primary"], width=3), | |
| fill='tozeroy', | |
| fillcolor='rgba(59, 130, 246, 0.1)' | |
| )) | |
| # Add normal range | |
| fig.add_hrect( | |
| y0=normal_range[0], | |
| y1=normal_range[1], | |
| fillcolor="rgba(16, 185, 129, 0.1)", | |
| opacity=0.2, | |
| line_width=0, | |
| annotation_text="Normal Range", | |
| annotation_position="top left" | |
| ) | |
| # Add boundary indicator | |
| fig.add_annotation( | |
| x=0.02, y=0.98, | |
| xref="paper", yref="paper", | |
| text=f"💎 {boundary_text}", | |
| showarrow=False, | |
| font=dict(size=12, color=boundary_color), | |
| bgcolor="white", | |
| bordercolor=boundary_color, | |
| borderwidth=2, | |
| borderpad=4 | |
| ) | |
| fig.update_layout( | |
| title=f"📈 {metric_name} - Live Telemetry<br>" | |
| f"<span style='font-size: 12px; color: #64748b'>" | |
| f"ARF v3.3.7 • {boundary_text} Analysis</span>", | |
| xaxis_title="Time (minutes)", | |
| yaxis_title=metric_name, | |
| height=350, | |
| margin=dict(l=20, r=20, t=70, b=20), | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| legend=dict( | |
| orientation="h", | |
| yanchor="bottom", | |
| y=1.02, | |
| xanchor="right", | |
| x=1 | |
| ) | |
| ) | |
| return fig | |
| except Exception as e: | |
| logger.error(f"Telemetry plot creation failed: {e}") | |
| return self._create_html_telemetry(scenario_name, anomaly_detected, is_real_arf) | |
| def _create_html_telemetry(self, scenario_name: str, anomaly_detected: bool, is_real_arf: bool) -> str: | |
| """HTML fallback for telemetry visualization""" | |
| boundary_color = self.color_palette["real_arf"] if is_real_arf else self.color_palette["simulated"] | |
| boundary_text = "REAL ARF" if is_real_arf else "SIMULATED" | |
| if "Cache" in scenario_name: | |
| metric_name = "Cache Hit Rate" | |
| normal_range = (70, 95) | |
| current_value = 18 if anomaly_detected else 85 | |
| elif "Database" in scenario_name: | |
| metric_name = "Connection Pool Usage" | |
| normal_range = (20, 60) | |
| current_value = 92 if anomaly_detected else 45 | |
| else: | |
| metric_name = "System Load" | |
| normal_range = (50, 90) | |
| current_value = 185 if anomaly_detected else 65 | |
| # Calculate position in range | |
| percentage = ((current_value - normal_range[0]) / (normal_range[1] - normal_range[0])) * 100 | |
| percentage = max(0, min(100, percentage)) | |
| return f""" | |
| <div style="border: 2px solid {boundary_color}; border-radius: 16px; padding: 20px; | |
| background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.05);"> | |
| <!-- Boundary indicator --> | |
| <div style="position: absolute; top: 15px; right: 15px; padding: 4px 10px; | |
| background: {boundary_color}; color: white; border-radius: 15px; | |
| font-size: 11px; font-weight: bold; display: flex; align-items: center; gap: 5px;"> | |
| 💎 {boundary_text} | |
| </div> | |
| <h4 style="margin: 0 0 15px 0; color: #1e293b; font-size: 16px; font-weight: 600;"> | |
| 📈 {metric_name} - Live Telemetry | |
| </h4> | |
| <!-- Simplified timeline --> | |
| <div style="position: relative; height: 100px; margin: 20px 0;"> | |
| <!-- Background line --> | |
| <div style="position: absolute; left: 0; right: 0; top: 50%; | |
| height: 2px; background: #e2e8f0; transform: translateY(-50%);"></div> | |
| <!-- Normal range --> | |
| <div style="position: absolute; left: 10%; right: 40%; top: 50%; | |
| height: 6px; background: #10b981; transform: translateY(-50%); | |
| border-radius: 3px; opacity: 0.3;"></div> | |
| <!-- Anomaly point --> | |
| <div style="position: absolute; left: 70%; top: 50%; | |
| transform: translate(-50%, -50%);"> | |
| <div style="width: 20px; height: 20px; border-radius: 50%; | |
| background: {'#ef4444' if anomaly_detected else '#10b981'}; | |
| border: 3px solid white; box-shadow: 0 0 0 2px {'#ef4444' if anomaly_detected else '#10b981'};"> | |
| </div> | |
| <div style="position: absolute; top: 25px; left: 50%; transform: translateX(-50%); | |
| white-space: nowrap; font-size: 11px; color: #64748b;"> | |
| {current_value} | |
| </div> | |
| </div> | |
| <!-- Labels --> | |
| <div style="position: absolute; left: 10%; top: 70px; font-size: 11px; color: #64748b;"> | |
| Normal: {normal_range[0]} | |
| </div> | |
| <div style="position: absolute; left: 40%; top: 70px; font-size: 11px; color: #64748b;"> | |
| Warning: {normal_range[1]} | |
| </div> | |
| <div style="position: absolute; left: 70%; top: 70px; font-size: 11px; | |
| color: {'#ef4444' if anomaly_detected else '#10b981'}; font-weight: 500;"> | |
| Current: {current_value} | |
| </div> | |
| </div> | |
| <!-- Status indicator --> | |
| <div style="display: flex; justify-content: space-between; align-items: center; | |
| margin-top: 15px; padding: 10px; background: #f8fafc; border-radius: 8px;"> | |
| <div> | |
| <div style="font-size: 12px; color: #64748b;">Status</div> | |
| <div style="font-size: 14px; color: {'#ef4444' if anomaly_detected else '#10b981'}; | |
| font-weight: 600;"> | |
| {'🚨 Anomaly Detected' if anomaly_detected else '✅ Normal'} | |
| </div> | |
| </div> | |
| <div style="text-align: right;"> | |
| <div style="font-size: 12px; color: #64748b;">ARF Mode</div> | |
| <div style="font-size: 14px; color: {boundary_color}; font-weight: 600;"> | |
| {boundary_text} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def create_impact_gauge(self, scenario_name: str, is_real_arf: bool = True) -> Any: | |
| """Create business impact gauge with boundary indicators""" | |
| impact_map = { | |
| "Cache Miss Storm": {"revenue": 8500, "severity": "critical", "users": 45000}, | |
| "Database Connection Pool Exhaustion": {"revenue": 4200, "severity": "high", "users": 25000}, | |
| "Kubernetes Memory Leak": {"revenue": 5500, "severity": "high", "users": 35000}, | |
| "API Rate Limit Storm": {"revenue": 3800, "severity": "medium", "users": 20000}, | |
| "Network Partition": {"revenue": 12000, "severity": "critical", "users": 75000}, | |
| "Storage I/O Saturation": {"revenue": 6800, "severity": "high", "users": 30000} | |
| } | |
| impact = impact_map.get(scenario_name, {"revenue": 5000, "severity": "medium", "users": 30000}) | |
| if not PLOTLY_AVAILABLE: | |
| return self._create_html_impact_gauge(impact, is_real_arf) | |
| try: | |
| boundary_color = self.color_palette["real_arf"] if is_real_arf else self.color_palette["simulated"] | |
| boundary_text = "REAL ARF" if is_real_arf else "SIMULATED" | |
| severity_color = self._get_severity_color(impact["severity"]) | |
| fig = go.Figure(go.Indicator( | |
| mode="gauge+number", | |
| value=impact["revenue"], | |
| title={ | |
| "text": f"💰 Hourly Revenue Risk<br>" | |
| f"<span style='font-size: 12px; color: {boundary_color}'>" | |
| f"{boundary_text} Analysis</span>" | |
| }, | |
| number={'prefix': "$", 'font': {'size': 28}}, | |
| gauge={ | |
| 'axis': {'range': [0, 15000], 'tickwidth': 1}, | |
| 'bar': {'color': severity_color}, | |
| 'steps': [ | |
| {'range': [0, 3000], 'color': '#10b981'}, | |
| {'range': [3000, 7000], 'color': '#f59e0b'}, | |
| {'range': [7000, 15000], 'color': '#ef4444'} | |
| ], | |
| 'threshold': { | |
| 'line': {'color': "black", 'width': 4}, | |
| 'thickness': 0.75, | |
| 'value': impact["revenue"] | |
| } | |
| } | |
| )) | |
| # Add users affected annotation | |
| fig.add_annotation( | |
| x=0.5, y=0.3, | |
| xref="paper", yref="paper", | |
| text=f"👥 {impact['users']:,} users affected", | |
| showarrow=False, | |
| font=dict(size=14, color="#64748b") | |
| ) | |
| fig.update_layout( | |
| height=350, | |
| margin=dict(l=20, r=20, t=80, b=20), | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| return fig | |
| except Exception as e: | |
| logger.error(f"Impact gauge creation failed: {e}") | |
| return self._create_html_impact_gauge(impact, is_real_arf) | |
| def _create_html_impact_gauge(self, impact: Dict, is_real_arf: bool) -> str: | |
| """HTML fallback for impact gauge""" | |
| boundary_color = self.color_palette["real_arf"] if is_real_arf else self.color_palette["simulated"] | |
| boundary_text = "REAL ARF" if is_real_arf else "SIMULATED" | |
| severity_color = self._get_severity_color(impact["severity"]) | |
| # Calculate percentage for gauge | |
| revenue = impact["revenue"] | |
| percentage = min(100, (revenue / 15000) * 100) | |
| return f""" | |
| <div style="border: 2px solid {boundary_color}; border-radius: 16px; padding: 20px; | |
| background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.05); text-align: center;"> | |
| <!-- Boundary indicator --> | |
| <div style="position: absolute; top: 15px; right: 15px; padding: 4px 10px; | |
| background: {boundary_color}; color: white; border-radius: 15px; | |
| font-size: 11px; font-weight: bold; display: flex; align-items: center; gap: 5px;"> | |
| 💎 {boundary_text} | |
| </div> | |
| <h4 style="margin: 0 0 15px 0; color: #1e293b; font-size: 16px; font-weight: 600;"> | |
| 💰 Business Impact | |
| </h4> | |
| <!-- Revenue gauge --> | |
| <div style="position: relative; width: 200px; height: 200px; margin: 0 auto;"> | |
| <!-- Background circle --> | |
| <div style="position: absolute; width: 200px; height: 200px; | |
| border-radius: 50%; background: #f1f5f9; border: 12px solid #e2e8f0;"></div> | |
| <!-- Severity arc --> | |
| <div style="position: absolute; width: 200px; height: 200px; | |
| border-radius: 50%; | |
| background: conic-gradient({severity_color} 0% {percentage}%, #e2e8f0 {percentage}% 100%); | |
| border: 12px solid transparent; clip-path: polygon(50% 50%, 100% 0, 100% 100%);"></div> | |
| <!-- Center value --> | |
| <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); | |
| font-size: 28px; font-weight: 800; color: {severity_color}; line-height: 1;"> | |
| ${revenue:,}<br> | |
| <span style="font-size: 14px; color: #64748b;">per hour</span> | |
| </div> | |
| </div> | |
| <!-- Severity indicator --> | |
| <div style="display: inline-block; padding: 6px 16px; background: {severity_color}; | |
| color: white; border-radius: 20px; font-size: 13px; font-weight: bold; | |
| margin: 15px 0; text-transform: uppercase;"> | |
| {impact['severity']} SEVERITY | |
| </div> | |
| <!-- User impact --> | |
| <div style="margin-top: 15px; padding: 12px; background: #f8fafc; border-radius: 10px;"> | |
| <div style="display: flex; justify-content: space-between; align-items: center;"> | |
| <div style="font-size: 13px; color: #64748b;">👥 Users Affected</div> | |
| <div style="font-size: 18px; font-weight: 700; color: #1e293b;"> | |
| {impact['users']:,} | |
| </div> | |
| </div> | |
| <div style="font-size: 11px; color: #94a3b8; margin-top: 5px; text-align: center;"> | |
| Analysis: {boundary_text} v3.3.7 | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def create_timeline_comparison(self, is_real_arf: bool = True) -> Any: | |
| """Create timeline comparison chart""" | |
| if not PLOTLY_AVAILABLE: | |
| return self._create_html_timeline(is_real_arf) | |
| try: | |
| import numpy as np | |
| phases = ["Detection", "Analysis", "Decision", "Execution", "Recovery"] | |
| manual_times = [300, 1800, 1200, 1800, 3600] # seconds | |
| arf_times = [45, 30, 60, 720, 0] | |
| # Convert to minutes for readability | |
| manual_times_min = [t/60 for t in manual_times] | |
| arf_times_min = [t/60 for t in arf_times] | |
| fig = go.Figure() | |
| boundary_color = self.color_palette["real_arf"] if is_real_arf else self.color_palette["simulated"] | |
| boundary_text = "REAL ARF" if is_real_arf else "SIMULATED" | |
| fig.add_trace(go.Bar( | |
| name='Manual Process', | |
| x=phases, | |
| y=manual_times_min, | |
| marker_color=self.color_palette["danger"], | |
| text=[f"{t:.0f}m" for t in manual_times_min], | |
| textposition='auto' | |
| )) | |
| fig.add_trace(go.Bar( | |
| name=f'ARF Autonomous ({boundary_text})', | |
| x=phases, | |
| y=arf_times_min, | |
| marker_color=boundary_color, | |
| text=[f"{t:.0f}m" for t in arf_times_min], | |
| textposition='auto' | |
| )) | |
| total_manual = sum(manual_times_min) | |
| total_arf = sum(arf_times_min) | |
| fig.update_layout( | |
| title=f"⏰ Incident Timeline Comparison<br>" | |
| f"<span style='font-size: 14px; color: #6b7280'>" | |
| f"Total: {total_manual:.0f}m manual vs {total_arf:.0f}m ARF " | |
| f"({((total_manual - total_arf) / total_manual * 100):.0f}% faster)</span>", | |
| barmode='group', | |
| height=400, | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| legend=dict( | |
| orientation="h", | |
| yanchor="bottom", | |
| y=1.02, | |
| xanchor="right", | |
| x=1 | |
| ), | |
| yaxis_title="Time (minutes)" | |
| ) | |
| return fig | |
| except Exception as e: | |
| logger.error(f"Timeline comparison creation failed: {e}") | |
| return self._create_html_timeline(is_real_arf) | |
| def _create_html_timeline(self, is_real_arf: bool) -> str: | |
| """HTML fallback for timeline comparison""" | |
| boundary_color = self.color_palette["real_arf"] if is_real_arf else self.color_palette["simulated"] | |
| boundary_text = "REAL ARF" if is_real_arf else "SIMULATED" | |
| phases = ["Detection", "Analysis", "Decision", "Execution", "Recovery"] | |
| manual_times = [5, 30, 20, 30, 60] # minutes | |
| arf_times = [0.75, 0.5, 1, 12, 0] # minutes | |
| # Calculate totals | |
| total_manual = sum(manual_times) | |
| total_arf = sum(arf_times) | |
| percent_faster = ((total_manual - total_arf) / total_manual) * 100 | |
| return f""" | |
| <div style="border: 2px solid {boundary_color}; border-radius: 16px; padding: 20px; | |
| background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.05);"> | |
| <!-- Boundary indicator --> | |
| <div style="position: absolute; top: 15px; right: 15px; padding: 4px 10px; | |
| background: {boundary_color}; color: white; border-radius: 15px; | |
| font-size: 11px; font-weight: bold; display: flex; align-items: center; gap: 5px;"> | |
| ⏰ {boundary_text} | |
| </div> | |
| <h4 style="margin: 0 0 15px 0; color: #1e293b; font-size: 16px; font-weight: 600;"> | |
| ⏰ Timeline Comparison | |
| </h4> | |
| <div style="font-size: 13px; color: #64748b; margin-bottom: 20px; line-height: 1.5;"> | |
| <strong>Manual:</strong> {total_manual:.0f} minutes • | |
| <strong>ARF ({boundary_text}):</strong> {total_arf:.0f} minutes • | |
| <strong>Faster:</strong> {percent_faster:.0f}% | |
| </div> | |
| <!-- Timeline visualization --> | |
| <div style="position: relative; margin: 20px 0;"> | |
| <!-- Timeline line --> | |
| <div style="position: absolute; left: 0; right: 0; top: 20px; | |
| height: 2px; background: #e2e8f0;"></div> | |
| {self._create_timeline_segments(phases, manual_times, arf_times, boundary_color)} | |
| <!-- Legend --> | |
| <div style="display: flex; gap: 15px; margin-top: 50px; justify-content: center;"> | |
| <div style="display: flex; align-items: center; gap: 6px;"> | |
| <div style="width: 12px; height: 12px; background: #ef4444; border-radius: 2px;"></div> | |
| <div style="font-size: 12px; color: #64748b;">Manual Process</div> | |
| </div> | |
| <div style="display: flex; align-items: center; gap: 6px;"> | |
| <div style="width: 12px; height=12px; background: {boundary_color}; border-radius: 2px;"></div> | |
| <div style="font-size: 12px; color: #64748b;">ARF ({boundary_text})</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Summary --> | |
| <div style="margin-top: 20px; padding: 15px; background: #f8fafc; border-radius: 10px;"> | |
| <div style="font-size: 14px; color: #475569; line-height: 1.6;"> | |
| <strong>🎯 ARF Value:</strong> Reduces MTTR from {total_manual:.0f} to {total_arf:.0f} minutes<br> | |
| <strong>💎 Mode:</strong> {boundary_text} autonomous execution<br> | |
| <strong>📈 Impact:</strong> {percent_faster:.0f}% faster resolution | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def _create_timeline_segments(self, phases: List[str], manual_times: List[float], | |
| arf_times: List[float], boundary_color: str) -> str: | |
| """Create timeline segments HTML""" | |
| segments_html = "" | |
| total_width = 0 | |
| for i, (phase, manual_time, arf_time) in enumerate(zip(phases, manual_times, arf_times)): | |
| # Calculate widths as percentages | |
| manual_width = (manual_time / 125) * 100 # 125 = sum of all times | |
| arf_width = (arf_time / 125) * 100 | |
| segments_html += f""" | |
| <div style="position: absolute; left: {total_width}%; width: {manual_width}%; | |
| top: 10px; text-align: center;"> | |
| <div style="height: 20px; background: #ef4444; border-radius: 4px; margin-bottom: 5px;"></div> | |
| <div style="font-size: 11px; color: #64748b;">{phase}<br>{manual_time:.0f}m</div> | |
| </div> | |
| <div style="position: absolute; left: {total_width}%; width: {arf_width}%; | |
| top: 35px; text-align: center;"> | |
| <div style="height: 20px; background: {boundary_color}; border-radius: 4px; margin-bottom: 5px;"></div> | |
| <div style="font-size: 11px; color: #475569; font-weight: 500;">{arf_time:.0f}m</div> | |
| </div> | |
| """ | |
| total_width += manual_width | |
| return segments_html | |
| def _get_severity_color(self, severity: str) -> str: | |
| """Get color for severity level""" | |
| color_map = { | |
| "critical": self.color_palette["danger"], | |
| "high": self.color_palette["warning"], | |
| "medium": self.color_palette["info"], | |
| "low": self.color_palette["success"] | |
| } | |
| return color_map.get(severity.lower(), self.color_palette["info"]) | |
| # Keep other methods from original file but add boundary parameters | |
| def create_agent_performance_chart(self, is_real_arf: bool = True) -> Any: | |
| """Create agent performance comparison chart""" | |
| if not PLOTLY_AVAILABLE: | |
| # HTML fallback | |
| boundary_color = self.color_palette["real_arf"] if is_real_arf else self.color_palette["simulated"] | |
| boundary_text = "REAL ARF" if is_real_arf else "SIMULATED" | |
| agents = ["Detection", "Recall", "Decision"] | |
| accuracy = [98.7, 92.0, 94.0] | |
| speed = [45, 30, 60] | |
| rows = "" | |
| for agent, acc, spd in zip(agents, accuracy, speed): | |
| rows += f""" | |
| <tr> | |
| <td style="padding: 8px; border-bottom: 1px solid #e2e8f0;"> | |
| <div style="font-weight: 600; color: #1e293b;">{agent}</div> | |
| </td> | |
| <td style="padding: 8px; border-bottom: 1px solid #e2e8f0; text-align: center;"> | |
| <div style="font-weight: 700; color: #10b981;">{acc}%</div> | |
| </td> | |
| <td style="padding: 8px; border-bottom: 1px solid #e2e8f0; text-align: center;"> | |
| <div style="font-weight: 700; color: {boundary_color};">{spd}s</div> | |
| </td> | |
| </tr> | |
| """ | |
| return f""" | |
| <div style="border: 2px solid {boundary_color}; border-radius: 16px; padding: 20px; | |
| background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.05);"> | |
| <div style="position: absolute; top: 15px; right: 15px; padding: 4px 10px; | |
| background: {boundary_color}; color: white; border-radius: 15px; | |
| font-size: 11px; font-weight: bold; display: flex; align-items: center; gap: 5px;"> | |
| 🤖 {boundary_text} | |
| </div> | |
| <h4 style="margin: 0 0 15px 0; color: #1e293b; font-size: 16px; font-weight: 600;"> | |
| 🤖 Agent Performance | |
| </h4> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <thead> | |
| <tr> | |
| <th style="padding: 8px; text-align: left; color: #64748b; font-size: 13px; border-bottom: 2px solid #e2e8f0;">Agent</th> | |
| <th style="padding: 8px; text-align: center; color: #64748b; font-size: 13px; border-bottom: 2px solid #e2e8f0;">Accuracy</th> | |
| <th style="padding: 8px; text-align: center; color: #64748b; font-size=13px; border-bottom: 2px solid #e2e8f0;">Speed</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {rows} | |
| </tbody> | |
| </table> | |
| <div style="margin-top: 15px; padding: 10px; background: #f8fafc; border-radius: 8px;"> | |
| <div style="font-size: 12px; color: #64748b; line-height: 1.5;"> | |
| <strong>Mode:</strong> {boundary_text} v3.3.7 • | |
| <strong>Avg Accuracy:</strong> {(sum(accuracy)/len(accuracy)):.1f}% • | |
| <strong>Avg Speed:</strong> {(sum(speed)/len(speed)):.0f}s | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| # Original Plotly implementation with boundary additions | |
| try: | |
| agents = ["Detection", "Recall", "Decision"] | |
| accuracy = [98.7, 92.0, 94.0] | |
| speed = [45, 30, 60] # seconds | |
| confidence = [99.8, 92.0, 94.0] | |
| boundary_color = self.color_palette["real_arf"] if is_real_arf else self.color_palette["simulated"] | |
| boundary_text = "REAL ARF" if is_real_arf else "SIMULATED" | |
| fig = go.Figure(data=[ | |
| go.Bar(name='Accuracy (%)', x=agents, y=accuracy, | |
| marker_color=self.color_palette["primary"]), | |
| go.Bar(name='Speed (seconds)', x=agents, y=speed, | |
| marker_color=boundary_color), | |
| go.Bar(name='Confidence (%)', x=agents, y=confidence, | |
| marker_color=self.color_palette["info"]) | |
| ]) | |
| # Add boundary annotation | |
| fig.add_annotation( | |
| x=0.98, y=0.98, | |
| xref="paper", yref="paper", | |
| text=f"🤖 {boundary_text}", | |
| showarrow=False, | |
| font=dict(size=12, color=boundary_color), | |
| bgcolor="white", | |
| bordercolor=boundary_color, | |
| borderwidth=2, | |
| borderpad=4 | |
| ) | |
| fig.update_layout( | |
| title=f"🤖 Agent Performance Metrics<br>" | |
| f"<span style='font-size: 12px; color: #64748b'>{boundary_text} v3.3.7</span>", | |
| barmode='group', | |
| height=400, | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| legend=dict( | |
| orientation="h", | |
| yanchor="bottom", | |
| y=1.02, | |
| xanchor="right", | |
| x=1 | |
| ) | |
| ) | |
| return fig | |
| except Exception as e: | |
| logger.error(f"Agent performance chart creation failed: {e}") | |
| # Return HTML fallback | |
| return self.create_agent_performance_chart.__wrapped__(self, is_real_arf) |