Cluster similar prompts together using fuzzy matching Takes (prompt, timestamp) pairs
(&self, prompts: Vec<(String, i64)>)
| 25 | /// Cluster similar prompts together using fuzzy matching |
| 26 | /// Takes (prompt, timestamp) pairs |
| 27 | pub fn cluster(&self, prompts: Vec<(String, i64)>) -> Vec<PromptCluster> { |
| 28 | // Count occurrences and track latest timestamp |
| 29 | let mut counts: HashMap<String, (usize, i64)> = HashMap::new(); |
| 30 | for (prompt, timestamp) in &prompts { |
| 31 | let normalized = self.normalize(prompt); |
| 32 | if !normalized.is_empty() && normalized.len() >= self.min_length { |
| 33 | let entry = counts.entry(normalized).or_insert((0, 0)); |
| 34 | entry.0 += 1; |
| 35 | if *timestamp > entry.1 { |
| 36 | entry.1 = *timestamp; |
| 37 | } |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | // Sort by count descending for clustering |
| 42 | let mut items: Vec<(String, usize, i64)> = counts |
| 43 | .into_iter() |
| 44 | .map(|(k, (count, ts))| (k, count, ts)) |
| 45 | .collect(); |
| 46 | items.sort_by_key(|item| std::cmp::Reverse(item.1)); |
| 47 | |
| 48 | // Cluster similar items |
| 49 | let mut clusters: Vec<PromptCluster> = Vec::new(); |
| 50 | |
| 51 | for (prompt, count, timestamp) in items { |
| 52 | let mut found_cluster = false; |
| 53 | |
| 54 | for cluster in &mut clusters { |
| 55 | if self.is_similar(&prompt, &cluster.canonical) { |
| 56 | cluster.variants.push(prompt.clone()); |
| 57 | cluster.count += count; |
| 58 | if timestamp > cluster.latest_timestamp { |
| 59 | cluster.latest_timestamp = timestamp; |
| 60 | } |
| 61 | found_cluster = true; |
| 62 | break; |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | if !found_cluster { |
| 67 | clusters.push(PromptCluster { |
| 68 | canonical: prompt.clone(), |
| 69 | variants: vec![prompt], |
| 70 | count, |
| 71 | latest_timestamp: timestamp, |
| 72 | }); |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | clusters |
| 77 | } |
| 78 | |
| 79 | /// Sort clusters by count (default) |
| 80 | pub fn sort_by_count(clusters: &mut [PromptCluster]) { |
no test coverage detected