use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::multimodal::MultimodalRef;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompactedContent {
pub summary: String,
pub key_facts: Vec<String>,
pub entities: Vec<String>,
pub actions: Vec<String>,
pub outcomes: Vec<String>,
pub insights: Vec<String>,
}
impl CompactedContent {
pub fn estimated_tokens(&self) -> usize {
let text_len = self.summary.len()
+ self.key_facts.iter().map(|s| s.len()).sum::<usize>()
+ self.entities.iter().map(|s| s.len()).sum::<usize>()
+ self.actions.iter().map(|s| s.len()).sum::<usize>()
+ self.outcomes.iter().map(|s| s.len()).sum::<usize>()
+ self.insights.iter().map(|s| s.len()).sum::<usize>();
text_len / 4
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
pub enum RecordImportance {
Low = 1,
#[default]
Normal = 2,
High = 3,
Critical = 4,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalRecord {
pub id: String,
pub created_at: DateTime<Utc>,
pub source_timestamps: Vec<DateTime<Utc>>,
pub sequence_order: u64,
pub causal_parents: Vec<String>,
pub causal_children: Vec<String>,
pub decay_score: f64,
pub access_count: u32,
pub last_accessed: DateTime<Utc>,
pub content: CompactedContent,
pub multimodal_refs: Vec<MultimodalRef>,
pub source_ids: Vec<String>,
pub tags: Vec<String>,
pub importance: RecordImportance,
pub session_id: Option<String>,
pub metadata: HashMap<String, String>,
}
impl TemporalRecord {
pub fn decay_score_at(&self, now: DateTime<Utc>, half_life_hours: f64) -> f64 {
let age_hours = (now - self.created_at).num_seconds() as f64 / 3600.0;
let base_decay = (-age_hours * (2.0_f64.ln()) / half_life_hours).exp();
let access_bonus = if self.access_count > 0 {
let since_access = (now - self.last_accessed).num_seconds() as f64 / 3600.0;
let access_decay = (-since_access * (2.0_f64.ln()) / half_life_hours).exp();
let access_factor = (self.access_count as f64).ln_1p() * 0.1;
access_factor * access_decay
} else {
0.0
};
let importance_mult = match self.importance {
RecordImportance::Low => 0.5,
RecordImportance::Normal => 1.0,
RecordImportance::High => 1.5,
RecordImportance::Critical => 2.0,
};
((base_decay + access_bonus) * importance_mult).min(1.0)
}
pub fn record_access(&mut self) {
self.access_count += 1;
self.last_accessed = Utc::now();
self.decay_score = self.decay_score_at(Utc::now(), 24.0);
}
pub fn time_span(&self) -> Option<(DateTime<Utc>, DateTime<Utc>)> {
if self.source_timestamps.is_empty() {
return None;
}
let min = self.source_timestamps.iter().min().copied()?;
let max = self.source_timestamps.iter().max().copied()?;
Some((min, max))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsolidationReport {
pub started_at: DateTime<Utc>,
pub ended_at: DateTime<Utc>,
pub episodes_processed: usize,
pub records_produced: usize,
pub duplicates_removed: usize,
pub tokens_used: u64,
pub causal_links_created: usize,
pub multimodal_refs_count: usize,
pub errors: Vec<String>,
}
#[cfg(test)]
#[path = "../../tests/unit/consolidation/temporal/temporal_test.rs"]
mod tests;