codemem_engine/
scoring.rs1use crate::bm25;
4use chrono::{DateTime, Utc};
5use codemem_core::{MemoryNode, ScoreBreakdown};
6use codemem_storage::graph::GraphEngine;
7
8pub fn graph_strength_for_memory(graph: &GraphEngine, memory_id: &str) -> f64 {
14 let metrics = match graph.raw_graph_metrics_for_memory(memory_id) {
15 Some(m) => m,
16 None => return 0.0,
17 };
18
19 if metrics.code_neighbor_count == 0 {
20 return 0.0;
21 }
22
23 let connectivity_bonus = (metrics.code_neighbor_count as f64 / 5.0).min(1.0);
24 let edge_weight_bonus =
25 (metrics.total_edge_weight / metrics.code_neighbor_count as f64).min(1.0);
26
27 (0.4 * metrics.max_pagerank
28 + 0.3 * metrics.max_betweenness
29 + 0.2 * connectivity_bonus
30 + 0.1 * edge_weight_bonus)
31 .min(1.0)
32}
33
34pub fn truncate_content(s: &str, max: usize) -> String {
37 if s.len() <= max {
38 s.to_string()
39 } else {
40 let mut end = max;
41 while end > 0 && !s.is_char_boundary(end) {
42 end -= 1;
43 }
44 format!("{}...", &s[..end])
45 }
46}
47
48pub fn compute_score(
54 memory: &MemoryNode,
55 query_tokens: &[&str],
56 vector_similarity: f64,
57 graph: &GraphEngine,
58 bm25: &bm25::Bm25Index,
59 now: DateTime<Utc>,
60) -> ScoreBreakdown {
61 let token_overlap = if query_tokens.is_empty() {
64 0.0
65 } else {
66 let indexed_score = bm25.score_with_tokens_str(query_tokens, &memory.id);
69 if indexed_score > 0.0 {
70 indexed_score
71 } else {
72 bm25.score_text_with_tokens_str(query_tokens, &memory.content)
73 }
74 };
75
76 let age_hours = (now - memory.updated_at).num_hours().max(0) as f64;
78 let temporal = (-age_hours / (30.0 * 24.0)).exp();
79
80 let tag_matching = if !query_tokens.is_empty() {
84 let tag_str: String = memory.tags.join(" ").to_lowercase();
85 let matches = query_tokens
86 .iter()
87 .filter(|qt| tag_str.contains(**qt))
88 .count();
89 matches as f64 / query_tokens.len() as f64
90 } else {
91 0.0
92 };
93
94 let access_hours = (now - memory.last_accessed_at).num_hours().max(0) as f64;
96 let recency = (-access_hours / (7.0 * 24.0)).exp();
97
98 let graph_strength = graph_strength_for_memory(graph, &memory.id);
103
104 ScoreBreakdown {
105 vector_similarity,
106 graph_strength,
107 token_overlap,
108 temporal,
109 tag_matching,
110 importance: memory.importance,
111 confidence: memory.confidence,
112 recency,
113 }
114}
115
116#[cfg(test)]
117#[path = "tests/scoring_tests.rs"]
118mod tests;