1+ use crate :: metrics:: { MetricResults , MetricValue } ;
2+ use anyhow:: Result ;
3+ use og_graph:: graph:: CodeGraph ;
4+ use og_metrics_centrality:: { CentralityMetrics , CentralityResults } ;
5+ use og_metrics_community:: { CommunityDetection , CommunityResults } ;
6+ use og_metrics_risk:: { RiskAnalyzer , RiskResults } ;
7+ use og_metrics_quality:: { QualityAnalyzer , QualityResults } ;
8+ // Removed unused imports
9+ use std:: time:: Duration ;
10+ use std:: sync:: Arc ;
11+ use tracing:: { debug, info, warn, error} ;
12+
13+ /// Configuration for analytics engine v2 with improved error handling
14+ #[ derive( Debug , Clone ) ]
15+ pub struct AnalyticsConfigV2 {
16+ /// Weights for composite metrics
17+ pub weights : MetricWeights ,
18+ /// Enable parallel computation per metric type
19+ pub parallel_metrics : bool ,
20+ /// Timeout for each metric calculation
21+ pub metric_timeout : Duration ,
22+ /// PageRank iterations
23+ pub pagerank_iterations : usize ,
24+ /// PageRank damping factor
25+ pub pagerank_damping : f64 ,
26+ /// Community detection resolution
27+ pub louvain_resolution : f64 ,
28+ /// Enable sampling for large graphs
29+ pub use_sampling : bool ,
30+ /// Sample size for betweenness centrality
31+ pub betweenness_sample_size : usize ,
32+ }
33+
34+ impl Default for AnalyticsConfigV2 {
35+ fn default ( ) -> Self {
36+ Self {
37+ weights : MetricWeights :: default ( ) ,
38+ parallel_metrics : true ,
39+ metric_timeout : Duration :: from_secs ( 10 ) ,
40+ pagerank_iterations : 30 ,
41+ pagerank_damping : 0.85 ,
42+ louvain_resolution : 1.0 ,
43+ use_sampling : true ,
44+ betweenness_sample_size : 1000 ,
45+ }
46+ }
47+ }
48+
49+ /// Weights for composite metrics
50+ #[ derive( Debug , Clone ) ]
51+ pub struct MetricWeights {
52+ pub importance_pagerank : f64 ,
53+ pub importance_degree : f64 ,
54+ pub importance_betweenness : f64 ,
55+ pub risk_complexity : f64 ,
56+ pub risk_coupling : f64 ,
57+ pub risk_churn : f64 ,
58+ pub chokepoint_betweenness : f64 ,
59+ pub chokepoint_clustering : f64 ,
60+ pub chokepoint_degree : f64 ,
61+ pub payoff_risk : f64 ,
62+ pub payoff_importance : f64 ,
63+ pub payoff_coverage : f64 ,
64+ }
65+
66+ impl Default for MetricWeights {
67+ fn default ( ) -> Self {
68+ Self {
69+ importance_pagerank : 0.5 ,
70+ importance_degree : 0.3 ,
71+ importance_betweenness : 0.2 ,
72+ risk_complexity : 0.4 ,
73+ risk_coupling : 0.4 ,
74+ risk_churn : 0.2 ,
75+ chokepoint_betweenness : 0.6 ,
76+ chokepoint_clustering : 0.2 ,
77+ chokepoint_degree : 0.2 ,
78+ payoff_risk : 0.4 ,
79+ payoff_importance : 0.3 ,
80+ payoff_coverage : 0.3 ,
81+ }
82+ }
83+ }
84+
85+ /// Main analytics engine v2 with modular metrics
86+ pub struct AnalyticsEngineV2 {
87+ config : AnalyticsConfigV2 ,
88+ centrality_metrics : Arc < CentralityMetrics > ,
89+ community_detector : Arc < CommunityDetection > ,
90+ risk_analyzer : Arc < RiskAnalyzer > ,
91+ quality_analyzer : Arc < QualityAnalyzer > ,
92+ }
93+
94+ impl AnalyticsEngineV2 {
95+ /// Create a new analytics engine v2
96+ pub fn new ( config : AnalyticsConfigV2 ) -> Self {
97+ // Configure individual metric modules
98+ let mut centrality_metrics = CentralityMetrics :: new ( ) ;
99+ centrality_metrics. max_iterations = config. pagerank_iterations ;
100+ centrality_metrics. use_sampling = config. use_sampling ;
101+ centrality_metrics. sample_size = config. betweenness_sample_size ;
102+
103+ let community_detector = CommunityDetection :: with_resolution ( config. louvain_resolution ) ;
104+ let risk_analyzer = RiskAnalyzer :: new ( ) ;
105+ let quality_analyzer = QualityAnalyzer :: new ( ) ;
106+
107+ Self {
108+ config,
109+ centrality_metrics : Arc :: new ( centrality_metrics) ,
110+ community_detector : Arc :: new ( community_detector) ,
111+ risk_analyzer : Arc :: new ( risk_analyzer) ,
112+ quality_analyzer : Arc :: new ( quality_analyzer) ,
113+ }
114+ }
115+
116+ /// Validate graph before analysis
117+ fn validate_graph ( & self , graph : & CodeGraph ) -> Result < ( ) > {
118+ let node_count = graph. node_map . len ( ) ;
119+ let edge_count = graph. graph . edge_count ( ) ;
120+
121+ debug ! ( "Validating graph: {} nodes, {} edges" , node_count, edge_count) ;
122+
123+ if node_count == 0 {
124+ warn ! ( "Graph is empty" ) ;
125+ }
126+
127+ // Check for disconnected components (informational)
128+ let connected = petgraph:: algo:: connected_components ( & graph. graph ) ;
129+ if connected > 1 {
130+ info ! ( "Graph has {} disconnected components" , connected) ;
131+ }
132+
133+ Ok ( ( ) )
134+ }
135+
136+ /// Analyze a code graph with modular metrics
137+ pub async fn analyze ( & self , graph : & CodeGraph ) -> Result < ModularAnalysisReport > {
138+ info ! ( "Starting modular graph analysis with {} nodes" , graph. node_map. len( ) ) ;
139+
140+ // Validate graph
141+ self . validate_graph ( graph) ?;
142+
143+ let mut report = ModularAnalysisReport :: default ( ) ;
144+
145+ // Run each metric module with timeout and error recovery
146+ if self . config . parallel_metrics {
147+ // Run metrics in parallel with isolated error handling
148+ let ( centrality, community, risk, quality) = tokio:: join!(
149+ self . run_centrality_with_timeout( graph) ,
150+ self . run_community_with_timeout( graph) ,
151+ self . run_risk_with_timeout( graph) ,
152+ self . run_quality_with_timeout( graph) ,
153+ ) ;
154+
155+ report. centrality = centrality. unwrap_or_else ( |e| {
156+ error ! ( "Centrality metrics failed: {}" , e) ;
157+ report. errors . push ( format ! ( "Centrality: {}" , e) ) ;
158+ CentralityResults :: default ( )
159+ } ) ;
160+
161+ report. community = community. unwrap_or_else ( |e| {
162+ error ! ( "Community detection failed: {}" , e) ;
163+ report. errors . push ( format ! ( "Community: {}" , e) ) ;
164+ CommunityResults :: default ( )
165+ } ) ;
166+
167+ report. risk = risk. unwrap_or_else ( |e| {
168+ error ! ( "Risk analysis failed: {}" , e) ;
169+ report. errors . push ( format ! ( "Risk: {}" , e) ) ;
170+ RiskResults :: default ( )
171+ } ) ;
172+
173+ report. quality = quality. unwrap_or_else ( |e| {
174+ error ! ( "Quality metrics failed: {}" , e) ;
175+ report. errors . push ( format ! ( "Quality: {}" , e) ) ;
176+ QualityResults :: default ( )
177+ } ) ;
178+ } else {
179+ // Run metrics sequentially with individual error handling
180+ report. centrality = self . run_centrality_with_timeout ( graph) . await
181+ . unwrap_or_else ( |e| {
182+ error ! ( "Centrality metrics failed: {}" , e) ;
183+ report. errors . push ( format ! ( "Centrality: {}" , e) ) ;
184+ CentralityResults :: default ( )
185+ } ) ;
186+
187+ report. community = self . run_community_with_timeout ( graph) . await
188+ . unwrap_or_else ( |e| {
189+ error ! ( "Community detection failed: {}" , e) ;
190+ report. errors . push ( format ! ( "Community: {}" , e) ) ;
191+ CommunityResults :: default ( )
192+ } ) ;
193+
194+ report. risk = self . run_risk_with_timeout ( graph) . await
195+ . unwrap_or_else ( |e| {
196+ error ! ( "Risk analysis failed: {}" , e) ;
197+ report. errors . push ( format ! ( "Risk: {}" , e) ) ;
198+ RiskResults :: default ( )
199+ } ) ;
200+
201+ report. quality = self . run_quality_with_timeout ( graph) . await
202+ . unwrap_or_else ( |e| {
203+ error ! ( "Quality metrics failed: {}" , e) ;
204+ report. errors . push ( format ! ( "Quality: {}" , e) ) ;
205+ QualityResults :: default ( )
206+ } ) ;
207+ }
208+
209+ // Calculate composite scores
210+ report. calculate_composite_scores ( & self . config . weights ) ;
211+
212+ info ! ( "Modular analysis complete with {} errors" , report. errors. len( ) ) ;
213+ Ok ( report)
214+ }
215+
216+ /// Run centrality metrics with timeout
217+ async fn run_centrality_with_timeout ( & self , graph : & CodeGraph ) -> Result < CentralityResults > {
218+ let graph = graph. clone ( ) ;
219+ let metrics = Arc :: clone ( & self . centrality_metrics ) ;
220+ let timeout = self . config . metric_timeout ;
221+
222+ tokio:: time:: timeout ( timeout, tokio:: task:: spawn_blocking ( move || {
223+ metrics. calculate_all ( & graph)
224+ } ) )
225+ . await ?
226+ . map_err ( |e| anyhow:: anyhow!( "Task join error: {}" , e) ) ?
227+ }
228+
229+ /// Run community detection with timeout
230+ async fn run_community_with_timeout ( & self , graph : & CodeGraph ) -> Result < CommunityResults > {
231+ let graph = graph. clone ( ) ;
232+ let detector = Arc :: clone ( & self . community_detector ) ;
233+ let timeout = self . config . metric_timeout ;
234+
235+ tokio:: time:: timeout ( timeout, tokio:: task:: spawn_blocking ( move || {
236+ detector. detect_communities ( & graph)
237+ } ) )
238+ . await ?
239+ . map_err ( |e| anyhow:: anyhow!( "Task join error: {}" , e) ) ?
240+ }
241+
242+ /// Run risk analysis with timeout
243+ async fn run_risk_with_timeout ( & self , graph : & CodeGraph ) -> Result < RiskResults > {
244+ let graph = graph. clone ( ) ;
245+ let analyzer = Arc :: clone ( & self . risk_analyzer ) ;
246+ let timeout = self . config . metric_timeout ;
247+
248+ tokio:: time:: timeout ( timeout, tokio:: task:: spawn_blocking ( move || {
249+ analyzer. analyze_risks ( & graph)
250+ } ) )
251+ . await ?
252+ . map_err ( |e| anyhow:: anyhow!( "Task join error: {}" , e) ) ?
253+ }
254+
255+ /// Run quality analysis with timeout
256+ async fn run_quality_with_timeout ( & self , graph : & CodeGraph ) -> Result < QualityResults > {
257+ let graph = graph. clone ( ) ;
258+ let analyzer = Arc :: clone ( & self . quality_analyzer ) ;
259+ let timeout = self . config . metric_timeout ;
260+
261+ tokio:: time:: timeout ( timeout, tokio:: task:: spawn_blocking ( move || {
262+ analyzer. analyze_quality ( & graph)
263+ } ) )
264+ . await ?
265+ . map_err ( |e| anyhow:: anyhow!( "Task join error: {}" , e) ) ?
266+ }
267+ }
268+
269+ /// Modular analysis report combining all metric results
270+ #[ derive( Debug , Default , Clone ) ]
271+ pub struct ModularAnalysisReport {
272+ pub centrality : CentralityResults ,
273+ pub community : CommunityResults ,
274+ pub risk : RiskResults ,
275+ pub quality : QualityResults ,
276+ pub composite_scores : CompositeScores ,
277+ pub errors : Vec < String > ,
278+ }
279+
280+ impl ModularAnalysisReport {
281+ /// Calculate composite scores from individual metrics
282+ pub fn calculate_composite_scores ( & mut self , _weights : & MetricWeights ) {
283+ // This would calculate weighted composite scores
284+ // Implementation depends on specific business logic
285+ self . composite_scores = CompositeScores :: default ( ) ;
286+ }
287+
288+ /// Convert to legacy MetricResults format if needed
289+ pub fn to_metric_results ( & self ) -> Vec < MetricResults > {
290+ let mut results = Vec :: new ( ) ;
291+
292+ // Convert centrality results
293+ let mut centrality_result = MetricResults :: new ( "centrality" . to_string ( ) ) ;
294+ for ( node_id, metrics) in & self . centrality . degree {
295+ centrality_result. add_value (
296+ node_id. clone ( ) ,
297+ MetricValue :: Float ( metrics. total_degree ) ,
298+ ) ;
299+ }
300+ results. push ( centrality_result) ;
301+
302+ // Add other metric conversions as needed
303+
304+ results
305+ }
306+ }
307+
308+ #[ derive( Debug , Default , Clone ) ]
309+ pub struct CompositeScores {
310+ pub importance_scores : Vec < ( String , f64 ) > ,
311+ pub risk_scores : Vec < ( String , f64 ) > ,
312+ pub quality_scores : Vec < ( String , f64 ) > ,
313+ }
314+
315+ // Clone implementations moved to respective crates
316+
317+ #[ cfg( test) ]
318+ mod tests {
319+ use super :: * ;
320+
321+ #[ tokio:: test]
322+ async fn test_modular_engine ( ) {
323+ let graph = CodeGraph :: new ( ) ;
324+ let config = AnalyticsConfigV2 :: default ( ) ;
325+ let engine = AnalyticsEngineV2 :: new ( config) ;
326+
327+ let report = engine. analyze ( & graph) . await . unwrap ( ) ;
328+ assert ! ( report. errors. is_empty( ) || !report. errors. is_empty( ) ) ;
329+ }
330+ }
0 commit comments