do_memory_core/extraction/
utils.rs1use crate::patterns::Pattern;
4use crate::types::TaskContext;
5use chrono::{DateTime, Utc};
6use std::collections::HashSet;
7
8#[must_use]
10pub fn deduplicate_patterns(patterns: Vec<Pattern>) -> Vec<Pattern> {
11 let mut seen = HashSet::new();
12 let mut deduplicated = Vec::new();
13
14 for pattern in patterns {
15 let key = pattern.similarity_key();
16
17 if seen.insert(key) {
18 deduplicated.push(pattern);
19 }
20 }
21
22 deduplicated
23}
24
25#[must_use]
31pub fn rank_patterns(patterns: Vec<Pattern>, context: &TaskContext) -> Vec<Pattern> {
32 if patterns.is_empty() {
33 return patterns;
34 }
35
36 let query_tags: HashSet<_> = context.tags.iter().collect();
37 let now = Utc::now();
38
39 let mut decorated: Vec<(f64, Pattern)> = patterns
41 .into_iter()
42 .map(|p| {
43 let score = calculate_pattern_score(&p, context, &query_tags, now);
44 (score, p)
45 })
46 .collect();
47
48 decorated.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
51
52 decorated.into_iter().map(|(_, p)| p).collect()
54}
55
56fn calculate_pattern_score(
65 pattern: &Pattern,
66 current_context: &TaskContext,
67 query_tags: &HashSet<&String>,
68 now: DateTime<Utc>,
69) -> f64 {
70 let mut score = 0.0;
71
72 score += f64::from(pattern.success_rate()) * 100.0;
74
75 let sample_size = pattern.sample_size() as f64;
77 score += (sample_size.min(10.0) / 10.0) * 50.0;
78
79 if let Some(pattern_context) = pattern.context() {
81 score += calculate_context_similarity(pattern_context, current_context, query_tags) * 100.0;
82 }
83
84 match pattern {
86 Pattern::ToolSequence { tools, .. } => {
87 let mut unique_tools: usize = 0;
90 let mut seen: Vec<&String> = Vec::with_capacity(tools.len());
91 for tool in tools {
92 if !seen.contains(&tool) {
93 unique_tools += 1;
94 seen.push(tool);
95 }
96 }
97 score += (unique_tools as f64 / tools.len() as f64) * 20.0;
98 }
99 Pattern::ErrorRecovery { .. } => {
100 score += 30.0;
102 }
103 Pattern::DecisionPoint { outcome_stats, .. } => {
104 if outcome_stats.total_count > 5 {
106 score += 25.0;
107 }
108 }
109 Pattern::ContextPattern { evidence, .. } => {
110 score += (evidence.len() as f64).min(5.0) * 10.0;
112 }
113 }
114
115 let effectiveness = pattern.effectiveness();
118
119 score += f64::from(effectiveness.effectiveness_score()) * 100.0;
122
123 if effectiveness.times_applied > 0 {
126 let success_rate = effectiveness.application_success_rate();
127 let usage_confidence = (effectiveness.times_applied as f64).ln().min(3.0) / 3.0;
128 score += f64::from(success_rate) * usage_confidence * 50.0;
129 }
130
131 if effectiveness.avg_reward_delta > 0.0 {
134 let capped_delta = effectiveness.avg_reward_delta.min(0.5); score += f64::from(capped_delta) * 100.0; } else if effectiveness.avg_reward_delta < 0.0 {
137 let capped_penalty = effectiveness.avg_reward_delta.max(-0.5); score += f64::from(capped_penalty) * 100.0; }
141
142 if effectiveness.times_applied > 0 {
145 let days_since_use = (now - effectiveness.last_used).num_days();
146 if days_since_use < 30 {
147 score += (30.0 - days_since_use as f64) / 30.0 * 10.0;
148 }
149 }
150
151 score
152}
153
154fn calculate_context_similarity(
158 a: &TaskContext,
159 b: &TaskContext,
160 query_tags: &HashSet<&String>,
161) -> f64 {
162 let mut similarity = 0.0;
163 let mut factors = 0.0;
164
165 if a.language == b.language {
167 similarity += 1.0;
168 }
169 factors += 1.0;
170
171 if a.framework == b.framework {
173 similarity += 1.0;
174 }
175 factors += 1.0;
176
177 if a.domain == b.domain {
179 similarity += 0.8;
180 }
181 factors += 1.0;
182
183 if a.complexity == b.complexity {
185 similarity += 0.6;
186 }
187 factors += 1.0;
188
189 if !a.tags.is_empty() || !query_tags.is_empty() {
193 let mut intersection_count = 0;
194 let mut a_unique_count = 0;
195 let mut seen_in_a = Vec::with_capacity(a.tags.len());
196
197 for tag in &a.tags {
198 if !seen_in_a.contains(&tag) {
199 a_unique_count += 1;
200 seen_in_a.push(tag);
201 if query_tags.contains(tag) {
202 intersection_count += 1;
203 }
204 }
205 }
206
207 let union_count = query_tags.len() + a_unique_count - intersection_count;
208
209 if union_count > 0 {
210 similarity += (intersection_count as f64 / union_count as f64) * 0.7;
211 }
212 factors += 1.0;
213 }
214
215 if factors > 0.0 {
216 similarity / factors
217 } else {
218 0.0
219 }
220}