diff --git a/src/graph.rs b/src/graph.rs index 6428d3f..62dc986 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -504,7 +504,7 @@ fn label_propagation( /// A snapshot of graph topology metrics, for tracking evolution over time #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MetricsSnapshot { - pub timestamp: f64, + pub timestamp: i64, pub date: String, pub nodes: usize, pub edges: usize, @@ -548,14 +548,39 @@ pub fn save_metrics_snapshot(snap: &MetricsSnapshot) { } } -/// Health report: summary of graph metrics +/// Compute current graph metrics as a snapshot (no side effects). +pub fn current_metrics(graph: &Graph) -> MetricsSnapshot { + let now = crate::store::now_epoch(); + let date = crate::store::format_datetime_space(now); + MetricsSnapshot { + timestamp: now, + date, + nodes: graph.nodes().len(), + edges: graph.edge_count(), + communities: graph.community_count(), + sigma: graph.small_world_sigma(), + alpha: graph.degree_power_law_exponent(), + gini: graph.degree_gini(), + avg_cc: graph.avg_clustering_coefficient(), + avg_path_length: graph.avg_path_length(), + } +} + +/// Health report: summary of graph metrics. +/// Saves a metrics snapshot as a side effect (callers who want pure +/// computation should use `current_metrics` + `save_metrics_snapshot`). pub fn health_report(graph: &Graph, store: &Store) -> String { - let n = graph.nodes().len(); - let e = graph.edge_count(); - let avg_cc = graph.avg_clustering_coefficient(); - let avg_pl = graph.avg_path_length(); - let sigma = graph.small_world_sigma(); - let communities = graph.community_count(); + let snap = current_metrics(graph); + save_metrics_snapshot(&snap); + + let n = snap.nodes; + let e = snap.edges; + let avg_cc = snap.avg_cc; + let avg_pl = snap.avg_path_length; + let sigma = snap.sigma; + let alpha = snap.alpha; + let gini = snap.gini; + let communities = snap.communities; // Community sizes let mut comm_sizes: HashMap = HashMap::new(); @@ -576,10 +601,6 @@ pub fn health_report(graph: &Graph, store: &Store) -> String { degrees.iter().sum::() as f64 / n as f64 }; - // Topology metrics - let alpha = graph.degree_power_law_exponent(); - let gini = graph.degree_gini(); - // Low-CC nodes: poorly integrated let low_cc = graph.nodes().iter() .filter(|k| graph.clustering_coefficient(k) < 0.1) @@ -588,18 +609,6 @@ pub fn health_report(graph: &Graph, store: &Store) -> String { // Category breakdown let cats = store.category_counts(); - // Snapshot current metrics and log - let now = crate::store::now_epoch(); - let date = crate::store::format_datetime_space(now); - let snap = MetricsSnapshot { - timestamp: now, - date: date.clone(), - nodes: n, edges: e, communities, - sigma, alpha, gini, avg_cc, - avg_path_length: avg_pl, - }; - save_metrics_snapshot(&snap); - // Load history for deltas let history = load_metrics_history(); let prev = if history.len() >= 2 {