1use crate::kernels::{AnalysisConfig, AnalysisKernel, AnalysisResult};
7use crate::models::AccountingNetwork;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum Backend {
12 #[default]
14 Auto,
15 Cuda,
17 Cpu,
19}
20
21#[derive(Debug, Clone)]
23pub struct RuntimeStatus {
24 pub backend: Backend,
26 pub cuda_available: bool,
28 pub cuda_device_name: Option<String>,
30 pub cuda_compute_capability: Option<(u32, u32)>,
32 pub gpu_kernels_ready: bool,
34}
35
36impl Default for RuntimeStatus {
37 fn default() -> Self {
38 Self {
39 backend: Backend::Cpu,
40 cuda_available: false,
41 cuda_device_name: None,
42 cuda_compute_capability: None,
43 gpu_kernels_ready: false,
44 }
45 }
46}
47
48pub struct AnalysisRuntime {
52 backend: Backend,
54 cpu_kernel: AnalysisKernel,
56 status: RuntimeStatus,
58 #[cfg(feature = "cuda")]
60 gpu_executor: Option<super::executor::GpuExecutor>,
61}
62
63impl AnalysisRuntime {
64 pub fn new() -> Self {
66 Self::with_backend(Backend::Auto)
67 }
68
69 pub fn with_backend(backend: Backend) -> Self {
71 let cpu_kernel = AnalysisKernel::new(AnalysisConfig::default());
72 let mut status = RuntimeStatus::default();
73
74 #[cfg(feature = "cuda")]
76 let (gpu_executor, cuda_available) = Self::try_init_gpu(&mut status);
77
78 #[cfg(not(feature = "cuda"))]
79 let cuda_available = false;
80
81 let active_backend = match backend {
83 Backend::Auto => {
84 if cuda_available {
85 Backend::Cuda
86 } else {
87 Backend::Cpu
88 }
89 }
90 Backend::Cuda => {
91 if cuda_available {
92 Backend::Cuda
93 } else {
94 eprintln!("Warning: CUDA requested but not available, falling back to CPU");
95 Backend::Cpu
96 }
97 }
98 Backend::Cpu => Backend::Cpu,
99 };
100
101 status.backend = active_backend;
102 status.cuda_available = cuda_available;
103
104 Self {
105 backend: active_backend,
106 cpu_kernel,
107 status,
108 #[cfg(feature = "cuda")]
109 gpu_executor,
110 }
111 }
112
113 #[cfg(feature = "cuda")]
115 fn try_init_gpu(status: &mut RuntimeStatus) -> (Option<super::executor::GpuExecutor>, bool) {
116 if !ringkernel_cuda::is_cuda_available() {
118 return (None, false);
119 }
120
121 match super::executor::GpuExecutor::new() {
123 Ok(mut executor) => {
124 status.cuda_device_name = Some(executor.device_name().to_string());
125 status.cuda_compute_capability = Some(executor.compute_capability());
126
127 match executor.compile_kernels() {
129 Ok(()) => {
130 status.gpu_kernels_ready = true;
131 eprintln!(
132 "GPU: {} (CC {}.{}) - Kernels compiled",
133 executor.device_name(),
134 executor.compute_capability().0,
135 executor.compute_capability().1
136 );
137 (Some(executor), true)
138 }
139 Err(e) => {
140 eprintln!("Warning: Failed to compile GPU kernels: {}", e);
141 (None, false)
142 }
143 }
144 }
145 Err(e) => {
146 eprintln!("Warning: Failed to initialize GPU: {}", e);
147 (None, false)
148 }
149 }
150 }
151
152 pub fn backend(&self) -> Backend {
154 self.backend
155 }
156
157 pub fn status(&self) -> &RuntimeStatus {
159 &self.status
160 }
161
162 pub fn is_cuda_active(&self) -> bool {
164 self.backend == Backend::Cuda && self.status.gpu_kernels_ready
165 }
166
167 pub fn analyze(&self, network: &AccountingNetwork) -> AnalysisResult {
169 #[cfg(feature = "cuda")]
170 {
171 if self.is_cuda_active() {
172 if let Some(ref executor) = self.gpu_executor {
173 match executor.analyze(network) {
174 Ok(gpu_result) => {
175 return self.convert_gpu_result(network, gpu_result);
177 }
178 Err(e) => {
179 eprintln!("GPU analysis failed, falling back to CPU: {}", e);
180 }
181 }
182 }
183 }
184 }
185
186 self.cpu_kernel.analyze(network)
188 }
189
190 #[cfg(feature = "cuda")]
192 fn convert_gpu_result(
193 &self,
194 network: &AccountingNetwork,
195 gpu_result: super::executor::GpuAnalysisResult,
196 ) -> AnalysisResult {
197 use crate::kernels::AnalysisStats;
198 use crate::models::{GaapViolation, GaapViolationType, HybridTimestamp, ViolationSeverity};
199
200 let suspense_accounts: Vec<(u16, f32)> = gpu_result
202 .suspense_scores
203 .iter()
204 .enumerate()
205 .filter(|(_, &score)| score > 0.5)
206 .map(|(idx, &score)| (idx as u16, score))
207 .collect();
208
209 let gaap_violations: Vec<GaapViolation> = gpu_result
211 .gaap_violations
212 .iter()
213 .enumerate()
214 .filter(|(_, &flag)| flag > 0)
215 .map(|(idx, &flag)| {
216 let flow = network.flows.get(idx);
217 GaapViolation {
218 id: uuid::Uuid::new_v4(),
219 violation_type: match flag {
220 1 => GaapViolationType::RevenueToCashDirect,
221 2 => GaapViolationType::RevenueToExpense,
222 _ => GaapViolationType::UnbalancedEntry,
223 },
224 severity: if flag == 1 {
225 ViolationSeverity::High
226 } else {
227 ViolationSeverity::Medium
228 },
229 source_account: flow.map(|f| f.source_account_index).unwrap_or(0),
230 target_account: flow.map(|f| f.target_account_index).unwrap_or(0),
231 amount: flow.map(|f| f.amount).unwrap_or_default(),
232 journal_entry_id: uuid::Uuid::nil(),
233 detected_at: HybridTimestamp::now(),
234 description: match flag {
235 1 => "Revenue to Asset Direct (GPU detected)".to_string(),
236 2 => "Revenue to Expense Direct (GPU detected)".to_string(),
237 _ => "Unknown violation".to_string(),
238 },
239 }
240 })
241 .collect();
242
243 AnalysisResult {
244 stats: AnalysisStats {
245 accounts_analyzed: network.accounts.len(),
246 flows_analyzed: network.flows.len(),
247 suspense_count: suspense_accounts.len(),
248 gaap_violation_count: gaap_violations.len(),
249 fraud_pattern_count: 0,
250 },
251 suspense_accounts,
252 gaap_violations,
253 fraud_patterns: Vec::new(), }
255 }
256
257 #[cfg(feature = "cuda")]
259 pub fn run_benchmarks(
260 &self,
261 network: &AccountingNetwork,
262 ) -> Option<super::executor::BenchmarkResults> {
263 if let Some(ref executor) = self.gpu_executor {
264 match executor.run_benchmarks(network) {
265 Ok(results) => Some(results),
266 Err(e) => {
267 eprintln!("Benchmark failed: {}", e);
268 None
269 }
270 }
271 } else {
272 None
273 }
274 }
275
276 #[cfg(feature = "cuda")]
278 pub fn cuda_kernel_code(&self, kernel_type: super::CudaKernelType) -> Option<String> {
279 super::GeneratedKernels::generate()
280 .ok()
281 .map(|k| match kernel_type {
282 super::CudaKernelType::SuspenseDetection => k.suspense_detection,
283 super::CudaKernelType::GaapViolation => k.gaap_violation,
284 super::CudaKernelType::BenfordAnalysis => k.benford_analysis,
285 _ => String::new(),
286 })
287 }
288}
289
290impl Default for AnalysisRuntime {
291 fn default() -> Self {
292 Self::new()
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use uuid::Uuid;
300
301 #[test]
302 fn test_runtime_creation() {
303 let runtime = AnalysisRuntime::new();
304 assert!(runtime.backend() == Backend::Cpu || runtime.backend() == Backend::Cuda);
306 }
307
308 #[test]
309 fn test_cpu_fallback() {
310 let runtime = AnalysisRuntime::with_backend(Backend::Cpu);
311 assert_eq!(runtime.backend(), Backend::Cpu);
312 }
313
314 #[test]
315 fn test_analysis() {
316 let runtime = AnalysisRuntime::new();
317 let network = AccountingNetwork::new(Uuid::new_v4(), 2024, 1);
318 let result = runtime.analyze(&network);
319 assert_eq!(result.stats.accounts_analyzed, network.accounts.len());
320 }
321}