use crate::storage::schema::Storage;
use rusqlite::{params, Result as SqliteResult};
use serde::{Deserialize, Serialize};
pub struct Analytics {
storage: Storage,
}
impl Analytics {
pub fn new(storage: Storage) -> Self {
Self { storage }
}
pub fn count_nodes_by_type(&self) -> SqliteResult<Vec<NodeTypeCount>> {
let mut stmt = self
.storage
.conn()
.prepare("SELECT node_type, COUNT(*) as count FROM intel_nodes GROUP BY node_type")?;
let counts = stmt
.query_map([], |row| {
Ok(NodeTypeCount {
node_type: row.get(0)?,
count: row.get(1)?,
})
})?
.collect::<SqliteResult<Vec<_>>>()?;
Ok(counts)
}
pub fn complexity_distribution(&self) -> SqliteResult<Vec<ComplexityBucket>> {
let mut stmt = self.storage.conn().prepare(
"SELECT
CASE
WHEN complexity < 5 THEN 'simple'
WHEN complexity < 10 THEN 'moderate'
WHEN complexity < 20 THEN 'complex'
ELSE 'very_complex'
END as bucket,
COUNT(*) as count
FROM intel_nodes
GROUP BY bucket
ORDER BY bucket",
)?;
let buckets = stmt
.query_map([], |row| {
Ok(ComplexityBucket {
bucket: row.get(0)?,
count: row.get(1)?,
})
})?
.collect::<SqliteResult<Vec<_>>>()?;
Ok(buckets)
}
pub fn count_edges_by_type(&self) -> SqliteResult<Vec<EdgeTypeCount>> {
let mut stmt = self
.storage
.conn()
.prepare("SELECT edge_type, COUNT(*) as count FROM intel_edges GROUP BY edge_type")?;
let counts = stmt
.query_map([], |row| {
Ok(EdgeTypeCount {
edge_type: row.get(0)?,
count: row.get(1)?,
})
})?
.collect::<SqliteResult<Vec<_>>>()?;
Ok(counts)
}
pub fn get_hotspots(&self, threshold: i32) -> SqliteResult<Vec<Hotspot>> {
let mut stmt = self.storage.conn().prepare(
"SELECT
n.id,
n.symbol_name,
n.file_path,
n.complexity,
COUNT(e.callee_id) as fan_out
FROM intel_nodes n
LEFT JOIN intel_edges e ON n.id = e.caller_id
WHERE n.complexity >= ?1
GROUP BY n.id
HAVING fan_out > ?2
ORDER BY n.complexity DESC, fan_out DESC",
)?;
let hotspots = stmt
.query_map(params![threshold, threshold / 2], |row| {
Ok(Hotspot {
node_id: row.get(0)?,
symbol_name: row.get(1)?,
file_path: row.get(2)?,
complexity: row.get(3)?,
fan_out: row.get(4)?,
})
})?
.collect::<SqliteResult<Vec<_>>>()?;
Ok(hotspots)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeTypeCount {
pub node_type: String,
pub count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplexityBucket {
pub bucket: String,
pub count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EdgeTypeCount {
pub edge_type: String,
pub count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Hotspot {
pub node_id: i64,
pub symbol_name: String,
pub file_path: String,
pub complexity: i32,
pub fan_out: i64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::schema::Storage;
use tempfile::NamedTempFile;
#[test]
fn test_analytics_creation() {
let temp_file = NamedTempFile::new().unwrap();
let storage = Storage::open(temp_file.path()).unwrap();
let analytics = Analytics::new(storage);
let counts = analytics.count_nodes_by_type().unwrap();
assert_eq!(counts.len(), 0);
}
}