use crate::episode::{Episode, PatternId};
use crate::extraction::extractor::PatternExtractor;
use crate::pattern::Pattern;
use crate::types::OutcomeStats;
pub fn extract_tool_sequence(extractor: &PatternExtractor, episode: &Episode) -> Option<Pattern> {
if episode.steps.is_empty() {
return None;
}
let success_rate = PatternExtractor::calculate_step_success_rate(episode);
if success_rate < extractor.success_threshold {
return None;
}
let tools: Vec<String> = episode
.steps
.iter()
.take(extractor.max_sequence_len)
.map(|step| step.tool.clone())
.collect();
Some(Pattern::ToolSequence {
id: PatternId::new_v4(),
tools,
context: episode.context.clone(),
success_rate,
avg_latency: PatternExtractor::calculate_average_latency(episode),
occurrence_count: 1,
effectiveness: crate::pattern::PatternEffectiveness::new(),
})
}
pub fn extract_decision_points(extractor: &PatternExtractor, episode: &Episode) -> Vec<Pattern> {
let mut patterns = Vec::new();
let success_rate = PatternExtractor::calculate_step_success_rate(episode);
if success_rate < extractor.success_threshold {
return patterns;
}
for step in &episode.steps {
let action_lower = step.action.to_lowercase();
if action_lower.contains("check")
|| action_lower.contains("verify")
|| action_lower.contains("validate")
|| action_lower.contains("is")
|| action_lower.contains("has")
{
let outcome_stats = OutcomeStats {
success_count: 1,
failure_count: 0,
total_count: 1,
avg_duration_secs: step.latency_ms as f32 / 1000.0,
};
patterns.push(Pattern::DecisionPoint {
id: PatternId::new_v4(),
condition: step.action.clone(),
action: step.tool.clone(),
outcome_stats,
context: episode.context.clone(),
effectiveness: crate::pattern::PatternEffectiveness::new(),
});
}
}
patterns
}
pub fn extract_error_recovery(_extractor: &PatternExtractor, episode: &Episode) -> Option<Pattern> {
use crate::types::ExecutionResult;
if episode.steps.len() < 2 {
return None;
}
let mut error_type = None;
let mut recovery_steps = Vec::new();
for i in 0..episode.steps.len().saturating_sub(1) {
let current = &episode.steps[i];
let next = &episode.steps[i + 1];
if !current.is_success() && next.is_success() {
if error_type.is_none() {
error_type = Some(
if let Some(ExecutionResult::Error { message }) = ¤t.result {
message.clone()
} else {
"Unknown error".to_string()
},
);
}
recovery_steps.push(format!("{}: {}", next.tool, next.action));
}
}
if error_type.is_none() || recovery_steps.is_empty() {
return None;
}
let success_rate = PatternExtractor::calculate_step_success_rate(episode);
if success_rate < 0.3 {
return None;
}
Some(Pattern::ErrorRecovery {
id: PatternId::new_v4(),
error_type: error_type.expect("error_type is guaranteed by preceding check"),
recovery_steps,
context: episode.context.clone(),
success_rate,
effectiveness: crate::pattern::PatternEffectiveness::new(),
})
}
pub fn extract_context_pattern(extractor: &PatternExtractor, episode: &Episode) -> Option<Pattern> {
let success_rate = if episode.steps.is_empty() {
if episode.is_complete() && episode.reward.as_ref().is_some_and(|r| r.total > 0.0) {
1.0
} else {
return None;
}
} else {
PatternExtractor::calculate_step_success_rate(episode)
};
if success_rate < extractor.success_threshold {
return None;
}
let mut context_features = Vec::new();
if let Some(ref lang) = episode.context.language {
context_features.push(format!("language:{lang}"));
}
if let Some(ref framework) = episode.context.framework {
context_features.push(format!("framework:{framework}"));
}
context_features.push(format!("domain:{}", episode.context.domain));
context_features.push(format!("complexity:{:?}", episode.context.complexity));
let recommended_approach = format!("{:?}", episode.task_type);
Some(Pattern::ContextPattern {
id: PatternId::new_v4(),
context_features,
recommended_approach,
evidence: vec![episode.episode_id],
success_rate,
effectiveness: crate::pattern::PatternEffectiveness::new(),
})
}