petter2025 commited on
Commit
5b9eca0
·
verified ·
1 Parent(s): 8c057f3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +205 -34
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, thresholds: Dict[str, float] = None):
277
- if thresholds is None:
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) # 24 hours
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
- refresh_dash_btn = gr.Button("Refresh Dashboard")
623
- refresh_dash_btn.click(
 
 
 
 
 
 
 
 
 
 
 
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=1,
643
  maximum=1000,
644
  step=1,
645
  info="How many past incidents to consider for risk calculation (0 = unlimited)"
646
  )
647
- infra_btn = gr.Button("Evaluate Intent")
 
 
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
- # Sales-driven Enterprise / OSS tab
 
 
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;">📧 Email me</a>
720
  </div>
721
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
722
 
723
- # Wire events
724
- infra_btn.click(
725
- fn=handle_infra_with_governance,
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