turbovault_tools/
analysis_tools.rs

1//! Vault analysis tools for statistics and relationship analysis
2
3use serde_json::json;
4use std::sync::Arc;
5use turbovault_core::prelude::*;
6use turbovault_vault::VaultManager;
7
8/// Analysis tools context
9pub struct AnalysisTools {
10    pub manager: Arc<VaultManager>,
11}
12
13/// Statistics response structure
14#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
15pub struct VaultStats {
16    pub total_files: usize,
17    pub total_links: usize,
18    pub orphaned_files: usize,
19    pub average_links_per_file: f64,
20}
21
22impl AnalysisTools {
23    /// Create new analysis tools
24    pub fn new(manager: Arc<VaultManager>) -> Self {
25        Self { manager }
26    }
27
28    /// Get vault statistics
29    pub async fn get_vault_stats(&self) -> Result<VaultStats> {
30        let stats = self.manager.get_stats().await?;
31
32        Ok(VaultStats {
33            total_files: stats.total_files,
34            total_links: stats.total_links,
35            orphaned_files: stats.orphaned_files,
36            average_links_per_file: stats.average_links_per_file,
37        })
38    }
39
40    /// List all orphaned notes (no incoming or outgoing links)
41    pub async fn list_orphaned_notes(&self) -> Result<Vec<String>> {
42        let orphans = self.manager.get_orphaned_notes().await?;
43
44        Ok(orphans
45            .into_iter()
46            .filter_map(|p| p.to_str().map(|s| s.to_string()))
47            .collect())
48    }
49
50    /// Detect cycles (mutual linking patterns)
51    pub async fn detect_cycles(&self) -> Result<Vec<Vec<String>>> {
52        let graph = self.manager.link_graph();
53        let graph_read = graph.read().await;
54
55        let cycles = graph_read
56            .cycles()
57            .into_iter()
58            .map(|cycle| {
59                cycle
60                    .into_iter()
61                    .filter_map(|p| p.to_str().map(|s| s.to_string()))
62                    .collect()
63            })
64            .collect();
65
66        Ok(cycles)
67    }
68
69    /// Get link density (total links / possible links)
70    pub async fn get_link_density(&self) -> Result<f64> {
71        let stats = self.manager.get_stats().await?;
72
73        // Link density = actual_links / possible_links
74        // where possible_links = n * (n - 1) for directed graph
75        if stats.total_files <= 1 {
76            return Ok(0.0);
77        }
78
79        let possible_links = (stats.total_files as f64) * ((stats.total_files as f64) - 1.0);
80        let density = (stats.total_links as f64) / possible_links;
81
82        Ok(density)
83    }
84
85    /// Get connectivity metrics
86    pub async fn get_connectivity_metrics(&self) -> Result<serde_json::Value> {
87        let stats = self.manager.get_stats().await?;
88        let density = self.get_link_density().await?;
89
90        Ok(json!({
91            "total_files": stats.total_files,
92            "total_links": stats.total_links,
93            "orphaned_files": stats.orphaned_files,
94            "connected_files": stats.total_files - stats.orphaned_files,
95            "average_links_per_file": stats.average_links_per_file,
96            "link_density": density,
97            "connectivity_rate": if stats.total_files > 0 {
98                (stats.total_files - stats.orphaned_files) as f64 / stats.total_files as f64
99            } else {
100                0.0
101            }
102        }))
103    }
104}