use chrono::{DateTime, Utc};
use post_cortex_embeddings::VectorMetadata;
pub trait ScoreAdjuster: Send + Sync {
fn adjust(&self, base_score: f32, metadata: &VectorMetadata) -> f32;
}
pub struct TemporalDecayAdjuster {
lambda: f32,
now: DateTime<Utc>,
}
impl TemporalDecayAdjuster {
pub fn new(lambda: f32, now: DateTime<Utc>) -> Self {
Self { lambda, now }
}
pub fn with_current_time(lambda: f32) -> Self {
Self::new(lambda, Utc::now())
}
}
impl ScoreAdjuster for TemporalDecayAdjuster {
fn adjust(&self, base_score: f32, metadata: &VectorMetadata) -> f32 {
if self.lambda <= 0.0 {
return base_score;
}
let days_since = (self.now - metadata.timestamp).num_days().max(0) as f32;
let decay_factor = (-self.lambda * days_since / 365.0).exp();
base_score * decay_factor
}
}
pub struct CompositeScoreAdjuster {
adjusters: Vec<Box<dyn ScoreAdjuster>>,
}
impl CompositeScoreAdjuster {
pub fn new(adjusters: Vec<Box<dyn ScoreAdjuster>>) -> Self {
Self { adjusters }
}
pub fn empty() -> Self {
Self {
adjusters: Vec::new(),
}
}
pub fn add(&mut self, adjuster: Box<dyn ScoreAdjuster>) {
self.adjusters.push(adjuster);
}
}
impl ScoreAdjuster for CompositeScoreAdjuster {
fn adjust(&self, base_score: f32, metadata: &VectorMetadata) -> f32 {
self.adjusters.iter().fold(base_score, |score, adjuster| {
adjuster.adjust(score, metadata)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Duration;
#[test]
fn test_temporal_decay_disabled() {
let now = Utc::now();
let adjuster = TemporalDecayAdjuster::new(0.0, now);
let metadata = VectorMetadata {
id: "test".to_string(),
source: "session123".to_string(),
content_type: "qa".to_string(),
timestamp: now - Duration::days(365), text: "test content".to_string(),
metadata: std::collections::HashMap::new(),
};
let adjusted = adjuster.adjust(0.8, &metadata);
assert_eq!(adjusted, 0.8); }
#[test]
fn test_temporal_decay_recent_content() {
let now = Utc::now();
let adjuster = TemporalDecayAdjuster::new(1.0, now);
let metadata = VectorMetadata {
id: "test".to_string(),
source: "session123".to_string(),
content_type: "qa".to_string(),
timestamp: now, text: "test content".to_string(),
metadata: std::collections::HashMap::new(),
};
let adjusted = adjuster.adjust(0.8, &metadata);
assert!((adjusted - 0.8).abs() < 0.01); }
#[test]
fn test_temporal_decay_old_content() {
let now = Utc::now();
let adjuster = TemporalDecayAdjuster::new(1.0, now);
let metadata = VectorMetadata {
id: "test".to_string(),
source: "session123".to_string(),
content_type: "qa".to_string(),
timestamp: now - Duration::days(365), text: "test content".to_string(),
metadata: std::collections::HashMap::new(),
};
let adjusted = adjuster.adjust(0.8, &metadata);
assert!((adjusted - 0.296).abs() < 0.01); }
#[test]
fn test_temporal_decay_half_lambda() {
let now = Utc::now();
let adjuster = TemporalDecayAdjuster::new(0.5, now);
let metadata = VectorMetadata {
id: "test".to_string(),
source: "session123".to_string(),
content_type: "qa".to_string(),
timestamp: now - Duration::days(365), text: "test content".to_string(),
metadata: std::collections::HashMap::new(),
};
let adjusted = adjuster.adjust(0.8, &metadata);
assert!((adjusted - 0.488).abs() < 0.01); }
#[test]
fn test_temporal_decay_future_timestamp() {
let now = Utc::now();
let adjuster = TemporalDecayAdjuster::new(1.0, now);
let metadata = VectorMetadata {
id: "test".to_string(),
source: "session123".to_string(),
content_type: "qa".to_string(),
timestamp: now + Duration::days(30), text: "test content".to_string(),
metadata: std::collections::HashMap::new(),
};
let adjusted = adjuster.adjust(0.8, &metadata);
assert!((adjusted - 0.8).abs() < 0.01); }
struct BoostAdjuster {
factor: f32,
}
impl ScoreAdjuster for BoostAdjuster {
fn adjust(&self, base_score: f32, _metadata: &VectorMetadata) -> f32 {
base_score * self.factor
}
}
#[test]
fn test_composite_adjuster_empty() {
let composite = CompositeScoreAdjuster::empty();
let metadata = VectorMetadata {
id: "test".to_string(),
source: "session123".to_string(),
content_type: "qa".to_string(),
timestamp: Utc::now(),
text: "test content".to_string(),
metadata: std::collections::HashMap::new(),
};
let adjusted = composite.adjust(0.8, &metadata);
assert_eq!(adjusted, 0.8); }
#[test]
fn test_composite_adjuster_single() {
let adjuster = BoostAdjuster { factor: 0.5 };
let composite = CompositeScoreAdjuster::new(vec![Box::new(adjuster)]);
let metadata = VectorMetadata {
id: "test".to_string(),
source: "session123".to_string(),
content_type: "qa".to_string(),
timestamp: Utc::now(),
text: "test content".to_string(),
metadata: std::collections::HashMap::new(),
};
let adjusted = composite.adjust(0.8, &metadata);
assert_eq!(adjusted, 0.4); }
#[test]
fn test_composite_adjuster_multiple() {
let now = Utc::now();
let composite = CompositeScoreAdjuster::new(vec![
Box::new(TemporalDecayAdjuster::new(0.693, now)),
Box::new(BoostAdjuster { factor: 2.0 }),
]);
let metadata = VectorMetadata {
id: "test".to_string(),
source: "session123".to_string(),
content_type: "qa".to_string(),
timestamp: now - Duration::days(365), text: "test content".to_string(),
metadata: std::collections::HashMap::new(),
};
let adjusted = composite.adjust(0.8, &metadata);
assert!((adjusted - 0.8).abs() < 0.01);
}
#[test]
fn test_composite_adjuster_add() {
let mut composite = CompositeScoreAdjuster::empty();
composite.add(Box::new(BoostAdjuster { factor: 0.5 }));
composite.add(Box::new(BoostAdjuster { factor: 0.5 }));
let metadata = VectorMetadata {
id: "test".to_string(),
source: "session123".to_string(),
content_type: "qa".to_string(),
timestamp: Utc::now(),
text: "test content".to_string(),
metadata: std::collections::HashMap::new(),
};
let adjusted = composite.adjust(0.8, &metadata);
assert_eq!(adjusted, 0.2); }
}