petter2025's picture
Update core/visualizations.py
ab30c61 verified
raw
history blame
41 kB
"""
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)