use crate::patterns::Pattern;
use crate::types::TaskContext;
use chrono::{DateTime, Utc};
use std::collections::HashSet;
#[must_use]
pub fn deduplicate_patterns(patterns: Vec<Pattern>) -> Vec<Pattern> {
let mut seen = HashSet::new();
let mut deduplicated = Vec::new();
for pattern in patterns {
let key = pattern.similarity_key();
if seen.insert(key) {
deduplicated.push(pattern);
}
}
deduplicated
}
#[must_use]
pub fn rank_patterns(patterns: Vec<Pattern>, context: &TaskContext) -> Vec<Pattern> {
if patterns.is_empty() {
return patterns;
}
let query_tags: HashSet<_> = context.tags.iter().collect();
let now = Utc::now();
let mut decorated: Vec<(f64, Pattern)> = patterns
.into_iter()
.map(|p| {
let score = calculate_pattern_score(&p, context, &query_tags, now);
(score, p)
})
.collect();
decorated.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
decorated.into_iter().map(|(_, p)| p).collect()
}
fn calculate_pattern_score(
pattern: &Pattern,
current_context: &TaskContext,
query_tags: &HashSet<&String>,
now: DateTime<Utc>,
) -> f64 {
let mut score = 0.0;
score += f64::from(pattern.success_rate()) * 100.0;
let sample_size = pattern.sample_size() as f64;
score += (sample_size.min(10.0) / 10.0) * 50.0;
if let Some(pattern_context) = pattern.context() {
score += calculate_context_similarity(pattern_context, current_context, query_tags) * 100.0;
}
match pattern {
Pattern::ToolSequence { tools, .. } => {
let mut unique_tools: usize = 0;
let mut seen: Vec<&String> = Vec::with_capacity(tools.len());
for tool in tools {
if !seen.contains(&tool) {
unique_tools += 1;
seen.push(tool);
}
}
score += (unique_tools as f64 / tools.len() as f64) * 20.0;
}
Pattern::ErrorRecovery { .. } => {
score += 30.0;
}
Pattern::DecisionPoint { outcome_stats, .. } => {
if outcome_stats.total_count > 5 {
score += 25.0;
}
}
Pattern::ContextPattern { evidence, .. } => {
score += (evidence.len() as f64).min(5.0) * 10.0;
}
}
let effectiveness = pattern.effectiveness();
score += f64::from(effectiveness.effectiveness_score()) * 100.0;
if effectiveness.times_applied > 0 {
let success_rate = effectiveness.application_success_rate();
let usage_confidence = (effectiveness.times_applied as f64).ln().min(3.0) / 3.0;
score += f64::from(success_rate) * usage_confidence * 50.0;
}
if effectiveness.avg_reward_delta > 0.0 {
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 {
let capped_penalty = effectiveness.avg_reward_delta.max(-0.5); score += f64::from(capped_penalty) * 100.0; }
if effectiveness.times_applied > 0 {
let days_since_use = (now - effectiveness.last_used).num_days();
if days_since_use < 30 {
score += (30.0 - days_since_use as f64) / 30.0 * 10.0;
}
}
score
}
fn calculate_context_similarity(
a: &TaskContext,
b: &TaskContext,
query_tags: &HashSet<&String>,
) -> f64 {
let mut similarity = 0.0;
let mut factors = 0.0;
if a.language == b.language {
similarity += 1.0;
}
factors += 1.0;
if a.framework == b.framework {
similarity += 1.0;
}
factors += 1.0;
if a.domain == b.domain {
similarity += 0.8;
}
factors += 1.0;
if a.complexity == b.complexity {
similarity += 0.6;
}
factors += 1.0;
if !a.tags.is_empty() || !query_tags.is_empty() {
let mut intersection_count = 0;
let mut a_unique_count = 0;
let mut seen_in_a = Vec::with_capacity(a.tags.len());
for tag in &a.tags {
if !seen_in_a.contains(&tag) {
a_unique_count += 1;
seen_in_a.push(tag);
if query_tags.contains(tag) {
intersection_count += 1;
}
}
}
let union_count = query_tags.len() + a_unique_count - intersection_count;
if union_count > 0 {
similarity += (intersection_count as f64 / union_count as f64) * 0.7;
}
factors += 1.0;
}
if factors > 0.0 {
similarity / factors
} else {
0.0
}
}