use crate::context::types::{BundleConfig, ContextItem};
use chrono::{DateTime, Utc};
#[must_use]
pub fn calculate_recency_score(
timestamp: DateTime<Utc>,
now: DateTime<Utc>,
half_life_days: f32,
) -> f32 {
let age_days = (now - timestamp).num_days() as f32;
if age_days < 0.0 {
return 1.0;
}
0.5_f32.powf(age_days / half_life_days)
}
#[must_use]
pub fn calculate_priority_score(
item: &ContextItem,
config: &BundleConfig,
now: DateTime<Utc>,
) -> f32 {
let recency = calculate_recency_score(item.timestamp(), now, config.recency_half_life_days);
let salience = item.salience();
let priority = recency * config.recency_weight + salience * config.salience_weight;
let weight_sum = config.recency_weight + config.salience_weight;
if weight_sum > 0.0 {
priority / weight_sum
} else {
0.0
}
}
#[must_use]
pub fn compare_by_priority(a: &ContextItem, b: &ContextItem) -> std::cmp::Ordering {
b.priority()
.partial_cmp(&a.priority())
.unwrap_or(std::cmp::Ordering::Equal)
}
#[must_use]
pub fn compare_by_recency(a: &ContextItem, b: &ContextItem) -> std::cmp::Ordering {
b.timestamp().cmp(&a.timestamp())
}
#[must_use]
pub fn compare_by_salience(a: &ContextItem, b: &ContextItem) -> std::cmp::Ordering {
b.salience()
.partial_cmp(&a.salience())
.unwrap_or(std::cmp::Ordering::Equal)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::episode::Episode;
use crate::types::{TaskContext, TaskType};
use std::sync::Arc;
fn create_test_episode(description: &str, days_ago: i64) -> Arc<Episode> {
let mut episode = Episode::new(
description.to_string(),
TaskContext::default(),
TaskType::Debugging,
);
episode.start_time = Utc::now() - chrono::Duration::days(days_ago);
Arc::new(episode)
}
#[test]
fn test_recency_score_recent() {
let now = Utc::now();
let recent = now - chrono::Duration::days(1);
let score = calculate_recency_score(recent, now, 30.0);
assert!(score > 0.9, "Recent item should have high score: {}", score);
}
#[test]
fn test_recency_score_half_life() {
let now = Utc::now();
let half_life_old = now - chrono::Duration::days(30);
let score = calculate_recency_score(half_life_old, now, 30.0);
assert!(
(score - 0.5).abs() < 0.01,
"Item at half_life should have ~0.5 score: {}",
score
);
}
#[test]
fn test_recency_score_old() {
let now = Utc::now();
let old = now - chrono::Duration::days(90);
let score = calculate_recency_score(old, now, 30.0);
assert!(score < 0.2, "Old item should have low score: {}", score);
}
#[test]
fn test_priority_score_balanced_weights() {
let config = BundleConfig::new(20, 0.5, 0.5);
let episode = create_test_episode("test", 0);
let item = ContextItem::from_episode(episode, 0.8);
let now = Utc::now();
let priority = calculate_priority_score(&item, &config, now);
assert!(
(priority - 0.9).abs() < 0.01,
"Expected ~0.9 priority: {}",
priority
);
}
#[test]
fn test_priority_score_recency_focused() {
let config = BundleConfig::new(20, 0.8, 0.2);
let episode = create_test_episode("test", 30); let item = ContextItem::from_episode(episode, 0.9);
let now = Utc::now();
let priority = calculate_priority_score(&item, &config, now);
let recency = calculate_recency_score(item.timestamp(), now, config.recency_half_life_days);
let expected = (recency * 0.8 + 0.9 * 0.2) / 1.0;
assert!(
(priority - expected).abs() < 0.01,
"Expected {}: {}",
expected,
priority
);
}
#[test]
fn test_compare_by_priority() {
let ep1 = create_test_episode("high priority", 0);
let ep2 = create_test_episode("low priority", 30);
let mut item1 = ContextItem::from_episode(ep1, 0.9);
let mut item2 = ContextItem::from_episode(ep2, 0.5);
item1.set_priority(0.9);
item2.set_priority(0.3);
assert_eq!(
compare_by_priority(&item1, &item2),
std::cmp::Ordering::Less
);
}
}