1use crate::memory::Mem;
6
7#[derive(Debug, PartialEq, Eq)]
9pub enum CurateAction {
10 Delete,
12 Keep,
14 Merge,
16 Promote,
18 Review,
20 Stale,
22 Demote,
24}
25
26#[derive(Debug)]
28pub struct CurateRecommendation {
29 pub action: CurateAction,
30 pub memory_id: String,
31 pub score: f64,
32 pub reason: String,
33 pub automatable: bool,
34}
35
36pub fn calculate_score(mem: &Mem, days_since_update: u32) -> f64 {
41 let confidence_factor = mem.trust.as_ref()
42 .map(|t| t.confidence.factor())
43 .unwrap_or(0.4); let freshness = (1.0 - days_since_update as f64 / 180.0).max(0.0);
46
47 let text_quality = assess_text_quality(&mem.content);
48
49 let usage_bonus = {
50 let loaded = mem.loaded_count as f64;
51 let referenced = mem.referenced_count as f64;
52 (loaded * 0.01 + referenced * 0.02).min(0.5)
53 };
54
55 let dispute_decay = mem.trust.as_ref()
56 .map(|t| t.dispute_decay())
57 .unwrap_or(0.0);
58
59 let level_bonus = match mem.level {
60 crate::memory::MemLevel::Cornerstone => 0.2,
61 crate::memory::MemLevel::Fact => 0.0,
62 crate::memory::MemLevel::Activity => -0.1,
63 };
64
65 mem.weight * confidence_factor * freshness * text_quality * (1.0 + usage_bonus) - dispute_decay + level_bonus
66}
67
68pub fn assess_text_quality(content: &str) -> f64 {
72 let len = content.chars().count();
73
74 let len_score = if len < 20 { 0.2 } else if len >= 100 { 1.0 } else { (len as f64 - 20.0) / 80.0 * 0.8 + 0.2 };
76
77 let tech_keywords = [
79 "类型", "函数", "模块", "架构", "测试", "API", "接口", "协议",
80 "引擎", "算法", "数据", "状态", "搜索", "索引", "配置",
81 "type", "fn", "mod", "struct", "impl", "async", "trait",
82 "test", "api", "http", "json", "sql", "db",
83 ];
84 let tech_count = tech_keywords.iter().filter(|k| content.to_lowercase().contains(&k.to_lowercase())).count();
85 let tech_score = (tech_count as f64 / 5.0).min(1.0);
86
87 let causal_markers = ["→", "->", "因为", "所以", "因此", "问题", "解决", "导致", "原因是", "方案", "because", "therefore", "solution"];
89 let causal_count = causal_markers.iter().filter(|m| content.to_lowercase().contains(&m.to_lowercase())).count();
90 let causal_score = (causal_count as f64 / 3.0).min(1.0);
91
92 let rep_score = if has_repetition(content, 8) { 0.0 } else { 1.0 };
94
95 let filler_ratio = filler_word_ratio(content);
97 let filler_score = 1.0 - filler_ratio.min(1.0);
98
99 let quant_count = content.chars().filter(|c| c.is_ascii_digit()).count();
101 let quant_score = (quant_count as f64 / 10.0).min(1.0);
102
103 len_score * 0.15 + tech_score * 0.20 + causal_score * 0.25 + rep_score * 0.10 + filler_score * 0.15 + quant_score * 0.15
104}
105
106fn has_repetition(content: &str, threshold: usize) -> bool {
108 let chars: Vec<char> = content.chars().collect();
109 if chars.len() < threshold { return false; }
110 for i in 0..=chars.len() - threshold {
111 if chars[i..i + threshold].iter().all(|&c| c == chars[i]) {
112 return true;
113 }
114 }
115 false
116}
117
118fn filler_word_ratio(content: &str) -> f64 {
120 let fillers = ["的", "了", "是", "在", "和", "有", "个", "也", "就", "都", "the", "a", "an", "is", "of", "to", "in", "and", "or", "it"];
121 let chars: Vec<char> = content.chars().collect();
122 if chars.is_empty() { return 0.0; }
123 let filler_chars: std::collections::HashSet<char> = fillers.iter()
125 .flat_map(|s| s.chars())
126 .collect();
127 let filler_count = chars.iter().filter(|c| filler_chars.contains(c)).count();
128 filler_count as f64 / chars.len() as f64
129}
130
131pub fn determine_action(mem: &Mem, score: f64) -> CurateRecommendation {
133 let is_cornerstone = mem.level == crate::memory::MemLevel::Cornerstone;
134 let is_activity = mem.level == crate::memory::MemLevel::Activity;
135 let has_disputes = mem.trust.as_ref()
136 .map(|t| t.disputes.as_ref().map(|d| !d.is_empty()).unwrap_or(false))
137 .unwrap_or(false);
138
139 let (action, reason, automatable) = if score < 0.15 && is_activity && !is_cornerstone {
140 (CurateAction::Delete, "极低评分 + 临时活动记忆".into(), true)
141 } else if is_cornerstone && has_disputes {
142 (CurateAction::Demote, "基石记忆存在未解决的争议".into(), false)
143 } else if is_cornerstone && score < 0.3 {
144 (CurateAction::Stale, "基石记忆长期未更新,评分过低".into(), false)
145 } else if score > 0.6 && mem.weight >= 0.7 && !has_disputes && !is_cornerstone {
146 (CurateAction::Promote, "高质量记忆,建议升级级别".into(), false)
147 } else if score < 0.3 && !is_activity {
148 (CurateAction::Review, "评分偏低,需要人工审核".into(), false)
149 } else {
150 (CurateAction::Keep, "评分正常,保留".into(), true)
151 };
152
153 CurateRecommendation {
154 action,
155 memory_id: mem.id.clone(),
156 score,
157 reason,
158 automatable,
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::memory::{Mem, MemLevel, MemoryZone};
166 use crate::trust::Confidence;
167
168 fn make_cur_mem(id: &str, content: &str, level: MemLevel, weight: f64, confidence: Confidence, loaded: u32, referenced: u32, disputes: u32) -> Mem {
169 let trust = {
170 let mut td = crate::trust::TrustData::new(confidence, "test");
171 if disputes > 0 {
172 let d: Vec<crate::trust::Dispute> = (0..disputes).map(|i| crate::trust::Dispute {
173 id: format!("d{}", i),
174 reason: "测试争议".into(),
175 disputed_at: "2026-01-01T00:00:00Z".into(),
176 resolved_at: None,
177 resolution: None,
178 }).collect();
179 td.disputes = Some(d);
180 }
181 Some(td)
182 };
183
184 Mem {
185 id: id.into(),
186 level,
187 zone: MemoryZone::General,
188 content: content.into(),
189 weight,
190 tags: vec![],
191 source: None, metadata: None,
192 last_matched: None, match_count: 0,
193 loaded_count: loaded, referenced_count: referenced,
194 last_loaded: None, last_referenced: None,
195 supersedes: None, superseded_at: None, superseded_by: None,
196 trust,
197 created_at: "2026-01-01T00:00:00Z".into(),
198 updated_at: "2026-01-01T00:00:00Z".into(),
199 }
200 }
201
202 #[test]
203 fn test_high_quality_gets_promote() {
204 let mem = make_cur_mem("m1", "基石原则:最少依赖 — 新增依赖前思考是否真的需要。核心模块架构设计应遵循类型系统约束,API 接口需要完整的测试覆盖。问题解决方案:优先使用标准库函数实现,避免引入外部依赖。根据 85% 测试覆盖率目标验证模块正确性。", MemLevel::Fact, 0.85, Confidence::High, 50, 30, 0);
205 let score = calculate_score(&mem, 5);
206 let rec = determine_action(&mem, score);
207 assert!(matches!(rec.action, CurateAction::Promote), "action = {:?}, score = {}", rec.action, score);
208 }
209
210 #[test]
211 fn test_low_score_activity_gets_delete() {
212 let mem = make_cur_mem("m1", "test", MemLevel::Activity, 0.1, Confidence::Speculative, 0, 0, 0);
213 let score = calculate_score(&mem, 90);
214 let rec = determine_action(&mem, score);
215 assert!(matches!(rec.action, CurateAction::Delete), "action = {:?}, score = {}", rec.action, score);
216 assert!(rec.automatable);
217 }
218
219 #[test]
220 fn test_cornerstone_with_disputes_gets_demote() {
221 let mem = make_cur_mem("cs1", "基石原则", MemLevel::Cornerstone, 0.9, Confidence::Certain, 100, 50, 2);
222 let score = calculate_score(&mem, 10);
223 let rec = determine_action(&mem, score);
224 assert!(matches!(rec.action, CurateAction::Demote), "action = {:?}, score = {}", rec.action, score);
225 assert!(!rec.automatable);
226 }
227
228 #[test]
229 fn test_text_quality_range() {
230 let high = assess_text_quality("基石原则:最少依赖 — 新增依赖前思考是否真的需要。类型系统架构设计需要完整的模块 API 接口测试,解决方案应优先使用标准库函数。目标:85% 测试覆盖率,确保代码质量达到行业标准。");
231 let low = assess_text_quality("test");
232 assert!(high > low, "high={}, low={}", high, low);
233 assert!(high > 0.5);
234 assert!(low < 0.5);
235 }
236
237 #[test]
238 fn test_freshness_decay() {
239 let mem = make_cur_mem("m1", "测试记忆 — 包含技术关键词 TypeScript 类型系统 架构设计", MemLevel::Fact, 0.5, Confidence::Medium, 0, 0, 0);
240 let score_recent = calculate_score(&mem, 1);
241 let score_old = calculate_score(&mem, 200);
242 assert!(score_recent > score_old, "recent={}, old={}", score_recent, score_old);
243 }
244}