use super::super::policy::EvictionPolicy;
use super::scoring::calculate_relevance_score;
use crate::episode::Episode;
use uuid::Uuid;
#[must_use]
pub fn evict_if_needed(
episodes: &[Episode],
max_episodes: usize,
policy: EvictionPolicy,
) -> Vec<Uuid> {
let current_count = episodes.len();
if current_count < max_episodes {
return Vec::new();
}
let eviction_count = (current_count - max_episodes) + 1;
match policy {
EvictionPolicy::LRU => evict_lru(episodes, eviction_count),
EvictionPolicy::RelevanceWeighted => evict_relevance_weighted(episodes, eviction_count),
}
}
fn evict_lru(episodes: &[Episode], count: usize) -> Vec<Uuid> {
let mut episodes_with_time: Vec<_> = episodes
.iter()
.map(|e| {
let time = e.end_time.unwrap_or(e.start_time);
(e.episode_id, time)
})
.collect();
episodes_with_time.sort_by_key(|a| a.1);
episodes_with_time
.iter()
.take(count)
.map(|(id, _)| *id)
.collect()
}
fn evict_relevance_weighted(episodes: &[Episode], count: usize) -> Vec<Uuid> {
let mut episodes_with_scores: Vec<_> = episodes
.iter()
.map(|e| {
let score = calculate_relevance_score(e);
(e.episode_id, score)
})
.collect();
episodes_with_scores.sort_by(|a, b| a.1.total_cmp(&b.1));
episodes_with_scores
.iter()
.take(count)
.map(|(id, _)| *id)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Episode;
use crate::types::{ComplexityLevel, RewardScore, TaskContext, TaskType};
fn create_test_episode(task_desc: &str) -> Episode {
let context = TaskContext {
language: Some("rust".to_string()),
framework: None,
complexity: ComplexityLevel::Moderate,
domain: "testing".to_string(),
tags: vec![],
};
Episode::new(task_desc.to_string(), context, TaskType::Testing)
}
#[test]
fn test_evict_if_needed_under_capacity() {
let episodes: Vec<Episode> = (0..5)
.map(|i| create_test_episode(&format!("Task {i}")))
.collect();
let to_evict = evict_if_needed(&episodes, 10, EvictionPolicy::LRU);
assert!(to_evict.is_empty());
}
#[test]
fn test_evict_if_needed_at_capacity() {
let episodes: Vec<Episode> = (0..3)
.map(|i| create_test_episode(&format!("Task {i}")))
.collect();
let to_evict = evict_if_needed(&episodes, 3, EvictionPolicy::LRU);
assert_eq!(to_evict.len(), 1);
}
#[test]
fn test_evict_if_needed_over_capacity() {
let episodes: Vec<Episode> = (0..5)
.map(|i| create_test_episode(&format!("Task {i}")))
.collect();
let to_evict = evict_if_needed(&episodes, 3, EvictionPolicy::LRU);
assert_eq!(to_evict.len(), 3);
}
#[test]
fn test_lru_eviction() {
use std::thread;
use std::time::Duration;
let mut episodes = vec![create_test_episode("Old task")];
thread::sleep(Duration::from_millis(10));
episodes.push(create_test_episode("Middle task"));
thread::sleep(Duration::from_millis(10));
episodes.push(create_test_episode("New task"));
let to_evict = evict_if_needed(&episodes, 2, EvictionPolicy::LRU);
assert_eq!(to_evict.len(), 2);
assert!(to_evict.contains(&episodes[0].episode_id));
assert!(to_evict.contains(&episodes[1].episode_id));
assert!(!to_evict.contains(&episodes[2].episode_id));
}
#[test]
fn test_relevance_weighted_eviction() {
let mut low_quality = create_test_episode("Low quality");
low_quality.reward = Some(RewardScore {
total: 0.2,
base: 0.2,
efficiency: 1.0,
complexity_bonus: 1.0,
quality_multiplier: 1.0,
learning_bonus: 0.0,
});
let mut medium_quality = create_test_episode("Medium quality");
medium_quality.reward = Some(RewardScore {
total: 1.0,
base: 1.0,
efficiency: 1.0,
complexity_bonus: 1.0,
quality_multiplier: 1.0,
learning_bonus: 0.0,
});
let mut high_quality = create_test_episode("High quality");
high_quality.reward = Some(RewardScore {
total: 1.8,
base: 1.0,
efficiency: 1.5,
complexity_bonus: 1.2,
quality_multiplier: 1.0,
learning_bonus: 0.0,
});
let episodes = vec![
low_quality.clone(),
medium_quality.clone(),
high_quality.clone(),
];
let to_evict = evict_if_needed(&episodes, 2, EvictionPolicy::RelevanceWeighted);
assert_eq!(to_evict.len(), 2);
assert!(to_evict.contains(&low_quality.episode_id));
assert!(to_evict.contains(&medium_quality.episode_id));
assert!(!to_evict.contains(&high_quality.episode_id));
}
#[test]
fn test_zero_capacity() {
let episodes = vec![create_test_episode("Task 1")];
let to_evict = evict_if_needed(&episodes, 0, EvictionPolicy::LRU);
assert_eq!(to_evict.len(), 1); }
#[test]
fn test_single_episode_capacity() {
let episodes = vec![create_test_episode("Task 1")];
let to_evict = evict_if_needed(&episodes, 1, EvictionPolicy::LRU);
assert_eq!(to_evict.len(), 1); }
#[test]
fn test_exactly_at_capacity_needs_eviction() {
let episodes: Vec<Episode> = (0..5)
.map(|i| create_test_episode(&format!("Task {i}")))
.collect();
let to_evict = evict_if_needed(&episodes, 5, EvictionPolicy::LRU);
assert_eq!(to_evict.len(), 1);
}
}