| """ |
| 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: |
| 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", |
| "simulated": "#f59e0b", |
| "mock": "#64748b", |
| } |
| |
| 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: |
| |
| return self._create_html_dashboard(roi_multiplier, is_real_arf) |
| |
| try: |
| |
| fig = go.Figure() |
| |
| |
| 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 |
| } |
| } |
| )) |
| |
| |
| 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 |
| ) |
| |
| |
| 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 |
| |
| |
| time_points = np.arange(0, 100, 1) |
| |
| |
| 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: |
| |
| 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)' |
| )) |
| |
| |
| 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)' |
| )) |
| |
| |
| 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: |
| |
| 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)' |
| )) |
| |
| |
| 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" |
| ) |
| |
| |
| 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 |
| |
| |
| 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"] |
| } |
| } |
| )) |
| |
| |
| 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"]) |
| |
| |
| 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] |
| arf_times = [45, 30, 60, 720, 0] |
| |
| |
| 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] |
| arf_times = [0.75, 0.5, 1, 12, 0] |
| |
| |
| 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)): |
| |
| manual_width = (manual_time / 125) * 100 |
| 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"]) |
| |
| |
| def create_agent_performance_chart(self, is_real_arf: bool = True) -> Any: |
| """Create agent performance comparison chart""" |
| if not PLOTLY_AVAILABLE: |
| |
| 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> |
| """ |
| |
| |
| try: |
| agents = ["Detection", "Recall", "Decision"] |
| accuracy = [98.7, 92.0, 94.0] |
| speed = [45, 30, 60] |
| 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"]) |
| ]) |
| |
| |
| 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 self.create_agent_performance_chart.__wrapped__(self, is_real_arf) |