use crate::episode::Episode;
use crate::pattern::Pattern;
use chrono::Duration;
use tracing::{debug, instrument};
use super::extractors::{
extract_context_pattern, extract_decision_points, extract_error_recovery, extract_tool_sequence,
};
use super::{MAX_SEQUENCE_LENGTH, MIN_PATTERN_SUCCESS_RATE, MIN_SEQUENCE_LENGTH};
#[derive(Clone)]
pub struct PatternExtractor {
pub(crate) success_threshold: f32,
#[allow(dead_code)] pub(crate) min_sequence_len: usize,
pub(crate) max_sequence_len: usize,
}
impl Default for PatternExtractor {
fn default() -> Self {
Self::new()
}
}
impl PatternExtractor {
#[must_use]
pub fn new() -> Self {
Self {
success_threshold: MIN_PATTERN_SUCCESS_RATE,
min_sequence_len: MIN_SEQUENCE_LENGTH,
max_sequence_len: MAX_SEQUENCE_LENGTH,
}
}
#[must_use]
pub fn with_thresholds(
success_threshold: f32,
min_sequence_len: usize,
max_sequence_len: usize,
) -> Self {
Self {
success_threshold,
min_sequence_len,
max_sequence_len,
}
}
#[instrument(skip(self, episode), fields(episode_id = %episode.episode_id))]
pub fn extract(&self, episode: &Episode) -> Vec<Pattern> {
let mut patterns = Vec::new();
if !episode.is_complete() {
return patterns;
}
if let Some(tool_seq) = extract_tool_sequence(self, episode) {
patterns.push(tool_seq);
}
patterns.extend(extract_decision_points(self, episode));
if let Some(error_recovery) = extract_error_recovery(self, episode) {
patterns.push(error_recovery);
}
if let Some(context_pattern) = extract_context_pattern(self, episode) {
patterns.push(context_pattern);
}
debug!(
pattern_count = patterns.len(),
"Extracted patterns from episode"
);
patterns
}
#[allow(clippy::cast_precision_loss)]
pub(crate) fn calculate_step_success_rate(episode: &Episode) -> f32 {
if episode.steps.is_empty() {
return 0.0;
}
let successful = episode.successful_steps_count();
successful as f32 / episode.steps.len() as f32
}
#[allow(clippy::cast_precision_loss, clippy::cast_possible_wrap)]
pub(crate) fn calculate_average_latency(episode: &Episode) -> Duration {
if episode.steps.is_empty() {
return Duration::zero();
}
let total_ms: u64 = episode.steps.iter().map(|s| s.latency_ms).sum();
let avg_ms = total_ms / episode.steps.len() as u64;
Duration::milliseconds(avg_ms as i64)
}
}