use crate::episode::Episode;
#[must_use]
pub fn calculate_relevance_score(episode: &Episode) -> f32 {
let quality_score = extract_quality_score(episode);
let recency_score = calculate_recency_score(episode);
(quality_score * 0.7) + (recency_score * 0.3)
}
#[must_use]
pub fn extract_quality_score(episode: &Episode) -> f32 {
if let Some(ref salient) = episode.salient_features {
let feature_count = salient.count();
if feature_count > 0 {
return (feature_count as f32 / 10.0).min(1.0);
}
}
if let Some(ref reward) = episode.reward {
return (reward.total / 2.0).clamp(0.0, 1.0);
}
0.5
}
#[must_use]
pub fn calculate_recency_score(episode: &Episode) -> f32 {
use chrono::Utc;
let now = Utc::now();
let episode_time = episode.end_time.unwrap_or(episode.start_time);
let age_duration = now.signed_duration_since(episode_time);
let age_hours = age_duration.num_hours() as f32;
let decay_factor = 24.0; let score = (-age_hours / decay_factor).exp();
score.clamp(0.0, 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Episode;
use crate::types::{ComplexityLevel, RewardScore, TaskContext, TaskOutcome, 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_relevance_score_calculation() {
let mut episode = create_test_episode("Test task");
episode.reward = Some(RewardScore {
total: 1.0,
base: 1.0,
efficiency: 1.0,
complexity_bonus: 1.0,
quality_multiplier: 1.0,
learning_bonus: 0.0,
});
episode.complete(TaskOutcome::Success {
verdict: "Done".to_string(),
artifacts: vec![],
});
let score = calculate_relevance_score(&episode);
assert!((0.0..=1.0).contains(&score));
}
#[test]
fn test_recency_score_new_episode() {
let mut episode = create_test_episode("New task");
episode.complete(TaskOutcome::Success {
verdict: "Done".to_string(),
artifacts: vec![],
});
let score = calculate_recency_score(&episode);
assert!(score > 0.9, "Expected recency score > 0.9, got {score}");
}
#[test]
fn test_recency_score_old_episode() {
let mut episode = create_test_episode("Old task");
let old_time = chrono::Utc::now() - chrono::Duration::days(30);
episode.start_time = old_time;
episode.complete(TaskOutcome::Success {
verdict: "Done".to_string(),
artifacts: vec![],
});
episode.end_time = Some(old_time);
let score = calculate_recency_score(&episode);
assert!(score < 0.5, "Expected recency score < 0.5, got {score}");
}
#[test]
fn test_quality_score_from_reward() {
let mut episode = create_test_episode("Test task");
episode.reward = Some(RewardScore {
total: 1.5,
base: 1.0,
efficiency: 1.2,
complexity_bonus: 1.1,
quality_multiplier: 1.0,
learning_bonus: 0.3,
});
let score = extract_quality_score(&episode);
assert!((0.0..=1.0).contains(&score));
assert!((score - 0.75).abs() < 0.1);
}
#[test]
fn test_quality_score_default() {
let episode = create_test_episode("Test task");
let score = extract_quality_score(&episode);
assert_eq!(score, 0.5); }
}