use crate::error::RecommendResult;
use crate::Recommendation;
pub struct FreshnessBalancer {
freshness_weight: f32,
fresh_threshold_days: u32,
}
impl FreshnessBalancer {
#[must_use]
pub fn new(freshness_weight: f32, fresh_threshold_days: u32) -> Self {
Self {
freshness_weight: freshness_weight.clamp(0.0, 1.0),
fresh_threshold_days,
}
}
pub fn balance(
&self,
mut recommendations: Vec<Recommendation>,
) -> RecommendResult<Vec<Recommendation>> {
let now = chrono::Utc::now().timestamp();
for rec in &mut recommendations {
let age_days = (now - rec.metadata.created_at) / 86400;
let freshness_boost = self.calculate_freshness_boost(age_days as u32);
rec.score =
rec.score * (1.0 - self.freshness_weight) + freshness_boost * self.freshness_weight;
}
recommendations.sort_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
});
for (idx, rec) in recommendations.iter_mut().enumerate() {
rec.rank = idx + 1;
}
Ok(recommendations)
}
fn calculate_freshness_boost(&self, age_days: u32) -> f32 {
if age_days <= self.fresh_threshold_days {
1.0
} else {
let decay = (age_days - self.fresh_threshold_days) as f32 / 365.0;
(1.0 - decay).max(0.0)
}
}
#[must_use]
pub fn is_fresh(&self, created_at: i64) -> bool {
let now = chrono::Utc::now().timestamp();
let age_days = (now - created_at) / 86400;
age_days <= i64::from(self.fresh_threshold_days)
}
}
impl Default for FreshnessBalancer {
fn default() -> Self {
Self::new(0.2, 7) }
}
pub struct RecencyCalculator;
impl RecencyCalculator {
#[must_use]
pub fn calculate_score(created_at: i64, half_life_days: u32) -> f32 {
let now = chrono::Utc::now().timestamp();
let age_days = (now - created_at) as f32 / 86400.0;
let decay_rate = (2.0_f32).ln() / half_life_days as f32;
(-decay_rate * age_days).exp()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_freshness_balancer() {
let balancer = FreshnessBalancer::new(0.3, 7);
assert_eq!(balancer.fresh_threshold_days, 7);
}
#[test]
fn test_is_fresh() {
let balancer = FreshnessBalancer::default();
let now = chrono::Utc::now().timestamp();
assert!(balancer.is_fresh(now));
let old = now - (30 * 86400); assert!(!balancer.is_fresh(old));
}
#[test]
fn test_recency_score() {
let now = chrono::Utc::now().timestamp();
let score = RecencyCalculator::calculate_score(now, 7);
assert!((score - 1.0).abs() < 0.01);
}
}