pub struct NoveltyComputer {
session_start: i64,
decay_rate: f64,
}
impl NoveltyComputer {
#[must_use]
pub fn new(session_start: i64, decay_rate: f64) -> Self {
Self {
session_start,
decay_rate,
}
}
#[must_use]
#[inline]
pub fn compute(&self, fact_created_at: i64) -> f64 {
let _span = tracing::info_span!("memory.five_signal.novelty.compute").entered();
#[expect(clippy::cast_precision_loss)]
let days = ((fact_created_at - self.session_start).max(0) as f64) / 86_400.0;
(-self.decay_rate * days).exp()
}
}
#[cfg(test)]
mod tests {
use super::*;
const SESSION_START: i64 = 1_700_000_000;
#[test]
fn at_session_start_is_one() {
let c = NoveltyComputer::new(SESSION_START, 0.1);
assert!((c.compute(SESSION_START) - 1.0).abs() < 1e-9);
}
#[test]
fn before_session_start_is_one() {
let c = NoveltyComputer::new(SESSION_START, 0.1);
assert!((c.compute(SESSION_START - 86_400) - 1.0).abs() < 1e-9);
}
#[test]
fn ten_days_later() {
let c = NoveltyComputer::new(SESSION_START, 0.1);
let ten_days = SESSION_START + 10 * 86_400;
let expected = (-1.0_f64).exp();
assert!((c.compute(ten_days) - expected).abs() < 1e-9);
}
#[test]
fn monotone_decreasing() {
let c = NoveltyComputer::new(SESSION_START, 0.1);
let early = c.compute(SESSION_START + 86_400);
let late = c.compute(SESSION_START + 10 * 86_400);
assert!(early > late, "novelty must decrease with time");
}
}