Update app.py
Browse files
app.py
CHANGED
|
@@ -299,10 +299,13 @@ import plotly.express as px
|
|
| 299 |
import pandas as pd
|
| 300 |
import numpy as np
|
| 301 |
|
|
|
|
|
|
|
|
|
|
| 302 |
def create_simple_telemetry_plot(scenario_name: str, is_real_arf: bool = True) -> go.Figure:
|
| 303 |
"""
|
| 304 |
MINIMAL FIX: Returns Plotly figure instead of HTML string
|
| 305 |
-
|
| 306 |
"""
|
| 307 |
try:
|
| 308 |
# Generate sample telemetry data
|
|
@@ -361,19 +364,17 @@ def create_simple_telemetry_plot(scenario_name: str, is_real_arf: bool = True) -
|
|
| 361 |
line=dict(color='#ef4444', width=3)
|
| 362 |
))
|
| 363 |
|
| 364 |
-
# Add threshold line
|
| 365 |
fig.add_hline(y=threshold, line_dash="dash",
|
| 366 |
-
line_color="#f59e0b"
|
| 367 |
-
annotation_text="Threshold",
|
| 368 |
-
annotation_position="top right")
|
| 369 |
|
| 370 |
-
# Update layout - FIXED: Using 'size' not 'weight' in font
|
| 371 |
fig.update_layout(
|
| 372 |
-
title=
|
| 373 |
-
text
|
| 374 |
-
font
|
| 375 |
-
x
|
| 376 |
-
|
| 377 |
xaxis_title="Time",
|
| 378 |
yaxis_title=y_label,
|
| 379 |
height=300,
|
|
@@ -395,9 +396,13 @@ def create_simple_telemetry_plot(scenario_name: str, is_real_arf: bool = True) -
|
|
| 395 |
)
|
| 396 |
return fig
|
| 397 |
|
|
|
|
|
|
|
|
|
|
| 398 |
def create_simple_impact_plot(scenario_name: str, is_real_arf: bool = True) -> go.Figure:
|
| 399 |
"""
|
| 400 |
MINIMAL FIX: Returns Plotly figure (gauge chart) instead of HTML string
|
|
|
|
| 401 |
"""
|
| 402 |
try:
|
| 403 |
# Impact values based on scenario
|
|
@@ -411,18 +416,16 @@ def create_simple_impact_plot(scenario_name: str, is_real_arf: bool = True) -> g
|
|
| 411 |
}
|
| 412 |
|
| 413 |
impact = impact_values.get(scenario_name, 5000)
|
| 414 |
-
savings = int(impact * 0.85)
|
| 415 |
|
| 416 |
-
# Create gauge chart
|
| 417 |
fig = go.Figure(go.Indicator(
|
| 418 |
-
mode
|
| 419 |
-
value
|
| 420 |
-
domain
|
| 421 |
-
title
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
'axis': {'range': [None, impact * 1.2], 'tickwidth': 1, 'tickcolor': "darkblue"},
|
| 426 |
'bar': {'color': "#ef4444"},
|
| 427 |
'bgcolor': "white",
|
| 428 |
'borderwidth': 2,
|
|
@@ -431,21 +434,15 @@ def create_simple_impact_plot(scenario_name: str, is_real_arf: bool = True) -> g
|
|
| 431 |
{'range': [0, impact * 0.3], 'color': '#10b981'},
|
| 432 |
{'range': [impact * 0.3, impact * 0.7], 'color': '#f59e0b'},
|
| 433 |
{'range': [impact * 0.7, impact], 'color': '#ef4444'}
|
| 434 |
-
]
|
| 435 |
-
'threshold': {
|
| 436 |
-
'line': {'color': "red", 'width': 4},
|
| 437 |
-
'thickness': 0.75,
|
| 438 |
-
'value': impact
|
| 439 |
-
}
|
| 440 |
}
|
| 441 |
))
|
| 442 |
|
| 443 |
-
# Update layout - FIXED:
|
| 444 |
fig.update_layout(
|
| 445 |
height=400,
|
| 446 |
margin=dict(l=20, r=20, t=60, b=20),
|
| 447 |
-
paper_bgcolor='white'
|
| 448 |
-
font=dict(color='#1e293b')
|
| 449 |
)
|
| 450 |
|
| 451 |
return fig
|
|
@@ -461,31 +458,35 @@ def create_simple_impact_plot(scenario_name: str, is_real_arf: bool = True) -> g
|
|
| 461 |
fig.update_layout(height=400)
|
| 462 |
return fig
|
| 463 |
|
|
|
|
|
|
|
|
|
|
| 464 |
def create_empty_plot(title: str, is_real_arf: bool = True) -> go.Figure:
|
| 465 |
"""
|
| 466 |
MINIMAL FIX: Returns Plotly figure (placeholder) instead of HTML string
|
|
|
|
| 467 |
"""
|
| 468 |
fig = go.Figure()
|
| 469 |
|
| 470 |
-
# Add text annotation
|
| 471 |
fig.add_annotation(
|
| 472 |
x=0.5, y=0.5,
|
| 473 |
text=title,
|
| 474 |
showarrow=False,
|
| 475 |
-
font=
|
| 476 |
xref="paper",
|
| 477 |
yref="paper"
|
| 478 |
)
|
| 479 |
|
| 480 |
fig.update_layout(
|
| 481 |
-
title=
|
| 482 |
-
text
|
| 483 |
-
font
|
| 484 |
-
|
| 485 |
height=300,
|
| 486 |
plot_bgcolor='white',
|
| 487 |
-
xaxis=
|
| 488 |
-
yaxis=
|
| 489 |
margin=dict(l=20, r=20, t=40, b=20)
|
| 490 |
)
|
| 491 |
|
|
@@ -1125,12 +1126,15 @@ def extract_roi_multiplier(roi_result: Dict) -> float:
|
|
| 1125 |
return 5.2
|
| 1126 |
|
| 1127 |
# ===========================================
|
| 1128 |
-
#
|
| 1129 |
# ===========================================
|
| 1130 |
def update_scenario_display(scenario_name: str) -> tuple:
|
| 1131 |
"""
|
| 1132 |
-
|
| 1133 |
-
|
|
|
|
|
|
|
|
|
|
| 1134 |
"""
|
| 1135 |
components = get_components()
|
| 1136 |
scenarios = components["INCIDENT_SCENARIOS"]
|
|
@@ -1212,22 +1216,24 @@ def update_scenario_display(scenario_name: str) -> tuple:
|
|
| 1212 |
"""
|
| 1213 |
|
| 1214 |
# Get visualizations as Plotly figures (FIXED)
|
| 1215 |
-
|
| 1216 |
-
|
| 1217 |
-
|
| 1218 |
-
# Create timeline visualization as Plotly figure (FIXED)
|
| 1219 |
-
timeline_viz = create_empty_plot(f"Timeline: {scenario_name}", settings.use_true_arf)
|
| 1220 |
|
| 1221 |
-
return scenario_card_html,
|
| 1222 |
|
| 1223 |
# ===========================================
|
| 1224 |
-
#
|
| 1225 |
# ===========================================
|
| 1226 |
@AsyncRunner.async_to_sync
|
| 1227 |
-
async def run_true_arf_analysis(scenario_name: str):
|
| 1228 |
"""
|
| 1229 |
-
|
| 1230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1231 |
"""
|
| 1232 |
|
| 1233 |
components = get_components()
|
|
@@ -1253,12 +1259,94 @@ async def run_true_arf_analysis(scenario_name: str):
|
|
| 1253 |
# Check if we have real ARF
|
| 1254 |
is_real_arf = installation["oss_installed"] or settings.use_true_arf
|
| 1255 |
|
| 1256 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1257 |
if is_real_arf and "real" in str(analysis_result).lower():
|
| 1258 |
-
|
| 1259 |
"status": "success",
|
| 1260 |
"scenario": scenario_name,
|
| 1261 |
-
"arf_version": "
|
| 1262 |
"analysis": {
|
| 1263 |
"detected": True,
|
| 1264 |
"confidence": 94,
|
|
@@ -1272,38 +1360,67 @@ async def run_true_arf_analysis(scenario_name: str):
|
|
| 1272 |
"recall": {"status": "active", "similar_incidents": 3},
|
| 1273 |
"decision": {"status": "active", "healing_intent_created": True}
|
| 1274 |
},
|
| 1275 |
-
"boundary_note": "OSS analysis complete → Ready for Enterprise execution"
|
| 1276 |
}
|
| 1277 |
else:
|
| 1278 |
-
|
| 1279 |
"status": "mock_analysis",
|
| 1280 |
"scenario": scenario_name,
|
| 1281 |
"arf_version": "mock",
|
| 1282 |
"analysis": {
|
| 1283 |
-
"detected":
|
| 1284 |
-
"confidence":
|
| 1285 |
-
"similar_incidents":
|
| 1286 |
-
"healing_intent_created":
|
| 1287 |
-
"recommended_action": "
|
| 1288 |
-
"estimated_recovery": "
|
| 1289 |
},
|
| 1290 |
"agents": {
|
| 1291 |
-
"detection": {"status": "
|
| 1292 |
-
"recall": {"status": "
|
| 1293 |
-
"decision": {"status": "
|
| 1294 |
},
|
| 1295 |
-
"boundary_note": "Mock analysis -
|
| 1296 |
}
|
| 1297 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1298 |
except Exception as e:
|
| 1299 |
logger.error(f"True ARF analysis failed: {e}")
|
| 1300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1301 |
"status": "error",
|
| 1302 |
"error": str(e),
|
| 1303 |
"scenario": scenario_name,
|
| 1304 |
"arf_version": "3.3.9",
|
| 1305 |
"recommendation": "Check ARF installation"
|
| 1306 |
}
|
|
|
|
|
|
|
| 1307 |
|
| 1308 |
# ===========================================
|
| 1309 |
# FIXED EXECUTION FUNCTION
|
|
@@ -1570,7 +1687,7 @@ def create_demo_interface():
|
|
| 1570 |
outputs=[scenario_card, telemetry_viz, impact_viz, timeline_viz]
|
| 1571 |
)
|
| 1572 |
|
| 1573 |
-
# Run OSS Analysis
|
| 1574 |
oss_btn.click(
|
| 1575 |
fn=run_true_arf_analysis,
|
| 1576 |
inputs=[scenario_dropdown],
|
|
|
|
| 299 |
import pandas as pd
|
| 300 |
import numpy as np
|
| 301 |
|
| 302 |
+
# ===========================================
|
| 303 |
+
# SURGICAL FIX 1: create_simple_telemetry_plot()
|
| 304 |
+
# ===========================================
|
| 305 |
def create_simple_telemetry_plot(scenario_name: str, is_real_arf: bool = True) -> go.Figure:
|
| 306 |
"""
|
| 307 |
MINIMAL FIX: Returns Plotly figure instead of HTML string
|
| 308 |
+
FIXED: Removed font weight properties, returns valid Plotly figure
|
| 309 |
"""
|
| 310 |
try:
|
| 311 |
# Generate sample telemetry data
|
|
|
|
| 364 |
line=dict(color='#ef4444', width=3)
|
| 365 |
))
|
| 366 |
|
| 367 |
+
# Add threshold line - FIXED: Simplified without problematic properties
|
| 368 |
fig.add_hline(y=threshold, line_dash="dash",
|
| 369 |
+
line_color="#f59e0b")
|
|
|
|
|
|
|
| 370 |
|
| 371 |
+
# Update layout - FIXED: Using only 'size' not 'weight' in font
|
| 372 |
fig.update_layout(
|
| 373 |
+
title={
|
| 374 |
+
'text': title,
|
| 375 |
+
'font': {'size': 18, 'color': '#1e293b'},
|
| 376 |
+
'x': 0.5
|
| 377 |
+
},
|
| 378 |
xaxis_title="Time",
|
| 379 |
yaxis_title=y_label,
|
| 380 |
height=300,
|
|
|
|
| 396 |
)
|
| 397 |
return fig
|
| 398 |
|
| 399 |
+
# ===========================================
|
| 400 |
+
# SURGICAL FIX 2: create_simple_impact_plot()
|
| 401 |
+
# ===========================================
|
| 402 |
def create_simple_impact_plot(scenario_name: str, is_real_arf: bool = True) -> go.Figure:
|
| 403 |
"""
|
| 404 |
MINIMAL FIX: Returns Plotly figure (gauge chart) instead of HTML string
|
| 405 |
+
FIXED: Removed problematic font properties, simplified gauge
|
| 406 |
"""
|
| 407 |
try:
|
| 408 |
# Impact values based on scenario
|
|
|
|
| 416 |
}
|
| 417 |
|
| 418 |
impact = impact_values.get(scenario_name, 5000)
|
|
|
|
| 419 |
|
| 420 |
+
# Create gauge chart - FIXED: Simplified without problematic properties
|
| 421 |
fig = go.Figure(go.Indicator(
|
| 422 |
+
mode="gauge+number",
|
| 423 |
+
value=impact,
|
| 424 |
+
domain={'x': [0, 1], 'y': [0, 1]},
|
| 425 |
+
title={'text': f"Revenue Impact: ${impact:,}/hour"},
|
| 426 |
+
number={'prefix': "$", 'suffix': "/hour"},
|
| 427 |
+
gauge={
|
| 428 |
+
'axis': {'range': [None, impact * 1.2]},
|
|
|
|
| 429 |
'bar': {'color': "#ef4444"},
|
| 430 |
'bgcolor': "white",
|
| 431 |
'borderwidth': 2,
|
|
|
|
| 434 |
{'range': [0, impact * 0.3], 'color': '#10b981'},
|
| 435 |
{'range': [impact * 0.3, impact * 0.7], 'color': '#f59e0b'},
|
| 436 |
{'range': [impact * 0.7, impact], 'color': '#ef4444'}
|
| 437 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
}
|
| 439 |
))
|
| 440 |
|
| 441 |
+
# Update layout - FIXED: Simplified layout
|
| 442 |
fig.update_layout(
|
| 443 |
height=400,
|
| 444 |
margin=dict(l=20, r=20, t=60, b=20),
|
| 445 |
+
paper_bgcolor='white'
|
|
|
|
| 446 |
)
|
| 447 |
|
| 448 |
return fig
|
|
|
|
| 458 |
fig.update_layout(height=400)
|
| 459 |
return fig
|
| 460 |
|
| 461 |
+
# ===========================================
|
| 462 |
+
# SURGICAL FIX 3: create_empty_plot()
|
| 463 |
+
# ===========================================
|
| 464 |
def create_empty_plot(title: str, is_real_arf: bool = True) -> go.Figure:
|
| 465 |
"""
|
| 466 |
MINIMAL FIX: Returns Plotly figure (placeholder) instead of HTML string
|
| 467 |
+
FIXED: Simplified font properties
|
| 468 |
"""
|
| 469 |
fig = go.Figure()
|
| 470 |
|
| 471 |
+
# Add text annotation - FIXED: Simplified font
|
| 472 |
fig.add_annotation(
|
| 473 |
x=0.5, y=0.5,
|
| 474 |
text=title,
|
| 475 |
showarrow=False,
|
| 476 |
+
font={'size': 16, 'color': "#64748b"},
|
| 477 |
xref="paper",
|
| 478 |
yref="paper"
|
| 479 |
)
|
| 480 |
|
| 481 |
fig.update_layout(
|
| 482 |
+
title={
|
| 483 |
+
'text': "Visualization Placeholder",
|
| 484 |
+
'font': {'size': 14, 'color': "#94a3b8"}
|
| 485 |
+
},
|
| 486 |
height=300,
|
| 487 |
plot_bgcolor='white',
|
| 488 |
+
xaxis={'visible': False},
|
| 489 |
+
yaxis={'visible': False},
|
| 490 |
margin=dict(l=20, r=20, t=40, b=20)
|
| 491 |
)
|
| 492 |
|
|
|
|
| 1126 |
return 5.2
|
| 1127 |
|
| 1128 |
# ===========================================
|
| 1129 |
+
# SURGICAL FIX 4: update_scenario_display()
|
| 1130 |
# ===========================================
|
| 1131 |
def update_scenario_display(scenario_name: str) -> tuple:
|
| 1132 |
"""
|
| 1133 |
+
FIXED: Returns exactly 4 values as expected by UI:
|
| 1134 |
+
1. scenario_card_html (HTML string)
|
| 1135 |
+
2. telemetry_fig (Plotly figure from create_simple_telemetry_plot())
|
| 1136 |
+
3. impact_fig (Plotly figure from create_simple_impact_plot())
|
| 1137 |
+
4. timeline_fig (Plotly figure from create_empty_plot())
|
| 1138 |
"""
|
| 1139 |
components = get_components()
|
| 1140 |
scenarios = components["INCIDENT_SCENARIOS"]
|
|
|
|
| 1216 |
"""
|
| 1217 |
|
| 1218 |
# Get visualizations as Plotly figures (FIXED)
|
| 1219 |
+
telemetry_fig = create_simple_telemetry_plot(scenario_name, settings.use_true_arf)
|
| 1220 |
+
impact_fig = create_simple_impact_plot(scenario_name, settings.use_true_arf)
|
| 1221 |
+
timeline_fig = create_empty_plot(f"Timeline: {scenario_name}", settings.use_true_arf)
|
|
|
|
|
|
|
| 1222 |
|
| 1223 |
+
return scenario_card_html, telemetry_fig, impact_fig, timeline_fig
|
| 1224 |
|
| 1225 |
# ===========================================
|
| 1226 |
+
# SURGICAL FIX 5: run_true_arf_analysis()
|
| 1227 |
# ===========================================
|
| 1228 |
@AsyncRunner.async_to_sync
|
| 1229 |
+
async def run_true_arf_analysis(scenario_name: str) -> tuple:
|
| 1230 |
"""
|
| 1231 |
+
FIXED: Returns exactly 5 values as expected by UI:
|
| 1232 |
+
1. detection_html (HTML string)
|
| 1233 |
+
2. recall_html (HTML string)
|
| 1234 |
+
3. decision_html (HTML string)
|
| 1235 |
+
4. oss_results_dict (Python dict for JSON display)
|
| 1236 |
+
5. incident_table_html (HTML string)
|
| 1237 |
"""
|
| 1238 |
|
| 1239 |
components = get_components()
|
|
|
|
| 1259 |
# Check if we have real ARF
|
| 1260 |
is_real_arf = installation["oss_installed"] or settings.use_true_arf
|
| 1261 |
|
| 1262 |
+
# Create HTML for active agents
|
| 1263 |
+
boundary_color = boundaries["oss"]["color"] if is_real_arf else "#f59e0b"
|
| 1264 |
+
boundary_text = boundaries["oss"]["label"] if is_real_arf else "Mock ARF"
|
| 1265 |
+
|
| 1266 |
+
# Detection Agent HTML
|
| 1267 |
+
detection_html = f"""
|
| 1268 |
+
<div style="border: 2px solid {boundary_color}; border-radius: 14px; padding: 18px;
|
| 1269 |
+
background: linear-gradient(135deg, {boundary_color}10 0%, #ffffff 100%);
|
| 1270 |
+
text-align: center; flex: 1; margin: 5px; min-height: 180px;">
|
| 1271 |
+
<div style="font-size: 32px; margin-bottom: 10px; color: {boundary_color};">🕵️♂️</div>
|
| 1272 |
+
<div style="width: 100%;">
|
| 1273 |
+
<h4 style="margin: 0 0 8px 0; font-size: 16px; color: #1e293b;">Detection Agent</h4>
|
| 1274 |
+
<p style="font-size: 13px; color: #64748b; margin-bottom: 12px; line-height: 1.4;">
|
| 1275 |
+
Anomaly detected with 94% confidence
|
| 1276 |
+
</p>
|
| 1277 |
+
<div style="display: flex; justify-content: space-around; margin-bottom: 12px;">
|
| 1278 |
+
<span style="font-size: 11px; padding: 3px 8px; background: {boundary_color}20;
|
| 1279 |
+
border-radius: 6px; color: {boundary_color}; font-weight: 500;">
|
| 1280 |
+
Status: Active
|
| 1281 |
+
</span>
|
| 1282 |
+
</div>
|
| 1283 |
+
<div style="display: inline-block; padding: 5px 14px; background: {boundary_color};
|
| 1284 |
+
border-radius: 20px; font-size: 12px; font-weight: bold; color: white;
|
| 1285 |
+
text-transform: uppercase; letter-spacing: 0.5px;">
|
| 1286 |
+
DETECTED
|
| 1287 |
+
</div>
|
| 1288 |
+
</div>
|
| 1289 |
+
</div>
|
| 1290 |
+
"""
|
| 1291 |
+
|
| 1292 |
+
# Recall Agent HTML
|
| 1293 |
+
recall_html = f"""
|
| 1294 |
+
<div style="border: 2px solid {boundary_color}; border-radius: 14px; padding: 18px;
|
| 1295 |
+
background: linear-gradient(135deg, {boundary_color}10 0%, #ffffff 100%);
|
| 1296 |
+
text-align: center; flex: 1; margin: 5px; min-height: 180px;">
|
| 1297 |
+
<div style="font-size: 32px; margin-bottom: 10px; color: {boundary_color};">🧠</div>
|
| 1298 |
+
<div style="width: 100%;">
|
| 1299 |
+
<h4 style="margin: 0 0 8px 0; font-size: 16px; color: #1e293b;">Recall Agent</h4>
|
| 1300 |
+
<p style="font-size: 13px; color: #64748b; margin-bottom: 12px; line-height: 1.4;">
|
| 1301 |
+
3 similar incidents found in RAG memory
|
| 1302 |
+
</p>
|
| 1303 |
+
<div style="display: flex; justify-content: space-around; margin-bottom: 12px;">
|
| 1304 |
+
<span style="font-size: 11px; padding: 3px 8px; background: {boundary_color}20;
|
| 1305 |
+
border-radius: 6px; color: {boundary_color}; font-weight: 500;">
|
| 1306 |
+
Status: Active
|
| 1307 |
+
</span>
|
| 1308 |
+
</div>
|
| 1309 |
+
<div style="display: inline-block; padding: 5px 14px; background: {boundary_color};
|
| 1310 |
+
border-radius: 20px; font-size: 12px; font-weight: bold; color: white;
|
| 1311 |
+
text-transform: uppercase; letter-spacing: 0.5px;">
|
| 1312 |
+
RECALLED
|
| 1313 |
+
</div>
|
| 1314 |
+
</div>
|
| 1315 |
+
</div>
|
| 1316 |
+
"""
|
| 1317 |
+
|
| 1318 |
+
# Decision Agent HTML
|
| 1319 |
+
decision_html = f"""
|
| 1320 |
+
<div style="border: 2px solid {boundary_color}; border-radius: 14px; padding: 18px;
|
| 1321 |
+
background: linear-gradient(135deg, {boundary_color}10 0%, #ffffff 100%);
|
| 1322 |
+
text-align: center; flex: 1; margin: 5px; min-height: 180px;">
|
| 1323 |
+
<div style="font-size: 32px; margin-bottom: 10px; color: {boundary_color};">🎯</div>
|
| 1324 |
+
<div style="width: 100%;">
|
| 1325 |
+
<h4 style="margin: 0 0 8px 0; font-size: 16px; color: #1e293b;">Decision Agent</h4>
|
| 1326 |
+
<p style="font-size: 13px; color: #64748b; margin-bottom: 12px; line-height: 1.4;">
|
| 1327 |
+
HealingIntent created: Scale Redis cluster
|
| 1328 |
+
</p>
|
| 1329 |
+
<div style="display: flex; justify-content: space-around; margin-bottom: 12px;">
|
| 1330 |
+
<span style="font-size: 11px; padding: 3px 8px; background: {boundary_color}20;
|
| 1331 |
+
border-radius: 6px; color: {boundary_color}; font-weight: 500;">
|
| 1332 |
+
Status: Active
|
| 1333 |
+
</span>
|
| 1334 |
+
</div>
|
| 1335 |
+
<div style="display: inline-block; padding: 5px 14px; background: {boundary_color};
|
| 1336 |
+
border-radius: 20px; font-size: 12px; font-weight: bold; color: white;
|
| 1337 |
+
text-transform: uppercase; letter-spacing: 0.5px;">
|
| 1338 |
+
DECIDED
|
| 1339 |
+
</div>
|
| 1340 |
+
</div>
|
| 1341 |
+
</div>
|
| 1342 |
+
"""
|
| 1343 |
+
|
| 1344 |
+
# OSS Results Dict for JSON display
|
| 1345 |
if is_real_arf and "real" in str(analysis_result).lower():
|
| 1346 |
+
oss_results_dict = {
|
| 1347 |
"status": "success",
|
| 1348 |
"scenario": scenario_name,
|
| 1349 |
+
"arf_version": boundaries["oss"]["version"],
|
| 1350 |
"analysis": {
|
| 1351 |
"detected": True,
|
| 1352 |
"confidence": 94,
|
|
|
|
| 1360 |
"recall": {"status": "active", "similar_incidents": 3},
|
| 1361 |
"decision": {"status": "active", "healing_intent_created": True}
|
| 1362 |
},
|
| 1363 |
+
"boundary_note": f"OSS analysis complete → Ready for Enterprise execution"
|
| 1364 |
}
|
| 1365 |
else:
|
| 1366 |
+
oss_results_dict = {
|
| 1367 |
"status": "mock_analysis",
|
| 1368 |
"scenario": scenario_name,
|
| 1369 |
"arf_version": "mock",
|
| 1370 |
"analysis": {
|
| 1371 |
+
"detected": True,
|
| 1372 |
+
"confidence": 94,
|
| 1373 |
+
"similar_incidents": 3,
|
| 1374 |
+
"healing_intent_created": True,
|
| 1375 |
+
"recommended_action": "Scale Redis cluster from 3 to 5 nodes",
|
| 1376 |
+
"estimated_recovery": "12 minutes"
|
| 1377 |
},
|
| 1378 |
"agents": {
|
| 1379 |
+
"detection": {"status": "active", "confidence": 94},
|
| 1380 |
+
"recall": {"status": "active", "similar_incidents": 3},
|
| 1381 |
+
"decision": {"status": "active", "healing_intent_created": True}
|
| 1382 |
},
|
| 1383 |
+
"boundary_note": f"Mock analysis - {boundary_text}"
|
| 1384 |
}
|
| 1385 |
|
| 1386 |
+
# Incident Table HTML
|
| 1387 |
+
incident_table_html = get_audit_manager().get_incident_table()
|
| 1388 |
+
|
| 1389 |
+
return detection_html, recall_html, decision_html, oss_results_dict, incident_table_html
|
| 1390 |
+
|
| 1391 |
except Exception as e:
|
| 1392 |
logger.error(f"True ARF analysis failed: {e}")
|
| 1393 |
+
|
| 1394 |
+
# Return error state with proper types
|
| 1395 |
+
error_html = f"""
|
| 1396 |
+
<div style="border: 2px solid #ef4444; border-radius: 14px; padding: 18px;
|
| 1397 |
+
background: linear-gradient(135deg, #fef2f2 0%, #ffffff 100%);
|
| 1398 |
+
text-align: center; flex: 1; margin: 5px; min-height: 180px;">
|
| 1399 |
+
<div style="font-size: 32px; margin-bottom: 10px; color: #ef4444;">❌</div>
|
| 1400 |
+
<div style="width: 100%;">
|
| 1401 |
+
<h4 style="margin: 0 0 8px 0; font-size: 16px; color: #1e293b;">Analysis Error</h4>
|
| 1402 |
+
<p style="font-size: 13px; color: #64748b; margin-bottom: 12px; line-height: 1.4;">
|
| 1403 |
+
Failed to analyze incident
|
| 1404 |
+
</p>
|
| 1405 |
+
<div style="display: flex; justify-content: space-around; margin-bottom: 12px;">
|
| 1406 |
+
<span style="font-size: 11px; padding: 3px 8px; background: #ef4444;
|
| 1407 |
+
border-radius: 6px; color: white; font-weight: 500;">
|
| 1408 |
+
Status: Error
|
| 1409 |
+
</span>
|
| 1410 |
+
</div>
|
| 1411 |
+
</div>
|
| 1412 |
+
</div>
|
| 1413 |
+
"""
|
| 1414 |
+
|
| 1415 |
+
error_dict = {
|
| 1416 |
"status": "error",
|
| 1417 |
"error": str(e),
|
| 1418 |
"scenario": scenario_name,
|
| 1419 |
"arf_version": "3.3.9",
|
| 1420 |
"recommendation": "Check ARF installation"
|
| 1421 |
}
|
| 1422 |
+
|
| 1423 |
+
return error_html, error_html, error_html, error_dict, "Error loading incident table"
|
| 1424 |
|
| 1425 |
# ===========================================
|
| 1426 |
# FIXED EXECUTION FUNCTION
|
|
|
|
| 1687 |
outputs=[scenario_card, telemetry_viz, impact_viz, timeline_viz]
|
| 1688 |
)
|
| 1689 |
|
| 1690 |
+
# Run OSS Analysis - FIXED: Now receives 5 values
|
| 1691 |
oss_btn.click(
|
| 1692 |
fn=run_true_arf_analysis,
|
| 1693 |
inputs=[scenario_dropdown],
|