use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::chunk::MemoryChunk;
use crate::query::MemoryPredicate;
use crate::session::MemorySession;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum EvictionReason {
UserDelete,
Aging,
LowImportance,
RedactRule,
StorageCap,
PurgeAll,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EvictionEntry {
pub ts: DateTime<Utc>,
pub chunk_id: String,
pub reason: EvictionReason,
#[serde(default)]
pub metadata: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AccessEntry {
pub ts: DateTime<Utc>,
pub chunk_id: String,
pub retrieved_by: String,
pub query_hash: String,
pub rank: usize,
pub used: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ExportFilter {
#[serde(default)]
pub predicate: Option<MemoryPredicate>,
#[serde(default)]
pub include_eviction_log: bool,
#[serde(default)]
pub include_access_log: bool,
#[serde(default = "default_true")]
pub include_sessions: bool,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ExportBundle {
pub chunks: Vec<MemoryChunk>,
#[serde(default)]
pub sessions: Vec<MemorySession>,
#[serde(default)]
pub evictions: Vec<EvictionEntry>,
#[serde(default)]
pub accesses: Vec<AccessEntry>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct PurgeReport {
pub chunks_deleted: usize,
pub sessions_deleted: usize,
pub access_log_deleted: usize,
pub eviction_log_deleted: usize,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct AgingReport {
pub tier_promoted: usize,
pub deleted: usize,
pub bytes_reclaimed: u64,
#[serde(default)]
pub deletions_by_reason: Vec<(EvictionReason, usize)>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ReEmbedReport {
pub total: usize,
pub succeeded: usize,
pub failed: usize,
pub elapsed_ms: u64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct MemoryStats {
pub total_chunks: usize,
pub session_chunks: usize,
pub long_term_chunks: usize,
pub total_sessions: usize,
pub open_sessions: usize,
pub db_bytes: u64,
pub embedding_model: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn eviction_reason_serializes_snake_case() {
assert_eq!(
serde_json::to_value(EvictionReason::StorageCap).unwrap(),
json!("storage_cap")
);
assert_eq!(
serde_json::to_value(EvictionReason::UserDelete).unwrap(),
json!("user_delete")
);
}
#[test]
fn export_filter_defaults_include_sessions() {
let raw = "{}";
let f: ExportFilter = serde_json::from_str(raw).unwrap();
assert!(f.include_sessions);
assert!(!f.include_access_log);
assert!(!f.include_eviction_log);
}
#[test]
fn stats_round_trip() {
let s = MemoryStats {
total_chunks: 100,
session_chunks: 30,
long_term_chunks: 70,
total_sessions: 5,
open_sessions: 1,
db_bytes: 1024 * 1024,
embedding_model: Some("bge-small-en-v1.5".into()),
};
let raw = serde_json::to_string(&s).unwrap();
let back: MemoryStats = serde_json::from_str(&raw).unwrap();
assert_eq!(s, back);
}
}