Skip to main content

ringkernel_accnet/gui/
app.rs

1//! Main application window.
2
3use eframe::egui::{self, CentralPanel, RichText, SidePanel, TopBottomPanel};
4use uuid::Uuid;
5
6use super::canvas::NetworkCanvas;
7use super::dashboard::AnalyticsDashboard;
8use super::panels::{AlertsPanel, AnalyticsPanel, ControlPanel, EducationalOverlay};
9use super::theme::AccNetTheme;
10use crate::actors::{CoordinatorConfig, GpuActorRuntime};
11use crate::analytics::AnalyticsEngine;
12use crate::cuda::{AnalysisRuntime, Backend};
13use crate::fabric::{
14    Alert, AlertSeverity, CompanyArchetype, DataFabricPipeline, GeneratorConfig, PipelineConfig,
15};
16use crate::models::{AccountingNetwork, HybridTimestamp};
17
18/// Main application state.
19pub struct AccNetApp {
20    /// Network canvas.
21    canvas: NetworkCanvas,
22    /// Control panel.
23    controls: ControlPanel,
24    /// Analytics panel.
25    analytics: AnalyticsPanel,
26    /// Analytics dashboard with charts.
27    dashboard: AnalyticsDashboard,
28    /// Alerts panel.
29    alerts: AlertsPanel,
30    /// Educational overlay.
31    education: EducationalOverlay,
32    /// Visual theme.
33    theme: AccNetTheme,
34
35    /// Current accounting network.
36    network: AccountingNetwork,
37    /// Data fabric pipeline.
38    pipeline: Option<DataFabricPipeline>,
39    /// Analytics engine.
40    engine: AnalyticsEngine,
41    /// GPU-accelerated analysis runtime.
42    analysis_runtime: AnalysisRuntime,
43    /// GPU-native actor runtime for ring kernel analytics.
44    actor_runtime: GpuActorRuntime,
45
46    /// Frame counter.
47    frame_count: u64,
48    /// Batches processed.
49    batches_processed: u64,
50    /// Show debug info.
51    show_debug: bool,
52    /// Show dashboard.
53    show_dashboard: bool,
54    /// Last layout update time.
55    last_layout_update: std::time::Instant,
56}
57
58impl AccNetApp {
59    /// Create a new application.
60    pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
61        // Apply theme
62        let theme = AccNetTheme::dark();
63        theme.apply(&cc.egui_ctx);
64
65        // Initialize network
66        let entity_id = Uuid::new_v4();
67        let network = AccountingNetwork::new(entity_id, 2024, 1);
68
69        // Initialize GPU runtime with CUDA preference (auto-fallback to CPU)
70        let analysis_runtime = AnalysisRuntime::with_backend(Backend::Auto);
71        if analysis_runtime.is_cuda_active() {
72            let status = analysis_runtime.status();
73            eprintln!(
74                "AccNet: GPU acceleration active - {} (CC {}.{})",
75                status.cuda_device_name.as_deref().unwrap_or("Unknown"),
76                status.cuda_compute_capability.map(|c| c.0).unwrap_or(0),
77                status.cuda_compute_capability.map(|c| c.1).unwrap_or(0)
78            );
79        } else {
80            eprintln!("AccNet: CPU backend (CUDA not available)");
81        }
82
83        // Initialize GPU-native actor runtime for ring kernel analytics
84        let actor_runtime = GpuActorRuntime::new(CoordinatorConfig::default());
85        if actor_runtime.is_gpu_active() {
86            let status = actor_runtime.status();
87            eprintln!(
88                "AccNet: Ring Kernel Actor System active - {} kernels",
89                status.kernels_launched
90            );
91        } else {
92            eprintln!("AccNet: Ring Kernel Actor System (CPU fallback)");
93        }
94
95        Self {
96            canvas: NetworkCanvas::new(),
97            controls: ControlPanel::new(),
98            analytics: AnalyticsPanel::new(),
99            dashboard: AnalyticsDashboard::new(),
100            alerts: AlertsPanel::new(),
101            education: EducationalOverlay::new(),
102            theme,
103            network,
104            pipeline: None,
105            engine: AnalyticsEngine::new(),
106            analysis_runtime,
107            actor_runtime,
108            frame_count: 0,
109            batches_processed: 0,
110            show_debug: false,
111            show_dashboard: true,
112            last_layout_update: std::time::Instant::now(),
113        }
114    }
115
116    /// Initialize the pipeline with current settings.
117    fn init_pipeline(&mut self) {
118        let archetype = match self.controls.archetype_index {
119            0 => CompanyArchetype::retail_standard(),
120            1 => CompanyArchetype::saas_standard(),
121            2 => CompanyArchetype::manufacturing_standard(),
122            3 => CompanyArchetype::professional_services_standard(),
123            4 => CompanyArchetype::financial_services_standard(),
124            _ => CompanyArchetype::retail_standard(),
125        };
126
127        // Create pipeline configuration
128        let mut config = PipelineConfig::default();
129        config.batch_size = self.controls.config.batch_size;
130        config.inject_anomalies = self.controls.anomaly_rate > 0.0;
131        config.anomaly_config.injection_rate = self.controls.anomaly_rate as f64;
132
133        // Create generator config
134        let gen_config = GeneratorConfig {
135            seed: Some(42),
136            ..Default::default()
137        };
138
139        // Create new pipeline
140        self.pipeline = Some(DataFabricPipeline::new(archetype, gen_config, config));
141
142        // Get the network from pipeline
143        if let Some(ref pipeline) = self.pipeline {
144            self.network = pipeline.network().clone();
145        }
146
147        // Initialize canvas with network
148        self.canvas.initialize(&self.network);
149        self.batches_processed = 0;
150    }
151
152    /// Process pipeline events.
153    fn process_pipeline(&mut self) {
154        if !self.controls.running {
155            return;
156        }
157
158        if self.pipeline.is_none() {
159            self.init_pipeline();
160        }
161
162        let Some(pipeline) = &mut self.pipeline else {
163            return;
164        };
165
166        // Generate batches based on speed
167        let batches_to_process = (self.controls.speed).ceil() as usize;
168
169        for _ in 0..batches_to_process {
170            // Process one tick of the pipeline
171            let flows = pipeline.tick();
172
173            if !flows.is_empty() {
174                self.batches_processed += 1;
175            }
176        }
177
178        // Copy network state from pipeline
179        self.network = pipeline.network().clone();
180
181        // Run analysis using GPU-accelerated runtime (CUDA if available)
182        let analysis = self.analysis_runtime.analyze(&self.network);
183
184        // Also run ring kernel actor-based analytics
185        let actor_result = self.actor_runtime.analyze(&self.network);
186
187        // Use actor results for Benford analysis if available
188        if actor_result.benford_distribution.iter().sum::<u32>() > 0 {
189            // Update dashboard with actor-based Benford analysis
190            // (The actor runtime provides more accurate parallel analysis)
191        }
192
193        // Update network statistics
194        self.network.statistics.suspense_account_count = analysis.stats.suspense_count;
195        self.network.statistics.gaap_violation_count = analysis.stats.gaap_violation_count;
196        self.network.statistics.fraud_pattern_count = analysis.stats.fraud_pattern_count;
197
198        // Generate alerts for detected issues
199        for violation in &analysis.gaap_violations {
200            self.alerts.add_alert(Alert {
201                id: Uuid::new_v4(),
202                severity: match violation.severity {
203                    crate::models::ViolationSeverity::Low => AlertSeverity::Low,
204                    crate::models::ViolationSeverity::Medium => AlertSeverity::Medium,
205                    crate::models::ViolationSeverity::High => AlertSeverity::High,
206                    crate::models::ViolationSeverity::Critical => AlertSeverity::Critical,
207                },
208                alert_type: format!("GAAP: {:?}", violation.violation_type),
209                message: violation.violation_type.description().to_string(),
210                accounts: vec![violation.source_account, violation.target_account],
211                amount: Some(violation.amount),
212                timestamp: HybridTimestamp::now(),
213            });
214        }
215
216        for pattern in &analysis.fraud_patterns {
217            let severity = if pattern.risk_score > 0.8 {
218                AlertSeverity::Critical
219            } else if pattern.risk_score > 0.5 {
220                AlertSeverity::High
221            } else {
222                AlertSeverity::Medium
223            };
224
225            self.alerts.add_alert(Alert {
226                id: Uuid::new_v4(),
227                severity,
228                alert_type: format!("Fraud: {:?}", pattern.pattern_type),
229                message: pattern.pattern_type.description().to_string(),
230                accounts: pattern
231                    .involved_accounts
232                    .iter()
233                    .copied()
234                    .filter(|&x| x != 0)
235                    .collect(),
236                amount: Some(pattern.amount),
237                timestamp: HybridTimestamp::now(),
238            });
239        }
240
241        // Update analytics snapshot
242        let snapshot = self.engine.analyze(&self.network);
243        self.analytics.update(snapshot);
244
245        // Only update particles periodically, don't reinitialize layout
246        // This preserves node positions while refreshing flow animations
247        if self.batches_processed.is_multiple_of(100) && self.batches_processed > 0 {
248            self.canvas.refresh_particles(&self.network);
249        }
250    }
251
252    /// Render the top menu bar.
253    fn menu_bar(&mut self, ui: &mut egui::Ui) {
254        egui::menu::bar(ui, |ui| {
255            ui.menu_button("File", |ui| {
256                if ui.button("Reset").clicked() {
257                    self.controls.running = false;
258                    self.pipeline = None;
259                    self.network = AccountingNetwork::new(Uuid::new_v4(), 2024, 1);
260                    self.canvas.initialize(&self.network);
261                    self.alerts.clear();
262                    ui.close_menu();
263                }
264                ui.separator();
265                if ui.button("Exit").clicked() {
266                    std::process::exit(0);
267                }
268            });
269
270            ui.menu_button("View", |ui| {
271                ui.checkbox(&mut self.canvas.show_labels, "Show Labels");
272                ui.checkbox(&mut self.canvas.show_risk, "Show Risk Indicators");
273                ui.checkbox(&mut self.canvas.animate_layout, "Animate Layout");
274                ui.separator();
275                ui.checkbox(&mut self.show_dashboard, "Show Dashboard");
276                ui.separator();
277                if ui.button("Reset View").clicked() {
278                    self.canvas.reset_view();
279                    ui.close_menu();
280                }
281            });
282
283            ui.menu_button("Theme", |ui| {
284                if ui.button("Dark").clicked() {
285                    self.theme = AccNetTheme::dark();
286                    self.theme.apply(ui.ctx());
287                    self.canvas.theme = self.theme.clone();
288                    ui.close_menu();
289                }
290                if ui.button("Light").clicked() {
291                    self.theme = AccNetTheme::light();
292                    self.theme.apply(ui.ctx());
293                    self.canvas.theme = self.theme.clone();
294                    ui.close_menu();
295                }
296            });
297
298            ui.menu_button("Help", |ui| {
299                ui.checkbox(&mut self.education.visible, "Show Tutorial");
300                ui.separator();
301                if ui.button("About").clicked() {
302                    // Show about dialog
303                    ui.close_menu();
304                }
305            });
306
307            // Spacer
308            ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
309                ui.checkbox(&mut self.show_debug, "Debug");
310
311                // Backend indicator - shows actual GPU execution status
312                let (backend_text, backend_color) = if self.analysis_runtime.is_cuda_active() {
313                    let status = self.analysis_runtime.status();
314                    let device = status.cuda_device_name.as_deref().unwrap_or("GPU");
315                    (
316                        format!("GPU: {}", device),
317                        egui::Color32::from_rgb(100, 220, 100),
318                    )
319                } else {
320                    match self.analysis_runtime.backend() {
321                        Backend::Cuda => (
322                            "CUDA (fallback CPU)".to_string(),
323                            egui::Color32::from_rgb(220, 180, 100),
324                        ),
325                        Backend::Cpu => ("CPU".to_string(), egui::Color32::from_rgb(180, 180, 180)),
326                        Backend::Auto => {
327                            ("AUTO".to_string(), egui::Color32::from_rgb(180, 180, 180))
328                        }
329                    }
330                };
331                ui.label(RichText::new(backend_text).color(backend_color).small());
332                ui.separator();
333
334                ui.label(
335                    RichText::new(format!(
336                        "Batches: {} | Flows: {} | FPS: {:.1}",
337                        self.batches_processed,
338                        self.network.flows.len(),
339                        ui.ctx().input(|i| 1.0 / i.predicted_dt)
340                    ))
341                    .small(),
342                );
343            });
344        });
345    }
346}
347
348impl eframe::App for AccNetApp {
349    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
350        self.frame_count += 1;
351
352        // Check if reset is needed (e.g., company type changed)
353        if self.controls.needs_reset {
354            self.controls.needs_reset = false;
355            self.controls.running = false;
356            self.pipeline = None;
357            self.network = AccountingNetwork::new(Uuid::new_v4(), 2024, 1);
358            self.canvas.initialize(&self.network);
359            self.dashboard.reset();
360            self.alerts.clear();
361            self.batches_processed = 0;
362            self.last_layout_update = std::time::Instant::now();
363        }
364
365        // Process pipeline
366        self.process_pipeline();
367
368        // Periodically update layout edges and warm up (every 5 seconds)
369        let now = std::time::Instant::now();
370        if now.duration_since(self.last_layout_update).as_secs() >= 5 && self.controls.running {
371            self.canvas.layout.update_edges(&self.network);
372            self.canvas.layout.warm_up();
373            self.last_layout_update = now;
374        }
375
376        // Update canvas animations
377        self.canvas.update();
378
379        // Top menu bar
380        TopBottomPanel::top("menu_bar").show(ctx, |ui| {
381            self.menu_bar(ui);
382        });
383
384        // Left panel - Controls
385        SidePanel::left("controls")
386            .default_width(200.0)
387            .resizable(true)
388            .show(ctx, |ui| {
389                egui::ScrollArea::vertical().show(ui, |ui| {
390                    self.controls.show(ui, &self.theme);
391                });
392            });
393
394        // Right panel - Analytics & Alerts
395        SidePanel::right("analytics")
396            .default_width(280.0)
397            .resizable(true)
398            .show(ctx, |ui| {
399                egui::ScrollArea::vertical().show(ui, |ui| {
400                    self.analytics.show(ui, &self.network, &self.theme);
401                    ui.add_space(15.0);
402                    self.alerts.show(ui, &self.theme);
403                    ui.add_space(15.0);
404                    ui.separator();
405                    ui.add_space(5.0);
406                    // Extended analytics with heatmaps, rankings, patterns
407                    self.dashboard.update(&self.network, self.frame_count);
408                    self.dashboard.show(ui, &self.network, &self.theme);
409                });
410            });
411
412        // Bottom panel - Dashboard with histograms and charts
413        if self.show_dashboard {
414            egui::TopBottomPanel::bottom("dashboard")
415                .default_height(180.0)
416                .resizable(true)
417                .show(ctx, |ui| {
418                    // Update dashboard
419                    self.dashboard.update(&self.network, self.frame_count);
420
421                    ui.horizontal(|ui| {
422                        ui.heading(RichText::new("Analytics Dashboard").size(14.0));
423                        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
424                            if ui.small_button("x").clicked() {
425                                self.show_dashboard = false;
426                            }
427                        });
428                    });
429                    ui.separator();
430
431                    egui::ScrollArea::horizontal().show(ui, |ui| {
432                        ui.horizontal(|ui| {
433                            // Benford's histogram
434                            ui.vertical(|ui| {
435                                ui.set_width(180.0);
436                                let hist = super::charts::Histogram::benford(
437                                    self.dashboard.benford_counts(),
438                                );
439                                hist.show(ui, &self.theme);
440                            });
441
442                            ui.separator();
443
444                            // Account distribution donut
445                            ui.vertical(|ui| {
446                                ui.set_width(200.0);
447                                let counts = self.dashboard.account_type_counts();
448                                let chart = super::charts::DonutChart::new("Account Types")
449                                    .add("Asset", counts[0] as f64, self.theme.asset_color)
450                                    .add("Liability", counts[1] as f64, self.theme.liability_color)
451                                    .add("Equity", counts[2] as f64, self.theme.equity_color)
452                                    .add("Revenue", counts[3] as f64, self.theme.revenue_color)
453                                    .add("Expense", counts[4] as f64, self.theme.expense_color);
454                                chart.show(ui, &self.theme);
455                            });
456
457                            ui.separator();
458
459                            // Method distribution with explanations
460                            ui.vertical(|ui| {
461                                ui.set_width(220.0);
462                                let dist = super::charts::MethodDistribution::new(
463                                    self.dashboard.method_counts(),
464                                )
465                                .with_explanation(true);
466                                dist.show(ui, &self.theme);
467                            });
468
469                            ui.separator();
470
471                            // Account balances bar chart
472                            ui.vertical(|ui| {
473                                ui.set_width(180.0);
474                                let balances = self.dashboard.account_type_balances();
475                                let chart = super::charts::BalanceBarChart::new("Account Balances")
476                                    .add("Asset", balances[0], self.theme.asset_color)
477                                    .add("Liability", balances[1], self.theme.liability_color)
478                                    .add("Equity", balances[2], self.theme.equity_color)
479                                    .add("Revenue", balances[3], self.theme.revenue_color)
480                                    .add("Expense", balances[4], self.theme.expense_color);
481                                chart.show(ui, &self.theme);
482                            });
483
484                            ui.separator();
485
486                            // Issues bar chart
487                            ui.vertical(|ui| {
488                                ui.set_width(180.0);
489                                let chart = super::charts::BarChart::new("Detected Issues")
490                                    .add(
491                                        "Suspense",
492                                        self.network.statistics.suspense_account_count as f64,
493                                        self.theme.alert_medium,
494                                    )
495                                    .add(
496                                        "GAAP",
497                                        self.network.statistics.gaap_violation_count as f64,
498                                        self.theme.alert_high,
499                                    )
500                                    .add(
501                                        "Fraud",
502                                        self.network.statistics.fraud_pattern_count as f64,
503                                        self.theme.alert_critical,
504                                    );
505                                chart.show(ui, &self.theme);
506                            });
507
508                            ui.separator();
509
510                            // Flow rate sparkline
511                            ui.vertical(|ui| {
512                                ui.set_width(150.0);
513                                let mut sparkline = super::charts::Sparkline::new("Flow Rate");
514                                sparkline.color = self.theme.flow_normal;
515                                for val in self.dashboard.flow_rate_history() {
516                                    sparkline.push(*val);
517                                }
518                                sparkline.show(ui, &self.theme);
519
520                                ui.add_space(10.0);
521
522                                let mut risk_sparkline =
523                                    super::charts::Sparkline::new("Risk Trend");
524                                risk_sparkline.color = self.theme.alert_high;
525                                for val in self.dashboard.risk_history() {
526                                    risk_sparkline.push(*val * 100.0);
527                                }
528                                risk_sparkline.show(ui, &self.theme);
529                            });
530                        });
531                    });
532                });
533        }
534
535        // Central panel - Network Canvas
536        CentralPanel::default().show(ctx, |ui| {
537            self.canvas.show(ui, &self.network);
538        });
539
540        // Educational overlay (uses egui Window which takes ctx)
541        if self.education.visible {
542            egui::Window::new("Learn")
543                .collapsible(true)
544                .resizable(true)
545                .default_width(400.0)
546                .show(ctx, |ui| {
547                    self.education.show_content(ui, &self.theme);
548                });
549        }
550
551        // Debug window
552        if self.show_debug {
553            egui::Window::new("Debug Info")
554                .collapsible(true)
555                .show(ctx, |ui| {
556                    ui.label(format!("Frame: {}", self.frame_count));
557                    ui.label(format!("Accounts: {}", self.network.accounts.len()));
558                    ui.label(format!("Flows: {}", self.network.flows.len()));
559                    ui.label(format!("Particles: {}", self.canvas.particles.count()));
560                    ui.label(format!(
561                        "Layout converged: {}",
562                        self.canvas.layout.converged
563                    ));
564                    ui.label(format!("Selected node: {:?}", self.canvas.selected_node));
565                    ui.label(format!("Hovered node: {:?}", self.canvas.hovered_node));
566
567                    ui.separator();
568                    ui.heading("Ring Kernel Actor System");
569                    let status = self.actor_runtime.status();
570                    ui.label(format!("GPU Active: {}", status.cuda_active));
571                    ui.label(format!("Kernels: {}", status.kernels_launched));
572                    ui.label(format!("Messages Processed: {}", status.messages_processed));
573                    ui.label(format!(
574                        "Snapshots: {}",
575                        status.coordinator_stats.snapshots_processed
576                    ));
577                    ui.label(format!(
578                        "Avg Processing: {:.2} µs",
579                        status.coordinator_stats.avg_processing_time_us
580                    ));
581                });
582        }
583
584        // Request repaint for animation
585        ctx.request_repaint();
586    }
587}
588
589/// Run the application.
590pub fn run() -> eframe::Result<()> {
591    let options = eframe::NativeOptions {
592        viewport: egui::ViewportBuilder::default()
593            .with_inner_size([1400.0, 900.0])
594            .with_min_inner_size([800.0, 600.0])
595            .with_title("Accounting Network Analytics"),
596        ..Default::default()
597    };
598
599    eframe::run_native(
600        "AccNet",
601        options,
602        Box::new(|cc| Ok(Box::new(AccNetApp::new(cc)))),
603    )
604}
605
606#[cfg(test)]
607mod tests {
608    // App tests would require a graphics context
609}