use crate::memory::Mem;
#[derive(Debug, PartialEq, Eq)]
pub enum CurateAction {
Delete,
Keep,
Merge,
Promote,
Review,
Stale,
Demote,
}
#[derive(Debug)]
pub struct CurateRecommendation {
pub action: CurateAction,
pub memory_id: String,
pub score: f64,
pub reason: String,
pub automatable: bool,
}
pub fn calculate_score(mem: &Mem, days_since_update: u32) -> f64 {
let confidence_factor = mem.trust.as_ref()
.map(|t| t.confidence.factor())
.unwrap_or(0.4);
let freshness = (1.0 - days_since_update as f64 / 180.0).max(0.0);
let text_quality = assess_text_quality(&mem.content);
let usage_bonus = {
let loaded = mem.loaded_count as f64;
let referenced = mem.referenced_count as f64;
(loaded * 0.01 + referenced * 0.02).min(0.5)
};
let dispute_decay = mem.trust.as_ref()
.map(|t| t.dispute_decay())
.unwrap_or(0.0);
let level_bonus = match mem.level {
crate::memory::MemLevel::Cornerstone => 0.2,
crate::memory::MemLevel::Fact => 0.0,
crate::memory::MemLevel::Activity => -0.1,
};
mem.weight * confidence_factor * freshness * text_quality * (1.0 + usage_bonus) - dispute_decay + level_bonus
}
pub fn assess_text_quality(content: &str) -> f64 {
let len = content.chars().count();
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 };
let tech_keywords = [
"类型", "函数", "模块", "架构", "测试", "API", "接口", "协议",
"引擎", "算法", "数据", "状态", "搜索", "索引", "配置",
"type", "fn", "mod", "struct", "impl", "async", "trait",
"test", "api", "http", "json", "sql", "db",
];
let tech_count = tech_keywords.iter().filter(|k| content.to_lowercase().contains(&k.to_lowercase())).count();
let tech_score = (tech_count as f64 / 5.0).min(1.0);
let causal_markers = ["→", "->", "因为", "所以", "因此", "问题", "解决", "导致", "原因是", "方案", "because", "therefore", "solution"];
let causal_count = causal_markers.iter().filter(|m| content.to_lowercase().contains(&m.to_lowercase())).count();
let causal_score = (causal_count as f64 / 3.0).min(1.0);
let rep_score = if has_repetition(content, 8) { 0.0 } else { 1.0 };
let filler_ratio = filler_word_ratio(content);
let filler_score = 1.0 - filler_ratio.min(1.0);
let quant_count = content.chars().filter(|c| c.is_ascii_digit()).count();
let quant_score = (quant_count as f64 / 10.0).min(1.0);
len_score * 0.15 + tech_score * 0.20 + causal_score * 0.25 + rep_score * 0.10 + filler_score * 0.15 + quant_score * 0.15
}
fn has_repetition(content: &str, threshold: usize) -> bool {
let chars: Vec<char> = content.chars().collect();
if chars.len() < threshold { return false; }
for i in 0..=chars.len() - threshold {
if chars[i..i + threshold].iter().all(|&c| c == chars[i]) {
return true;
}
}
false
}
fn filler_word_ratio(content: &str) -> f64 {
let fillers = ["的", "了", "是", "在", "和", "有", "个", "也", "就", "都", "the", "a", "an", "is", "of", "to", "in", "and", "or", "it"];
let chars: Vec<char> = content.chars().collect();
if chars.is_empty() { return 0.0; }
let filler_chars: std::collections::HashSet<char> = fillers.iter()
.flat_map(|s| s.chars())
.collect();
let filler_count = chars.iter().filter(|c| filler_chars.contains(c)).count();
filler_count as f64 / chars.len() as f64
}
pub fn determine_action(mem: &Mem, score: f64) -> CurateRecommendation {
let is_cornerstone = mem.level == crate::memory::MemLevel::Cornerstone;
let is_activity = mem.level == crate::memory::MemLevel::Activity;
let has_disputes = mem.trust.as_ref()
.map(|t| t.disputes.as_ref().map(|d| !d.is_empty()).unwrap_or(false))
.unwrap_or(false);
let (action, reason, automatable) = if score < 0.15 && is_activity && !is_cornerstone {
(CurateAction::Delete, "极低评分 + 临时活动记忆".into(), true)
} else if is_cornerstone && has_disputes {
(CurateAction::Demote, "基石记忆存在未解决的争议".into(), false)
} else if is_cornerstone && score < 0.3 {
(CurateAction::Stale, "基石记忆长期未更新,评分过低".into(), false)
} else if score > 0.6 && mem.weight >= 0.7 && !has_disputes && !is_cornerstone {
(CurateAction::Promote, "高质量记忆,建议升级级别".into(), false)
} else if score < 0.3 && !is_activity {
(CurateAction::Review, "评分偏低,需要人工审核".into(), false)
} else {
(CurateAction::Keep, "评分正常,保留".into(), true)
};
CurateRecommendation {
action,
memory_id: mem.id.clone(),
score,
reason,
automatable,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory::{Mem, MemLevel, MemoryZone};
use crate::trust::Confidence;
fn make_cur_mem(id: &str, content: &str, level: MemLevel, weight: f64, confidence: Confidence, loaded: u32, referenced: u32, disputes: u32) -> Mem {
let trust = {
let mut td = crate::trust::TrustData::new(confidence, "test");
if disputes > 0 {
let d: Vec<crate::trust::Dispute> = (0..disputes).map(|i| crate::trust::Dispute {
id: format!("d{}", i),
reason: "测试争议".into(),
disputed_at: "2026-01-01T00:00:00Z".into(),
resolved_at: None,
resolution: None,
}).collect();
td.disputes = Some(d);
}
Some(td)
};
Mem {
id: id.into(),
level,
zone: MemoryZone::General,
content: content.into(),
weight,
tags: vec![],
source: None, metadata: None,
last_matched: None, match_count: 0,
loaded_count: loaded, referenced_count: referenced,
last_loaded: None, last_referenced: None,
supersedes: None, superseded_at: None, superseded_by: None,
trust,
created_at: "2026-01-01T00:00:00Z".into(),
updated_at: "2026-01-01T00:00:00Z".into(),
}
}
#[test]
fn test_high_quality_gets_promote() {
let mem = make_cur_mem("m1", "基石原则:最少依赖 — 新增依赖前思考是否真的需要。核心模块架构设计应遵循类型系统约束,API 接口需要完整的测试覆盖。问题解决方案:优先使用标准库函数实现,避免引入外部依赖。根据 85% 测试覆盖率目标验证模块正确性。", MemLevel::Fact, 0.85, Confidence::High, 50, 30, 0);
let score = calculate_score(&mem, 5);
let rec = determine_action(&mem, score);
assert!(matches!(rec.action, CurateAction::Promote), "action = {:?}, score = {}", rec.action, score);
}
#[test]
fn test_low_score_activity_gets_delete() {
let mem = make_cur_mem("m1", "test", MemLevel::Activity, 0.1, Confidence::Speculative, 0, 0, 0);
let score = calculate_score(&mem, 90);
let rec = determine_action(&mem, score);
assert!(matches!(rec.action, CurateAction::Delete), "action = {:?}, score = {}", rec.action, score);
assert!(rec.automatable);
}
#[test]
fn test_cornerstone_with_disputes_gets_demote() {
let mem = make_cur_mem("cs1", "基石原则", MemLevel::Cornerstone, 0.9, Confidence::Certain, 100, 50, 2);
let score = calculate_score(&mem, 10);
let rec = determine_action(&mem, score);
assert!(matches!(rec.action, CurateAction::Demote), "action = {:?}, score = {}", rec.action, score);
assert!(!rec.automatable);
}
#[test]
fn test_text_quality_range() {
let high = assess_text_quality("基石原则:最少依赖 — 新增依赖前思考是否真的需要。类型系统架构设计需要完整的模块 API 接口测试,解决方案应优先使用标准库函数。目标:85% 测试覆盖率,确保代码质量达到行业标准。");
let low = assess_text_quality("test");
assert!(high > low, "high={}, low={}", high, low);
assert!(high > 0.5);
assert!(low < 0.5);
}
#[test]
fn test_freshness_decay() {
let mem = make_cur_mem("m1", "测试记忆 — 包含技术关键词 TypeScript 类型系统 架构设计", MemLevel::Fact, 0.5, Confidence::Medium, 0, 0, 0);
let score_recent = calculate_score(&mem, 1);
let score_old = calculate_score(&mem, 200);
assert!(score_recent > score_old, "recent={}, old={}", score_recent, score_old);
}
}