1use 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
18pub struct AccNetApp {
20 canvas: NetworkCanvas,
22 controls: ControlPanel,
24 analytics: AnalyticsPanel,
26 dashboard: AnalyticsDashboard,
28 alerts: AlertsPanel,
30 education: EducationalOverlay,
32 theme: AccNetTheme,
34
35 network: AccountingNetwork,
37 pipeline: Option<DataFabricPipeline>,
39 engine: AnalyticsEngine,
41 analysis_runtime: AnalysisRuntime,
43 actor_runtime: GpuActorRuntime,
45
46 frame_count: u64,
48 batches_processed: u64,
50 show_debug: bool,
52 show_dashboard: bool,
54 last_layout_update: std::time::Instant,
56}
57
58impl AccNetApp {
59 pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
61 let theme = AccNetTheme::dark();
63 theme.apply(&cc.egui_ctx);
64
65 let entity_id = Uuid::new_v4();
67 let network = AccountingNetwork::new(entity_id, 2024, 1);
68
69 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 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 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 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 let gen_config = GeneratorConfig {
135 seed: Some(42),
136 ..Default::default()
137 };
138
139 self.pipeline = Some(DataFabricPipeline::new(archetype, gen_config, config));
141
142 if let Some(ref pipeline) = self.pipeline {
144 self.network = pipeline.network().clone();
145 }
146
147 self.canvas.initialize(&self.network);
149 self.batches_processed = 0;
150 }
151
152 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 let batches_to_process = (self.controls.speed).ceil() as usize;
168
169 for _ in 0..batches_to_process {
170 let flows = pipeline.tick();
172
173 if !flows.is_empty() {
174 self.batches_processed += 1;
175 }
176 }
177
178 self.network = pipeline.network().clone();
180
181 let analysis = self.analysis_runtime.analyze(&self.network);
183
184 let actor_result = self.actor_runtime.analyze(&self.network);
186
187 if actor_result.benford_distribution.iter().sum::<u32>() > 0 {
189 }
192
193 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 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 let snapshot = self.engine.analyze(&self.network);
243 self.analytics.update(snapshot);
244
245 if self.batches_processed.is_multiple_of(100) && self.batches_processed > 0 {
248 self.canvas.refresh_particles(&self.network);
249 }
250 }
251
252 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 ui.close_menu();
304 }
305 });
306
307 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
309 ui.checkbox(&mut self.show_debug, "Debug");
310
311 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 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 self.process_pipeline();
367
368 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 self.canvas.update();
378
379 TopBottomPanel::top("menu_bar").show(ctx, |ui| {
381 self.menu_bar(ui);
382 });
383
384 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 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 self.dashboard.update(&self.network, self.frame_count);
408 self.dashboard.show(ui, &self.network, &self.theme);
409 });
410 });
411
412 if self.show_dashboard {
414 egui::TopBottomPanel::bottom("dashboard")
415 .default_height(180.0)
416 .resizable(true)
417 .show(ctx, |ui| {
418 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 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 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 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 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 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 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 CentralPanel::default().show(ctx, |ui| {
537 self.canvas.show(ui, &self.network);
538 });
539
540 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 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 ctx.request_repaint();
586 }
587}
588
589pub 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 }