use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::core::scope::ScopeIdentifiers;
pub type FactId = String;
pub type ContentHash = String;
pub type EmbeddingVector = Vec<f32>;
pub type MetadataMap = HashMap<String, serde_json::Value>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoredFact {
pub id: FactId,
pub content: String,
pub scope: ScopeIdentifiers,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding: Option<EmbeddingVector>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub content_hash: ContentHash,
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub metadata: MetadataMap,
#[serde(skip_serializing_if = "Option::is_none")]
pub relevance_score: Option<f32>,
}
impl StoredFact {
pub fn new(id: impl Into<FactId>, content: impl Into<String>, scope: ScopeIdentifiers) -> Self {
let content = content.into();
let id = id.into();
let now = Utc::now();
let content_hash = Self::compute_hash(&content);
Self {
id,
content,
scope,
embedding: None,
created_at: now,
updated_at: now,
content_hash,
metadata: HashMap::new(),
relevance_score: None,
}
}
pub fn compute_hash(content: &str) -> ContentHash {
let digest = ::md5::compute(content);
format!("{:x}", digest)
}
pub fn with_embedding(mut self, embedding: EmbeddingVector) -> Self {
self.embedding = Some(embedding);
self
}
pub fn with_metadata(mut self, metadata: MetadataMap) -> Self {
self.metadata = metadata;
self
}
pub fn add_metadata(&mut self, key: impl Into<String>, value: serde_json::Value) {
self.metadata.insert(key.into(), value);
}
pub fn with_relevance_score(mut self, score: f32) -> Self {
self.relevance_score = Some(score);
self
}
pub fn update_content(&mut self, new_content: impl Into<String>) {
self.content = new_content.into();
self.content_hash = Self::compute_hash(&self.content);
self.updated_at = Utc::now();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stored_fact_creation() {
let scope = ScopeIdentifiers::for_user("user_123");
let fact = StoredFact::new("fact_1", "Test content", scope);
assert_eq!(fact.id, "fact_1");
assert_eq!(fact.content, "Test content");
assert!(!fact.content_hash.is_empty());
}
#[test]
fn test_content_hash() {
let hash1 = StoredFact::compute_hash("test");
let hash2 = StoredFact::compute_hash("test");
let hash3 = StoredFact::compute_hash("different");
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash3);
}
}