Update app.py
Browse files
app.py
CHANGED
|
@@ -13,6 +13,8 @@ import contextlib
|
|
| 13 |
import signal
|
| 14 |
import sys
|
| 15 |
import functools
|
|
|
|
|
|
|
| 16 |
from collections import deque
|
| 17 |
from scipy.stats import beta
|
| 18 |
import plotly.graph_objects as go
|
|
@@ -270,13 +272,11 @@ class BayesianRiskEngine:
|
|
| 270 |
return lo, hi
|
| 271 |
|
| 272 |
# ----------------------------------------------------------------------
|
| 273 |
-
# Policy Engine
|
| 274 |
# ----------------------------------------------------------------------
|
| 275 |
class PolicyEngine:
|
| 276 |
-
def __init__(self
|
| 277 |
-
|
| 278 |
-
thresholds = {"low": LOW_THRESHOLD, "high": HIGH_THRESHOLD}
|
| 279 |
-
self.thresholds = thresholds
|
| 280 |
|
| 281 |
def evaluate(self, risk):
|
| 282 |
if risk < self.thresholds["low"]:
|
|
@@ -507,6 +507,22 @@ def generate_action_timeline():
|
|
| 507 |
fig.update_layout(title="Autonomous Actions Timeline", xaxis_title="Time", yaxis_title="Approved (1) / Blocked (0)")
|
| 508 |
return fig
|
| 509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
def refresh_dashboard():
|
| 511 |
with history_lock:
|
| 512 |
total = len(decision_history)
|
|
@@ -523,9 +539,86 @@ def refresh_dashboard():
|
|
| 523 |
control_stats,
|
| 524 |
generate_risk_gauge(),
|
| 525 |
generate_decision_pie(),
|
| 526 |
-
generate_action_timeline()
|
|
|
|
| 527 |
)
|
| 528 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
# ----------------------------------------------------------------------
|
| 530 |
# OSS capabilities (mocked)
|
| 531 |
# ----------------------------------------------------------------------
|
|
@@ -551,7 +644,6 @@ oss_caps = {
|
|
| 551 |
def shutdown_handler(signum, frame):
|
| 552 |
logger.info("Received shutdown signal, cleaning up...")
|
| 553 |
shutdown_event.set()
|
| 554 |
-
# Wait a moment for threads to finish
|
| 555 |
time.sleep(2)
|
| 556 |
logger.info("Shutdown complete")
|
| 557 |
sys.exit(0)
|
|
@@ -562,18 +654,15 @@ signal.signal(signal.SIGINT, shutdown_handler)
|
|
| 562 |
# ----------------------------------------------------------------------
|
| 563 |
# Startup
|
| 564 |
# ----------------------------------------------------------------------
|
| 565 |
-
# Ensure data directory exists and DB has secure permissions
|
| 566 |
init_db()
|
| 567 |
refresh_history_from_db()
|
| 568 |
|
| 569 |
-
# Start memory monitor daemon thread
|
| 570 |
mem_thread = threading.Thread(target=memory_monitor_loop, daemon=True)
|
| 571 |
mem_thread.start()
|
| 572 |
|
| 573 |
-
# Start periodic vacuum (once a day)
|
| 574 |
def vacuum_scheduler():
|
| 575 |
while not shutdown_event.is_set():
|
| 576 |
-
time.sleep(86400)
|
| 577 |
if not shutdown_event.is_set():
|
| 578 |
vacuum_db()
|
| 579 |
vacuum_thread = threading.Thread(target=vacuum_scheduler, daemon=True)
|
|
@@ -582,7 +671,7 @@ vacuum_thread.start()
|
|
| 582 |
# ----------------------------------------------------------------------
|
| 583 |
# Gradio UI
|
| 584 |
# ----------------------------------------------------------------------
|
| 585 |
-
with gr.Blocks(title=f"ARF v{VERSION} – Bayesian Risk Scoring Demo") as demo:
|
| 586 |
gr.Markdown(f"""
|
| 587 |
# 🧠 ARF v{VERSION} – Bayesian Risk Scoring for AI Reliability (Demo)
|
| 588 |
**Mathematically rigorous risk estimation using conjugate priors and MCMC**
|
|
@@ -594,6 +683,9 @@ with gr.Blocks(title=f"ARF v{VERSION} – Bayesian Risk Scoring Demo") as demo:
|
|
| 594 |
All components are implemented with only `numpy`, `scipy`, and standard libraries.
|
| 595 |
""")
|
| 596 |
|
|
|
|
|
|
|
|
|
|
| 597 |
with gr.Tabs():
|
| 598 |
with gr.TabItem("Control Plane Dashboard"):
|
| 599 |
gr.Markdown("### 🎮 Control Plane")
|
|
@@ -619,12 +711,28 @@ with gr.Blocks(title=f"ARF v{VERSION} – Bayesian Risk Scoring Demo") as demo:
|
|
| 619 |
decision_pie = gr.Plot(label="Policy Decisions")
|
| 620 |
with gr.Row():
|
| 621 |
action_timeline = gr.Plot(label="Autonomous Actions Timeline")
|
| 622 |
-
|
| 623 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 624 |
fn=refresh_dashboard,
|
| 625 |
-
outputs=[control_stats, risk_gauge, decision_pie, action_timeline]
|
| 626 |
)
|
|
|
|
|
|
|
| 627 |
|
|
|
|
|
|
|
|
|
|
| 628 |
with gr.TabItem("Infrastructure Reliability"):
|
| 629 |
gr.Markdown("### 🏗️ Infrastructure Intent Evaluation with Bayesian Risk")
|
| 630 |
infra_state = gr.State(value={})
|
|
@@ -633,21 +741,40 @@ with gr.Blocks(title=f"ARF v{VERSION} – Bayesian Risk Scoring Demo") as demo:
|
|
| 633 |
infra_fault = gr.Dropdown(
|
| 634 |
["none", "switch_down", "server_overload", "cascade"],
|
| 635 |
value="none",
|
| 636 |
-
label="Inject Fault"
|
|
|
|
| 637 |
)
|
| 638 |
-
# Use a Number component to allow user to set context window
|
| 639 |
context_window_input = gr.Number(
|
| 640 |
value=50,
|
| 641 |
label="Context Window (number of recent events)",
|
| 642 |
-
minimum=
|
| 643 |
maximum=1000,
|
| 644 |
step=1,
|
| 645 |
info="How many past incidents to consider for risk calculation (0 = unlimited)"
|
| 646 |
)
|
| 647 |
-
|
|
|
|
|
|
|
| 648 |
with gr.Column():
|
| 649 |
infra_output = gr.JSON(label="Analysis Result")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
|
|
|
|
|
|
|
|
|
|
| 651 |
with gr.TabItem("Deep Analysis (MCMC)"):
|
| 652 |
gr.Markdown("### Markov Chain Monte Carlo (Metropolis‑Hastings)")
|
| 653 |
with gr.Row():
|
|
@@ -660,17 +787,46 @@ with gr.Blocks(title=f"ARF v{VERSION} – Bayesian Risk Scoring Demo") as demo:
|
|
| 660 |
with gr.Row():
|
| 661 |
hmc_trace_plot = gr.Plot(label="Trace Plot")
|
| 662 |
hmc_pair_plot = gr.Plot(label="Posterior Histogram")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
|
|
|
|
|
|
|
|
|
|
| 664 |
with gr.TabItem("Policy Management"):
|
| 665 |
gr.Markdown("### 📋 Execution Policies")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 666 |
policies_json = [
|
| 667 |
{"name": "Low Risk Policy", "conditions": [f"risk < {LOW_THRESHOLD}"], "action": "approve", "priority": 1},
|
| 668 |
{"name": "Medium Risk Policy", "conditions": [f"{LOW_THRESHOLD} ≤ risk ≤ {HIGH_THRESHOLD}"], "action": "escalate", "priority": 2},
|
| 669 |
{"name": "High Risk Policy", "conditions": [f"risk > {HIGH_THRESHOLD}"], "action": "deny", "priority": 3}
|
| 670 |
]
|
| 671 |
-
gr.JSON(label="Active Policies", value=policies_json)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
|
| 673 |
-
#
|
|
|
|
|
|
|
| 674 |
with gr.TabItem("Enterprise / OSS"):
|
| 675 |
gr.Markdown(f"""
|
| 676 |
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 2rem; border-radius: 12px; margin-bottom: 2rem; text-align: center; color: white;">
|
|
@@ -716,22 +872,37 @@ with gr.Blocks(title=f"ARF v{VERSION} – Bayesian Risk Scoring Demo") as demo:
|
|
| 716 |
|
| 717 |
<div style="text-align: center; margin-top: 2rem;">
|
| 718 |
<a href="https://calendly.com/petter2025us/30min" target="_blank" style="background: #764ba2; color: white; padding: 12px 24px; text-decoration: none; border-radius: 8px; font-weight: bold; margin-right: 1rem;">📅 Book a Demo</a>
|
| 719 |
-
<a href="mailto:petter2025us@outlook.com" style="background: #667eea; color: white; padding: 12px 24px; text-decoration: none; border-radius: 8px; font-weight: bold;">📧
|
| 720 |
</div>
|
| 721 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 722 |
|
| 723 |
-
#
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
inputs=[infra_fault, context_window_input, infra_state],
|
| 727 |
-
outputs=[infra_output, infra_state]
|
| 728 |
-
)
|
| 729 |
-
|
| 730 |
-
hmc_run_btn.click(
|
| 731 |
-
fn=run_hmc_mcmc,
|
| 732 |
-
inputs=[hmc_samples, hmc_warmup],
|
| 733 |
-
outputs=[hmc_summary, hmc_trace_plot, hmc_pair_plot]
|
| 734 |
-
)
|
| 735 |
|
| 736 |
# ----------------------------------------------------------------------
|
| 737 |
# Launch
|
|
|
|
| 13 |
import signal
|
| 14 |
import sys
|
| 15 |
import functools
|
| 16 |
+
import csv
|
| 17 |
+
import io
|
| 18 |
from collections import deque
|
| 19 |
from scipy.stats import beta
|
| 20 |
import plotly.graph_objects as go
|
|
|
|
| 272 |
return lo, hi
|
| 273 |
|
| 274 |
# ----------------------------------------------------------------------
|
| 275 |
+
# Policy Engine (uses global thresholds)
|
| 276 |
# ----------------------------------------------------------------------
|
| 277 |
class PolicyEngine:
|
| 278 |
+
def __init__(self):
|
| 279 |
+
self.thresholds = {"low": LOW_THRESHOLD, "high": HIGH_THRESHOLD}
|
|
|
|
|
|
|
| 280 |
|
| 281 |
def evaluate(self, risk):
|
| 282 |
if risk < self.thresholds["low"]:
|
|
|
|
| 507 |
fig.update_layout(title="Autonomous Actions Timeline", xaxis_title="Time", yaxis_title="Approved (1) / Blocked (0)")
|
| 508 |
return fig
|
| 509 |
|
| 510 |
+
@dashboard_cache
|
| 511 |
+
def generate_risk_trend():
|
| 512 |
+
"""Line chart showing risk over time."""
|
| 513 |
+
with history_lock:
|
| 514 |
+
if not risk_history:
|
| 515 |
+
return go.Figure()
|
| 516 |
+
times = [ts for ts, _ in risk_history]
|
| 517 |
+
risks = [r for _, r in risk_history]
|
| 518 |
+
fig = go.Figure()
|
| 519 |
+
fig.add_trace(go.Scatter(x=times, y=risks, mode='lines+markers', name='Risk', line=dict(color='red', width=2)))
|
| 520 |
+
# Add horizontal lines for thresholds
|
| 521 |
+
fig.add_hline(y=LOW_THRESHOLD, line_dash="dash", line_color="green", annotation_text=f"Low ({LOW_THRESHOLD})")
|
| 522 |
+
fig.add_hline(y=HIGH_THRESHOLD, line_dash="dash", line_color="orange", annotation_text=f"High ({HIGH_THRESHOLD})")
|
| 523 |
+
fig.update_layout(title="Risk Trend", xaxis_title="Time", yaxis_title="Risk Score", yaxis_range=[0, 1])
|
| 524 |
+
return fig
|
| 525 |
+
|
| 526 |
def refresh_dashboard():
|
| 527 |
with history_lock:
|
| 528 |
total = len(decision_history)
|
|
|
|
| 539 |
control_stats,
|
| 540 |
generate_risk_gauge(),
|
| 541 |
generate_decision_pie(),
|
| 542 |
+
generate_action_timeline(),
|
| 543 |
+
generate_risk_trend()
|
| 544 |
)
|
| 545 |
|
| 546 |
+
# ----------------------------------------------------------------------
|
| 547 |
+
# Batch simulation
|
| 548 |
+
# ----------------------------------------------------------------------
|
| 549 |
+
def run_batch_simulation(context_window: int):
|
| 550 |
+
"""Run evaluation for all fault types and return a summary table and individual results."""
|
| 551 |
+
fault_types = ["none", "switch_down", "server_overload", "cascade"]
|
| 552 |
+
results = []
|
| 553 |
+
for fault in fault_types:
|
| 554 |
+
# We'll call handle_infra_with_governance directly
|
| 555 |
+
# Since it returns (output, state) and we ignore state
|
| 556 |
+
output, _ = handle_infra_with_governance(fault, context_window, {})
|
| 557 |
+
if "error" in output:
|
| 558 |
+
results.append({
|
| 559 |
+
"Fault Type": fault,
|
| 560 |
+
"Risk": "Error",
|
| 561 |
+
"Decision": output["error"],
|
| 562 |
+
"Risk Level": "N/A",
|
| 563 |
+
"Confidence Interval": "N/A"
|
| 564 |
+
})
|
| 565 |
+
else:
|
| 566 |
+
results.append({
|
| 567 |
+
"Fault Type": fault,
|
| 568 |
+
"Risk": f"{output['risk']:.4f}",
|
| 569 |
+
"Decision": output["decision"],
|
| 570 |
+
"Risk Level": output["governance"]["control_plane_decision"]["risk_level"],
|
| 571 |
+
"Confidence Interval": f"[{output['risk_ci'][0]:.3f}, {output['risk_ci'][1]:.3f}]"
|
| 572 |
+
})
|
| 573 |
+
# Convert to DataFrame-like display
|
| 574 |
+
summary_table = results
|
| 575 |
+
return summary_table
|
| 576 |
+
|
| 577 |
+
# ----------------------------------------------------------------------
|
| 578 |
+
# Data export
|
| 579 |
+
# ----------------------------------------------------------------------
|
| 580 |
+
def export_history_to_csv():
|
| 581 |
+
"""Generate CSV of all decisions from database."""
|
| 582 |
+
try:
|
| 583 |
+
with contextlib.closing(sqlite3.connect(DB_PATH)) as conn:
|
| 584 |
+
cursor = conn.cursor()
|
| 585 |
+
cursor.execute("SELECT timestamp, decision_json, risk FROM decisions ORDER BY timestamp")
|
| 586 |
+
rows = cursor.fetchall()
|
| 587 |
+
if not rows:
|
| 588 |
+
return "No data to export", None
|
| 589 |
+
output = io.StringIO()
|
| 590 |
+
writer = csv.writer(output)
|
| 591 |
+
writer.writerow(["Timestamp", "Decision", "Risk", "Approved", "Risk Level", "Reason"])
|
| 592 |
+
for ts, json_str, risk in rows:
|
| 593 |
+
dec = json.loads(json_str)
|
| 594 |
+
writer.writerow([
|
| 595 |
+
ts,
|
| 596 |
+
json_str,
|
| 597 |
+
risk,
|
| 598 |
+
dec.get("approved", False),
|
| 599 |
+
dec.get("risk_level", ""),
|
| 600 |
+
dec.get("reason", "")
|
| 601 |
+
])
|
| 602 |
+
output.seek(0)
|
| 603 |
+
return output.getvalue()
|
| 604 |
+
except Exception as e:
|
| 605 |
+
logger.error(f"Export failed: {e}")
|
| 606 |
+
return f"Export failed: {str(e)}", None
|
| 607 |
+
|
| 608 |
+
# ----------------------------------------------------------------------
|
| 609 |
+
# Update thresholds (global)
|
| 610 |
+
# ----------------------------------------------------------------------
|
| 611 |
+
def update_thresholds(low: float, high: float):
|
| 612 |
+
global LOW_THRESHOLD, HIGH_THRESHOLD
|
| 613 |
+
if 0 <= low < high <= 1:
|
| 614 |
+
LOW_THRESHOLD = low
|
| 615 |
+
HIGH_THRESHOLD = high
|
| 616 |
+
# Also update PolicyEngine thresholds (but PolicyEngine reads from globals, so fine)
|
| 617 |
+
logger.info(f"Updated thresholds: low={low}, high={high}")
|
| 618 |
+
return f"Thresholds updated: approve < {low}, escalate {low}-{high}, deny > {high}"
|
| 619 |
+
else:
|
| 620 |
+
return f"Invalid thresholds: low={low}, high={high}. Must satisfy 0 ≤ low < high ≤ 1."
|
| 621 |
+
|
| 622 |
# ----------------------------------------------------------------------
|
| 623 |
# OSS capabilities (mocked)
|
| 624 |
# ----------------------------------------------------------------------
|
|
|
|
| 644 |
def shutdown_handler(signum, frame):
|
| 645 |
logger.info("Received shutdown signal, cleaning up...")
|
| 646 |
shutdown_event.set()
|
|
|
|
| 647 |
time.sleep(2)
|
| 648 |
logger.info("Shutdown complete")
|
| 649 |
sys.exit(0)
|
|
|
|
| 654 |
# ----------------------------------------------------------------------
|
| 655 |
# Startup
|
| 656 |
# ----------------------------------------------------------------------
|
|
|
|
| 657 |
init_db()
|
| 658 |
refresh_history_from_db()
|
| 659 |
|
|
|
|
| 660 |
mem_thread = threading.Thread(target=memory_monitor_loop, daemon=True)
|
| 661 |
mem_thread.start()
|
| 662 |
|
|
|
|
| 663 |
def vacuum_scheduler():
|
| 664 |
while not shutdown_event.is_set():
|
| 665 |
+
time.sleep(86400)
|
| 666 |
if not shutdown_event.is_set():
|
| 667 |
vacuum_db()
|
| 668 |
vacuum_thread = threading.Thread(target=vacuum_scheduler, daemon=True)
|
|
|
|
| 671 |
# ----------------------------------------------------------------------
|
| 672 |
# Gradio UI
|
| 673 |
# ----------------------------------------------------------------------
|
| 674 |
+
with gr.Blocks(title=f"ARF v{VERSION} – Bayesian Risk Scoring Demo", theme=gr.themes.Soft()) as demo:
|
| 675 |
gr.Markdown(f"""
|
| 676 |
# 🧠 ARF v{VERSION} – Bayesian Risk Scoring for AI Reliability (Demo)
|
| 677 |
**Mathematically rigorous risk estimation using conjugate priors and MCMC**
|
|
|
|
| 683 |
All components are implemented with only `numpy`, `scipy`, and standard libraries.
|
| 684 |
""")
|
| 685 |
|
| 686 |
+
# ------------------------------------------------------------------
|
| 687 |
+
# Control Plane Dashboard with auto-refresh
|
| 688 |
+
# ------------------------------------------------------------------
|
| 689 |
with gr.Tabs():
|
| 690 |
with gr.TabItem("Control Plane Dashboard"):
|
| 691 |
gr.Markdown("### 🎮 Control Plane")
|
|
|
|
| 711 |
decision_pie = gr.Plot(label="Policy Decisions")
|
| 712 |
with gr.Row():
|
| 713 |
action_timeline = gr.Plot(label="Autonomous Actions Timeline")
|
| 714 |
+
risk_trend = gr.Plot(label="Risk Trend")
|
| 715 |
+
with gr.Row():
|
| 716 |
+
auto_refresh = gr.Checkbox(label="Auto-refresh (3s)", value=False)
|
| 717 |
+
refresh_btn = gr.Button("Refresh Now")
|
| 718 |
+
# Auto-refresh timer
|
| 719 |
+
timer = gr.Timer(value=3, active=False) # will be toggled
|
| 720 |
+
def refresh_if_enabled(auto):
|
| 721 |
+
if auto:
|
| 722 |
+
return refresh_dashboard()
|
| 723 |
+
else:
|
| 724 |
+
return [gr.update() for _ in range(5)] # no update
|
| 725 |
+
timer.tick(refresh_if_enabled, inputs=[auto_refresh], outputs=[control_stats, risk_gauge, decision_pie, action_timeline, risk_trend])
|
| 726 |
+
refresh_btn.click(
|
| 727 |
fn=refresh_dashboard,
|
| 728 |
+
outputs=[control_stats, risk_gauge, decision_pie, action_timeline, risk_trend]
|
| 729 |
)
|
| 730 |
+
# Start/stop timer based on checkbox
|
| 731 |
+
auto_refresh.change(lambda v: gr.Timer(active=v), inputs=[auto_refresh], outputs=[timer])
|
| 732 |
|
| 733 |
+
# ------------------------------------------------------------------
|
| 734 |
+
# Infrastructure Reliability (with batch simulation)
|
| 735 |
+
# ------------------------------------------------------------------
|
| 736 |
with gr.TabItem("Infrastructure Reliability"):
|
| 737 |
gr.Markdown("### 🏗️ Infrastructure Intent Evaluation with Bayesian Risk")
|
| 738 |
infra_state = gr.State(value={})
|
|
|
|
| 741 |
infra_fault = gr.Dropdown(
|
| 742 |
["none", "switch_down", "server_overload", "cascade"],
|
| 743 |
value="none",
|
| 744 |
+
label="Inject Fault",
|
| 745 |
+
info="Select a fault type to simulate infrastructure issues."
|
| 746 |
)
|
|
|
|
| 747 |
context_window_input = gr.Number(
|
| 748 |
value=50,
|
| 749 |
label="Context Window (number of recent events)",
|
| 750 |
+
minimum=0,
|
| 751 |
maximum=1000,
|
| 752 |
step=1,
|
| 753 |
info="How many past incidents to consider for risk calculation (0 = unlimited)"
|
| 754 |
)
|
| 755 |
+
with gr.Row():
|
| 756 |
+
infra_btn = gr.Button("Evaluate Intent")
|
| 757 |
+
batch_btn = gr.Button("Run Batch Simulation", variant="secondary")
|
| 758 |
with gr.Column():
|
| 759 |
infra_output = gr.JSON(label="Analysis Result")
|
| 760 |
+
batch_results = gr.Dataframe(
|
| 761 |
+
headers=["Fault Type", "Risk", "Decision", "Risk Level", "Confidence Interval"],
|
| 762 |
+
label="Batch Simulation Results"
|
| 763 |
+
)
|
| 764 |
+
infra_btn.click(
|
| 765 |
+
fn=handle_infra_with_governance,
|
| 766 |
+
inputs=[infra_fault, context_window_input, infra_state],
|
| 767 |
+
outputs=[infra_output, infra_state]
|
| 768 |
+
)
|
| 769 |
+
batch_btn.click(
|
| 770 |
+
fn=run_batch_simulation,
|
| 771 |
+
inputs=[context_window_input],
|
| 772 |
+
outputs=[batch_results]
|
| 773 |
+
)
|
| 774 |
|
| 775 |
+
# ------------------------------------------------------------------
|
| 776 |
+
# Deep Analysis (MCMC)
|
| 777 |
+
# ------------------------------------------------------------------
|
| 778 |
with gr.TabItem("Deep Analysis (MCMC)"):
|
| 779 |
gr.Markdown("### Markov Chain Monte Carlo (Metropolis‑Hastings)")
|
| 780 |
with gr.Row():
|
|
|
|
| 787 |
with gr.Row():
|
| 788 |
hmc_trace_plot = gr.Plot(label="Trace Plot")
|
| 789 |
hmc_pair_plot = gr.Plot(label="Posterior Histogram")
|
| 790 |
+
hmc_run_btn.click(
|
| 791 |
+
fn=run_hmc_mcmc,
|
| 792 |
+
inputs=[hmc_samples, hmc_warmup],
|
| 793 |
+
outputs=[hmc_summary, hmc_trace_plot, hmc_pair_plot]
|
| 794 |
+
)
|
| 795 |
|
| 796 |
+
# ------------------------------------------------------------------
|
| 797 |
+
# Policy Management (with interactive sliders)
|
| 798 |
+
# ------------------------------------------------------------------
|
| 799 |
with gr.TabItem("Policy Management"):
|
| 800 |
gr.Markdown("### 📋 Execution Policies")
|
| 801 |
+
with gr.Row():
|
| 802 |
+
low_slider = gr.Slider(0, 1, value=LOW_THRESHOLD, step=0.01, label="Low Threshold (Approve <)")
|
| 803 |
+
high_slider = gr.Slider(0, 1, value=HIGH_THRESHOLD, step=0.01, label="High Threshold (Deny >)")
|
| 804 |
+
update_thresh_btn = gr.Button("Update Thresholds")
|
| 805 |
+
thresh_status = gr.Markdown(f"Current: approve < {LOW_THRESHOLD}, escalate {LOW_THRESHOLD}-{HIGH_THRESHOLD}, deny > {HIGH_THRESHOLD}")
|
| 806 |
policies_json = [
|
| 807 |
{"name": "Low Risk Policy", "conditions": [f"risk < {LOW_THRESHOLD}"], "action": "approve", "priority": 1},
|
| 808 |
{"name": "Medium Risk Policy", "conditions": [f"{LOW_THRESHOLD} ≤ risk ≤ {HIGH_THRESHOLD}"], "action": "escalate", "priority": 2},
|
| 809 |
{"name": "High Risk Policy", "conditions": [f"risk > {HIGH_THRESHOLD}"], "action": "deny", "priority": 3}
|
| 810 |
]
|
| 811 |
+
policy_display = gr.JSON(label="Active Policies", value=policies_json)
|
| 812 |
+
update_thresh_btn.click(
|
| 813 |
+
fn=update_thresholds,
|
| 814 |
+
inputs=[low_slider, high_slider],
|
| 815 |
+
outputs=[thresh_status]
|
| 816 |
+
).then(
|
| 817 |
+
fn=lambda: [
|
| 818 |
+
[
|
| 819 |
+
{"name": "Low Risk Policy", "conditions": [f"risk < {LOW_THRESHOLD}"], "action": "approve", "priority": 1},
|
| 820 |
+
{"name": "Medium Risk Policy", "conditions": [f"{LOW_THRESHOLD} ≤ risk ≤ {HIGH_THRESHOLD}"], "action": "escalate", "priority": 2},
|
| 821 |
+
{"name": "High Risk Policy", "conditions": [f"risk > {HIGH_THRESHOLD}"], "action": "deny", "priority": 3}
|
| 822 |
+
]
|
| 823 |
+
],
|
| 824 |
+
outputs=[policy_display]
|
| 825 |
+
)
|
| 826 |
|
| 827 |
+
# ------------------------------------------------------------------
|
| 828 |
+
# Enterprise / OSS (with data export)
|
| 829 |
+
# ------------------------------------------------------------------
|
| 830 |
with gr.TabItem("Enterprise / OSS"):
|
| 831 |
gr.Markdown(f"""
|
| 832 |
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 2rem; border-radius: 12px; margin-bottom: 2rem; text-align: center; color: white;">
|
|
|
|
| 872 |
|
| 873 |
<div style="text-align: center; margin-top: 2rem;">
|
| 874 |
<a href="https://calendly.com/petter2025us/30min" target="_blank" style="background: #764ba2; color: white; padding: 12px 24px; text-decoration: none; border-radius: 8px; font-weight: bold; margin-right: 1rem;">📅 Book a Demo</a>
|
| 875 |
+
<a href="mailto:petter2025us@outlook.com" style="background: #667eea; color: white; padding: 12px 24px; text-decoration: none; border-radius: 8px; font-weight: bold;">📧 Contact Sales</a>
|
| 876 |
</div>
|
| 877 |
""")
|
| 878 |
+
# Data export section
|
| 879 |
+
gr.Markdown("### 📥 Export Decision History")
|
| 880 |
+
with gr.Row():
|
| 881 |
+
export_btn = gr.DownloadButton("Download CSV", variant="primary")
|
| 882 |
+
export_btn.click(
|
| 883 |
+
fn=export_history_to_csv,
|
| 884 |
+
outputs=[gr.File(label="decision_history.csv", visible=False)] # hidden, but we need a file component
|
| 885 |
+
)
|
| 886 |
+
# Note: gradio DownloadButton works with a function that returns a file path or bytes.
|
| 887 |
+
# We'll create a temporary file.
|
| 888 |
+
def export_and_return_file():
|
| 889 |
+
csv_data = export_history_to_csv()
|
| 890 |
+
if csv_data and not csv_data.startswith("Export failed"):
|
| 891 |
+
# Write to a temporary file
|
| 892 |
+
import tempfile
|
| 893 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f:
|
| 894 |
+
f.write(csv_data)
|
| 895 |
+
return f.name
|
| 896 |
+
else:
|
| 897 |
+
return None
|
| 898 |
+
export_btn.click(
|
| 899 |
+
fn=export_and_return_file,
|
| 900 |
+
outputs=[gr.File(label="decision_history.csv")]
|
| 901 |
+
)
|
| 902 |
|
| 903 |
+
# ------------------------------------------------------------------
|
| 904 |
+
# Wire events for infra and MCMC (already done above)
|
| 905 |
+
# ------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 906 |
|
| 907 |
# ----------------------------------------------------------------------
|
| 908 |
# Launch
|