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
214            .try_read()
215            .ok_or_else(|| ServiceError::Internal("Failed to read StateManager".to_string()))?;
216        let core_state = state_manager.get_core_state();
217        let count = core_state.get_neuron_count() as usize;
218
219        Ok(count)
220    }
221
222    async fn get_total_synapse_count(&self) -> ServiceResult<usize> {
223        trace!(target: "feagi-services", "Getting total synapse count");
224
225        // CRITICAL: Read from StateManager's atomic cache - NO NPU lock, NO iteration
226        let state_manager_instance = StateManager::instance();
227        let state_manager = state_manager_instance
228            .try_read()
229            .ok_or_else(|| ServiceError::Internal("Failed to read StateManager".to_string()))?;
230        let core_state = state_manager.get_core_state();
231        let count = core_state.get_synapse_count() as usize;
232
233        Ok(count)
234    }
235
236    async fn get_populated_areas(&self) -> ServiceResult<Vec<(String, usize)>> {
237        trace!(target: "feagi-services", "Getting populated areas");
238
239        let areas = self.connectome.read().get_populated_areas();
240        Ok(areas)
241    }
242
243    async fn get_neuron_density(&self, cortical_id: &str) -> ServiceResult<f32> {
244        trace!(target: "feagi-services", "Getting neuron density for area: {}", cortical_id);
245
246        // Convert String to CorticalID
247        let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
248            .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
249
250        let density = self
251            .connectome
252            .read()
253            .get_neuron_density(&cortical_id_typed);
254        Ok(density)
255    }
256
257    async fn is_brain_initialized(&self) -> ServiceResult<bool> {
258        trace!(target: "feagi-services", "Checking if brain is initialized");
259
260        let initialized = self.connectome.read().is_initialized();
261        Ok(initialized)
262    }
263
264    async fn is_burst_engine_ready(&self) -> ServiceResult<bool> {
265        trace!(target: "feagi-services", "Checking if burst engine is ready");
266
267        let ready = self.connectome.read().has_npu();
268        Ok(ready)
269    }
270
271    async fn get_regular_neuron_count(&self) -> ServiceResult<usize> {
272        trace!(target: "feagi-services", "Getting regular (non-memory) neuron count");
273
274        // CRITICAL: Read from StateManager's atomic cache - NO iteration, NO locks, NO NPU access
275        let state_manager_instance = StateManager::instance();
276        let state_manager = state_manager_instance
277            .try_read()
278            .ok_or_else(|| ServiceError::Internal("Failed to read StateManager".to_string()))?;
279        let core_state = state_manager.get_core_state();
280        let count = core_state.get_regular_neuron_count() as usize;
281
282        Ok(count)
283    }
284
285    async fn get_memory_neuron_count(&self) -> ServiceResult<usize> {
286        trace!(target: "feagi-services", "Getting memory neuron count");
287
288        // CRITICAL: Read from StateManager's atomic cache - NO iteration, NO locks, NO NPU access
289        let state_manager_instance = StateManager::instance();
290        let state_manager = state_manager_instance
291            .try_read()
292            .ok_or_else(|| ServiceError::Internal("Failed to read StateManager".to_string()))?;
293        let core_state = state_manager.get_core_state();
294        let count = core_state.get_memory_neuron_count() as usize;
295
296        Ok(count)
297    }
298}