use std::time::Duration;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DemotionPolicy {
pub hot_max_age: Duration,
pub warm_max_age: Duration,
pub hot_token_budget: usize,
pub keep_recent: usize,
pub min_importance: f32,
}
impl Default for DemotionPolicy {
fn default() -> Self {
Self {
hot_max_age: Duration::from_secs(24 * 3600),
warm_max_age: Duration::from_secs(7 * 24 * 3600),
hot_token_budget: 50_000,
keep_recent: 4,
min_importance: 0.3,
}
}
}
impl DemotionPolicy {
pub fn should_demote(
&self,
message_age: Duration,
importance: f32,
tier_token_count: usize,
) -> bool {
if tier_token_count <= self.hot_token_budget {
return false;
}
if message_age < self.hot_max_age {
return false;
}
if importance >= self.min_importance {
return false;
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_values() {
let p = DemotionPolicy::default();
assert_eq!(p.hot_max_age, Duration::from_secs(86400));
assert_eq!(p.warm_max_age, Duration::from_secs(604800));
assert_eq!(p.hot_token_budget, 50_000);
assert_eq!(p.keep_recent, 4);
assert!((p.min_importance - 0.3).abs() < f32::EPSILON);
}
#[test]
fn test_should_demote_under_budget() {
let p = DemotionPolicy::default();
assert!(!p.should_demote(Duration::from_secs(100_000), 0.0, 1000));
}
#[test]
fn test_should_demote_young_message() {
let p = DemotionPolicy::default();
assert!(!p.should_demote(Duration::from_secs(3600), 0.0, 100_000));
}
#[test]
fn test_should_demote_important_message() {
let p = DemotionPolicy::default();
assert!(!p.should_demote(Duration::from_secs(100_000), 0.5, 100_000));
}
#[test]
fn test_should_demote_eligible() {
let p = DemotionPolicy::default();
assert!(p.should_demote(Duration::from_secs(100_000), 0.1, 100_000));
}
#[test]
fn test_should_demote_at_boundary() {
let p = DemotionPolicy::default();
assert!(!p.should_demote(Duration::from_secs(100_000), 0.3, 100_000));
assert!(p.should_demote(Duration::from_secs(100_000), 0.29, 100_000));
}
}