use chrono::{DateTime, Utc};
#[derive(Debug, Clone)]
pub struct DecayConfig {
pub half_life_days: f32,
pub floor: f32,
pub access_boost: f32,
pub access_boost_cap: f32,
}
impl Default for DecayConfig {
fn default() -> Self {
Self {
half_life_days: 90.0,
floor: 0.05,
access_boost: 0.05,
access_boost_cap: 0.3,
}
}
}
impl DecayConfig {
pub fn effective_importance(&self, base: f32, age_days: f32, accumulated_boost: f32) -> f32 {
let decayed = base * 2f32.powf(-age_days / self.half_life_days);
let boost = accumulated_boost.min(self.access_boost_cap);
(decayed + boost).clamp(self.floor, 1.0)
}
pub fn age_days(created_at: DateTime<Utc>) -> f32 {
let elapsed = Utc::now().signed_duration_since(created_at);
elapsed.num_seconds().max(0) as f32 / 86_400.0
}
pub fn effective_confidence(&self, base: f32, age_days: f32) -> f32 {
let decayed = base * 2f32.powf(-age_days / self.half_life_days);
decayed.clamp(self.floor, 1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_decay_at_age_zero() {
let cfg = DecayConfig::default();
let eff = cfg.effective_importance(0.8, 0.0, 0.0);
assert!((eff - 0.8).abs() < 1e-4, "got {eff}");
}
#[test]
fn half_life_at_90_days() {
let cfg = DecayConfig::default();
let eff = cfg.effective_importance(0.8, 90.0, 0.0);
assert!((eff - 0.4).abs() < 1e-3, "expected ~0.4 got {eff}");
}
#[test]
fn floor_clamps_minimum() {
let cfg = DecayConfig::default();
let eff = cfg.effective_importance(0.1, 365.0, 0.0);
assert_eq!(eff, cfg.floor, "should be floored at {}", cfg.floor);
}
#[test]
fn access_boost_applied() {
let cfg = DecayConfig::default();
let eff = cfg.effective_importance(0.5, 0.0, 0.3);
assert!((eff - 0.8).abs() < 1e-4, "got {eff}");
}
#[test]
fn access_boost_capped() {
let cfg = DecayConfig::default();
let eff_capped = cfg.effective_importance(0.5, 0.0, 1.0);
let eff_at_cap = cfg.effective_importance(0.5, 0.0, 0.3);
assert!((eff_capped - eff_at_cap).abs() < 1e-4);
}
#[test]
fn drawer_accumulated_boost() {
use super::super::palace::Drawer;
use uuid::Uuid;
let cfg = DecayConfig::default();
let mut d = Drawer::new(Uuid::new_v4(), "test");
assert_eq!(d.accumulated_boost(&cfg), 0.0);
d.record_access();
d.record_access();
assert!((d.accumulated_boost(&cfg) - 0.10).abs() < 1e-4);
}
#[test]
fn kg_triple_effective_confidence() {
let cfg = DecayConfig::default();
let eff = cfg.effective_confidence(1.0, 90.0);
assert!((eff - 0.5).abs() < 1e-3, "expected ~0.5 got {eff}");
}
}