Skip to main content

feagi_services/impls/
analytics_service_impl.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Analytics service implementation.
6
7Copyright 2025 Neuraville Inc.
8Licensed under the Apache License, Version 2.0
9*/
10
11use std::sync::Arc;
12
13use async_trait::async_trait;
14use feagi_brain_development::ConnectomeManager;
15use feagi_npu_burst_engine::BurstLoopRunner;
16use feagi_state_manager::StateManager;
17use feagi_structures::genomic::cortical_area::CorticalID;
18use parking_lot::RwLock;
19use tracing::trace;
20
21use crate::traits::AnalyticsService;
22use crate::types::*;
23
24/// Default implementation of AnalyticsService
25pub struct AnalyticsServiceImpl {
26    connectome: Arc<RwLock<ConnectomeManager>>,
27    burst_runner: Option<Arc<RwLock<BurstLoopRunner>>>,
28}
29
30impl AnalyticsServiceImpl {
31    pub fn new(
32        connectome: Arc<RwLock<ConnectomeManager>>,
33        burst_runner: Option<Arc<RwLock<BurstLoopRunner>>>,
34    ) -> Self {
35        Self {
36            connectome,
37            burst_runner,
38        }
39    }
40}
41
42#[async_trait]
43impl AnalyticsService for AnalyticsServiceImpl {
44    async fn get_system_health(&self) -> ServiceResult<SystemHealth> {
45        trace!(target: "feagi-services", "Getting system health");
46
47        let neuron_count = self.get_total_neuron_count().await?;
48        let manager = self.connectome.read();
49        let cortical_area_count = manager.get_cortical_area_count();
50        let neuron_capacity = manager.get_neuron_capacity();
51        let synapse_capacity = manager.get_synapse_capacity();
52
53        // Get burst engine status from BurstLoopRunner
54        let (burst_engine_active, burst_count) = if let Some(ref runner) = self.burst_runner {
55            let runner_lock = runner.read();
56            (runner_lock.is_running(), runner_lock.get_burst_count())
57        } else {
58            (false, 0)
59        };
60
61        // Brain is ready ONLY if genome is loaded AND burst engine is actively running
62        // This prevents the Brain Visualizer from exiting loading screen prematurely
63        let brain_readiness = cortical_area_count > 0 && burst_engine_active;
64
65        Ok(SystemHealth {
66            burst_engine_active,
67            brain_readiness,
68            neuron_count,
69            neuron_capacity,
70            synapse_capacity,
71            cortical_area_count,
72            burst_count,
73        })
74    }
75
76    async fn get_cortical_area_stats(&self, cortical_id: &str) -> ServiceResult<CorticalAreaStats> {
77        trace!(target: "feagi-services", "Getting cortical area stats: {}", cortical_id);
78
79        let manager = self.connectome.read();
80        let neuron_count = manager.get_neuron_count_in_area(
81            &CorticalID::try_from_base_64(cortical_id)
82                .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?,
83        );
84        let synapse_count = manager.get_synapse_count_in_area(
85            &CorticalID::try_from_base_64(cortical_id)
86                .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?,
87        );
88        let density = manager.get_neuron_density(
89            &CorticalID::try_from_base_64(cortical_id)
90                .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?,
91        );
92        let populated = neuron_count > 0;
93
94        Ok(CorticalAreaStats {
95            cortical_id: cortical_id.to_string(),
96            neuron_count,
97            synapse_count,
98            density,
99            populated,
100        })
101    }
102
103    async fn get_all_cortical_area_stats(&self) -> ServiceResult<Vec<CorticalAreaStats>> {
104        trace!(target: "feagi-services", "Getting all cortical area stats");
105
106        let all_stats = self.connectome.read().get_all_area_stats();
107
108        let stats: Vec<CorticalAreaStats> = all_stats
109            .into_iter()
110            .map(
111                |(cortical_id, neuron_count, synapse_count, density)| CorticalAreaStats {
112                    cortical_id,
113                    neuron_count,
114                    synapse_count,
115                    density,
116                    populated: neuron_count > 0,
117                },
118            )
119            .collect();
120
121        Ok(stats)
122    }
123
124    async fn get_connectivity_stats(
125        &self,
126        source_area: &str,
127        target_area: &str,
128    ) -> ServiceResult<ConnectivityStats> {
129        trace!(target: "feagi-services",
130            "Getting connectivity stats: {} -> {}",
131            source_area,
132            target_area
133        );
134
135        // Convert String to CorticalID
136        let source_id = CorticalID::try_from_base_64(source_area).map_err(|e| {
137            ServiceError::InvalidInput(format!("Invalid source cortical ID: {}", e))
138        })?;
139        let target_id = CorticalID::try_from_base_64(target_area).map_err(|e| {
140            ServiceError::InvalidInput(format!("Invalid target cortical ID: {}", e))
141        })?;
142
143        let manager = self.connectome.read();
144
145        // Verify both areas exist
146        if !manager.has_cortical_area(&source_id) {
147            return Err(ServiceError::NotFound {
148                resource: "CorticalArea".to_string(),
149                id: source_area.to_string(),
150            });
151        }
152        if !manager.has_cortical_area(&target_id) {
153            return Err(ServiceError::NotFound {
154                resource: "CorticalArea".to_string(),
155                id: target_area.to_string(),
156            });
157        }
158
159        // Get all neurons in source area
160        let source_neurons = manager.get_neurons_in_area(&source_id);
161
162        // Count synapses going from source to target
163        let mut synapse_count = 0;
164        let mut total_weight: u64 = 0;
165        let mut excitatory_count = 0;
166        let mut inhibitory_count = 0;
167
168        for source_neuron_id in source_neurons {
169            // Get outgoing synapses from this neuron
170            let outgoing = manager.get_outgoing_synapses(source_neuron_id);
171
172            for (target_neuron_id, weight, _psp, synapse_type) in outgoing {
173                // Check if target neuron is in target area
174                if let Some(target_cortical_id) =
175                    manager.get_neuron_cortical_id(target_neuron_id as u64)
176                {
177                    if target_cortical_id.as_base_64() == target_area {
178                        synapse_count += 1;
179                        total_weight += weight as u64;
180
181                        // synapse_type: 0 = excitatory, 1 = inhibitory (from feagi-types)
182                        if synapse_type == 0 {
183                            excitatory_count += 1;
184                        } else {
185                            inhibitory_count += 1;
186                        }
187                    }
188                }
189            }
190        }
191
192        let avg_weight = if synapse_count > 0 {
193            (total_weight as f64 / synapse_count as f64) as f32
194        } else {
195            0.0
196        };
197
198        Ok(ConnectivityStats {
199            source_area: source_area.to_string(),
200            target_area: target_area.to_string(),
201            synapse_count,
202            avg_weight,
203            excitatory_count,
204            inhibitory_count,
205        })
206    }
207
208    async fn get_total_neuron_count(&self) -> ServiceResult<usize> {
209        trace!(target: "feagi-services", "Getting total neuron count");
210
211        // CRITICAL: Read from StateManager's atomic cache - NO NPU lock, NO iteration
212        let state_manager_instance = StateManager::instance();
213        let state_manager = state_manager_instance.read();
214        let core_state = state_manager.get_core_state();
215        let count = core_state.get_neuron_count() as usize;
216
217        Ok(count)
218    }
219
220    async fn get_total_synapse_count(&self) -> ServiceResult<usize> {
221        trace!(target: "feagi-services", "Getting total synapse count");
222
223        // CRITICAL: Read from StateManager's atomic cache - NO NPU lock, NO iteration
224        let state_manager_instance = StateManager::instance();
225        let state_manager = state_manager_instance.read();
226        let core_state = state_manager.get_core_state();
227        let count = core_state.get_synapse_count() as usize;
228
229        Ok(count)
230    }
231
232    async fn get_populated_areas(&self) -> ServiceResult<Vec<(String, usize)>> {
233        trace!(target: "feagi-services", "Getting populated areas");
234
235        let areas = self.connectome.read().get_populated_areas();
236        Ok(areas)
237    }
238
239    async fn get_neuron_density(&self, cortical_id: &str) -> ServiceResult<f32> {
240        trace!(target: "feagi-services", "Getting neuron density for area: {}", cortical_id);
241
242        // Convert String to CorticalID
243        let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
244            .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
245
246        let density = self
247            .connectome
248            .read()
249            .get_neuron_density(&cortical_id_typed);
250        Ok(density)
251    }
252
253    async fn is_brain_initialized(&self) -> ServiceResult<bool> {
254        trace!(target: "feagi-services", "Checking if brain is initialized");
255
256        let initialized = self.connectome.read().is_initialized();
257        Ok(initialized)
258    }
259
260    async fn is_burst_engine_ready(&self) -> ServiceResult<bool> {
261        trace!(target: "feagi-services", "Checking if burst engine is ready");
262
263        let ready = self.connectome.read().has_npu();
264        Ok(ready)
265    }
266
267    async fn get_regular_neuron_count(&self) -> ServiceResult<usize> {
268        trace!(target: "feagi-services", "Getting regular (non-memory) neuron count");
269
270        // CRITICAL: Read from StateManager's atomic cache - NO iteration, NO locks, NO NPU access
271        let state_manager_instance = StateManager::instance();
272        let state_manager = state_manager_instance.read();
273        let core_state = state_manager.get_core_state();
274        let count = core_state.get_regular_neuron_count() as usize;
275
276        Ok(count)
277    }
278
279    async fn get_memory_neuron_count(&self) -> ServiceResult<usize> {
280        trace!(target: "feagi-services", "Getting memory neuron count");
281
282        // CRITICAL: Read from StateManager's atomic cache - NO iteration, NO locks, NO NPU access
283        let state_manager_instance = StateManager::instance();
284        let state_manager = state_manager_instance.read();
285        let core_state = state_manager.get_core_state();
286        let count = core_state.get_memory_neuron_count() as usize;
287
288        Ok(count)
289    }
290}