ringkernel_procint/gui/panels/
kpi_panel.rs

1//! KPI dashboard panel.
2
3use crate::analytics::{HealthStatus, ProcessKPIs};
4use crate::gui::widgets::{conformance_gauge, kpi_card, sparkline};
5use crate::gui::{section_header, styled_panel, Theme};
6use eframe::egui::Ui;
7use std::collections::VecDeque;
8
9/// KPI dashboard panel.
10pub struct KpiPanel {
11    /// Throughput history for sparkline.
12    throughput_history: VecDeque<f32>,
13    /// Fitness history for sparkline.
14    fitness_history: VecDeque<f32>,
15    /// Max history points.
16    max_history: usize,
17}
18
19impl Default for KpiPanel {
20    fn default() -> Self {
21        Self {
22            throughput_history: VecDeque::with_capacity(50),
23            fitness_history: VecDeque::with_capacity(50),
24            max_history: 50,
25        }
26    }
27}
28
29impl KpiPanel {
30    /// Update history with new KPIs.
31    pub fn update(&mut self, kpis: &ProcessKPIs) {
32        self.throughput_history
33            .push_back(kpis.events_per_second as f32);
34        if self.throughput_history.len() > self.max_history {
35            self.throughput_history.pop_front();
36        }
37
38        self.fitness_history.push_back(kpis.avg_fitness);
39        if self.fitness_history.len() > self.max_history {
40            self.fitness_history.pop_front();
41        }
42    }
43
44    /// Render the panel.
45    pub fn render(&mut self, ui: &mut Ui, theme: &Theme, kpis: &ProcessKPIs) {
46        styled_panel(ui, theme, |ui| {
47            section_header(ui, theme, "KEY METRICS");
48
49            // Throughput
50            ui.horizontal(|ui| {
51                kpi_card(ui, theme, "Throughput", &kpis.format_throughput(), None);
52            });
53
54            // Sparkline for throughput
55            let history: Vec<f32> = self.throughput_history.iter().copied().collect();
56            if !history.is_empty() {
57                sparkline(ui, theme, &history, ui.available_width(), 30.0);
58            }
59
60            ui.add_space(12.0);
61
62            // Conformance gauge
63            ui.horizontal(|ui| {
64                conformance_gauge(ui, theme, kpis.avg_fitness, 80.0);
65                ui.vertical(|ui| {
66                    ui.label(
67                        egui::RichText::new("Fitness")
68                            .size(11.0)
69                            .color(theme.text_muted),
70                    );
71                    ui.label(
72                        egui::RichText::new(kpis.format_fitness())
73                            .size(16.0)
74                            .strong(),
75                    );
76
77                    let health = kpis.health_status();
78                    let health_color = match health {
79                        HealthStatus::Excellent => theme.success,
80                        HealthStatus::Good => theme.accent,
81                        HealthStatus::Warning => theme.warning,
82                        HealthStatus::Critical => theme.error,
83                    };
84                    ui.label(
85                        egui::RichText::new(health.name())
86                            .size(11.0)
87                            .color(health_color),
88                    );
89                });
90            });
91
92            ui.add_space(12.0);
93
94            // Duration
95            kpi_card(ui, theme, "Avg Duration", &kpis.format_duration(), None);
96
97            ui.add_space(8.0);
98
99            // Cases and patterns
100            ui.horizontal(|ui| {
101                ui.vertical(|ui| {
102                    ui.label(
103                        egui::RichText::new("Cases")
104                            .size(11.0)
105                            .color(theme.text_muted),
106                    );
107                    ui.label(
108                        egui::RichText::new(format!("{}", kpis.cases_completed))
109                            .size(14.0)
110                            .strong(),
111                    );
112                });
113                ui.add_space(20.0);
114                ui.vertical(|ui| {
115                    ui.label(
116                        egui::RichText::new("Patterns")
117                            .size(11.0)
118                            .color(theme.text_muted),
119                    );
120                    ui.label(
121                        egui::RichText::new(format!("{}", kpis.pattern_count))
122                            .size(14.0)
123                            .strong(),
124                    );
125                });
126            });
127        });
128    }
129}