Skip to main content

sqlite_graph/
types.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4/// An episode is the fundamental unit of information.
5/// It represents "something happened" — a decision, conversation, event.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Episode {
8    pub id: String,
9    pub content: String,
10    pub source: Option<String>,
11    pub recorded_at: DateTime<Utc>,
12    pub metadata: Option<serde_json::Value>,
13}
14
15/// Builder for constructing episodes with a fluent API.
16pub struct EpisodeBuilder {
17    content: String,
18    source: Option<String>,
19    metadata: serde_json::Map<String, serde_json::Value>,
20    tags: Vec<String>,
21}
22
23impl EpisodeBuilder {
24    pub fn source(mut self, s: &str) -> Self {
25        self.source = Some(s.to_string());
26        self
27    }
28
29    pub fn tag(mut self, t: &str) -> Self {
30        self.tags.push(t.to_string());
31        self
32    }
33
34    pub fn meta(mut self, key: &str, val: impl Into<serde_json::Value>) -> Self {
35        self.metadata.insert(key.to_string(), val.into());
36        self
37    }
38
39    pub fn build(self) -> Episode {
40        let mut metadata = self.metadata;
41        if !self.tags.is_empty() {
42            let tags: Vec<serde_json::Value> = self
43                .tags
44                .into_iter()
45                .map(serde_json::Value::String)
46                .collect();
47            metadata.insert("tags".to_string(), serde_json::Value::Array(tags));
48        }
49
50        let metadata = if metadata.is_empty() {
51            None
52        } else {
53            Some(serde_json::Value::Object(metadata))
54        };
55
56        Episode {
57            id: uuid::Uuid::now_v7().to_string(),
58            content: self.content,
59            source: self.source,
60            recorded_at: Utc::now(),
61            metadata,
62        }
63    }
64}
65
66impl Episode {
67    pub fn builder(content: &str) -> EpisodeBuilder {
68        EpisodeBuilder {
69            content: content.to_string(),
70            source: None,
71            metadata: serde_json::Map::new(),
72            tags: Vec::new(),
73        }
74    }
75}
76
77/// An entity is a node in the graph — people, components, decisions, etc.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct Entity {
80    pub id: String,
81    pub name: String,
82    pub entity_type: String,
83    pub summary: Option<String>,
84    pub created_at: DateTime<Utc>,
85    pub metadata: Option<serde_json::Value>,
86}
87
88impl Entity {
89    pub fn new(name: &str, entity_type: &str) -> Self {
90        Self {
91            id: uuid::Uuid::now_v7().to_string(),
92            name: name.to_string(),
93            entity_type: entity_type.to_string(),
94            summary: None,
95            created_at: Utc::now(),
96            metadata: None,
97        }
98    }
99}
100
101/// An edge is a relationship between two entities.
102/// Edges are bi-temporal: valid_from/valid_until (real-world) + recorded_at (system).
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct Edge {
105    pub id: String,
106    pub source_id: String,
107    pub target_id: String,
108    pub relation: String,
109    pub fact: Option<String>,
110    pub valid_from: Option<DateTime<Utc>>,
111    pub valid_until: Option<DateTime<Utc>>,
112    pub recorded_at: DateTime<Utc>,
113    pub confidence: f64,
114    pub episode_id: Option<String>,
115    pub metadata: Option<serde_json::Value>,
116}
117
118impl Edge {
119    pub fn new(source_id: &str, target_id: &str, relation: &str) -> Self {
120        Self {
121            id: uuid::Uuid::now_v7().to_string(),
122            source_id: source_id.to_string(),
123            target_id: target_id.to_string(),
124            relation: relation.to_string(),
125            fact: None,
126            valid_from: None,
127            valid_until: None,
128            recorded_at: Utc::now(),
129            confidence: 1.0,
130            episode_id: None,
131            metadata: None,
132        }
133    }
134
135    /// Check if this edge is currently valid (not invalidated).
136    pub fn is_current(&self) -> bool {
137        self.valid_until.is_none()
138    }
139
140    /// Check if this edge was valid at a specific point in time.
141    pub fn is_valid_at(&self, at: DateTime<Utc>) -> bool {
142        let after_start = self.valid_from.is_none_or(|vf| vf <= at);
143        let before_end = self.valid_until.is_none_or(|vu| vu > at);
144        after_start && before_end
145    }
146}
147
148/// Result from adding an episode to the graph.
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct EpisodeResult {
151    pub episode_id: String,
152}
153
154/// Per-episode result from fused (RRF) search.
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct FusedEpisodeResult {
157    pub episode: Episode,
158    pub score: f64,
159}
160
161/// Graph-wide statistics.
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct GraphStats {
164    pub episode_count: usize,
165    pub entity_count: usize,
166    pub edge_count: usize,
167    pub sources: Vec<(String, usize)>,
168    pub db_size_bytes: u64,
169}
170
171/// Context around an entity — its immediate neighbors and edges.
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct EntityContext {
174    pub entity: Entity,
175    pub edges: Vec<Edge>,
176    pub neighbors: Vec<Entity>,
177}