Skip to main content

spatial_narrative/index/
temporal.rs

1//! Temporal indexing for efficient time-based queries.
2//!
3//! This module provides temporal indexing using B-tree structures,
4//! enabling fast queries like:
5//! - Time range queries
6//! - Before/after queries
7//! - Sliding window iteration
8//!
9//! # Example
10//!
11//! ```rust
12//! use spatial_narrative::index::TemporalIndex;
13//! use spatial_narrative::core::{Timestamp, TimeRange};
14//!
15//! // Build temporal index
16//! let mut index: TemporalIndex<&str> = TemporalIndex::new();
17//! index.insert("Morning", &Timestamp::parse("2024-01-01T10:00:00Z").unwrap());
18//! index.insert("Afternoon", &Timestamp::parse("2024-01-01T14:00:00Z").unwrap());
19//! index.insert("Evening", &Timestamp::parse("2024-01-01T20:00:00Z").unwrap());
20//!
21//! // Query events in a time range
22//! let start = Timestamp::parse("2024-01-01T12:00:00Z").unwrap();
23//! let end = Timestamp::parse("2024-01-01T18:00:00Z").unwrap();
24//! let results = index.query_range(&TimeRange::new(start, end));
25//! assert!(!results.is_empty());
26//! ```
27
28use crate::core::{TimeRange, Timestamp};
29use std::collections::BTreeMap;
30
31/// Temporal index for efficient time-based queries.
32///
33/// Uses a B-tree for O(log n) range queries.
34#[derive(Debug)]
35pub struct TemporalIndex<T> {
36    /// B-tree mapping timestamps to item indices
37    tree: BTreeMap<i64, Vec<usize>>,
38    /// The actual items
39    items: Vec<T>,
40    /// Timestamps for each item (for iteration)
41    timestamps: Vec<Timestamp>,
42}
43
44impl<T: Clone> TemporalIndex<T> {
45    /// Create an empty temporal index.
46    pub fn new() -> Self {
47        Self {
48            tree: BTreeMap::new(),
49            items: Vec::new(),
50            timestamps: Vec::new(),
51        }
52    }
53
54    /// Create a temporal index from items with a timestamp extractor.
55    pub fn from_iter<I, F>(iter: I, timestamp_fn: F) -> Self
56    where
57        I: IntoIterator<Item = T>,
58        F: Fn(&T) -> &Timestamp,
59    {
60        let mut index = Self::new();
61        for item in iter {
62            let ts = timestamp_fn(&item).clone();
63            index.insert(item, &ts);
64        }
65        index
66    }
67
68    /// Insert an item into the index.
69    pub fn insert(&mut self, item: T, timestamp: &Timestamp) {
70        let idx = self.items.len();
71        let key = timestamp.to_unix_millis();
72
73        self.items.push(item);
74        self.timestamps.push(timestamp.clone());
75        self.tree.entry(key).or_default().push(idx);
76    }
77
78    /// Query items within a time range (inclusive).
79    pub fn query_range(&self, range: &TimeRange) -> Vec<&T> {
80        let start_key = range.start.to_unix_millis();
81        let end_key = range.end.to_unix_millis();
82
83        self.tree
84            .range(start_key..=end_key)
85            .flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
86            .collect()
87    }
88
89    /// Query items before a timestamp.
90    pub fn before(&self, timestamp: &Timestamp) -> Vec<&T> {
91        let key = timestamp.to_unix_millis();
92
93        self.tree
94            .range(..key)
95            .flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
96            .collect()
97    }
98
99    /// Query items after a timestamp.
100    pub fn after(&self, timestamp: &Timestamp) -> Vec<&T> {
101        let key = timestamp.to_unix_millis();
102
103        self.tree
104            .range((key + 1)..)
105            .flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
106            .collect()
107    }
108
109    /// Query items at or before a timestamp.
110    pub fn at_or_before(&self, timestamp: &Timestamp) -> Vec<&T> {
111        let key = timestamp.to_unix_millis();
112
113        self.tree
114            .range(..=key)
115            .flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
116            .collect()
117    }
118
119    /// Query items at or after a timestamp.
120    pub fn at_or_after(&self, timestamp: &Timestamp) -> Vec<&T> {
121        let key = timestamp.to_unix_millis();
122
123        self.tree
124            .range(key..)
125            .flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
126            .collect()
127    }
128
129    /// Get the first (earliest) item.
130    pub fn first(&self) -> Option<&T> {
131        self.tree
132            .iter()
133            .next()
134            .and_then(|(_, indices)| indices.first().map(|&i| &self.items[i]))
135    }
136
137    /// Get the last (latest) item.
138    pub fn last(&self) -> Option<&T> {
139        self.tree
140            .iter()
141            .next_back()
142            .and_then(|(_, indices)| indices.last().map(|&i| &self.items[i]))
143    }
144
145    /// Returns items in chronological order.
146    pub fn chronological(&self) -> Vec<&T> {
147        self.tree
148            .iter()
149            .flat_map(|(_, indices)| indices.iter().map(|&i| &self.items[i]))
150            .collect()
151    }
152
153    /// Returns items in reverse chronological order.
154    pub fn reverse_chronological(&self) -> Vec<&T> {
155        self.tree
156            .iter()
157            .rev()
158            .flat_map(|(_, indices)| indices.iter().rev().map(|&i| &self.items[i]))
159            .collect()
160    }
161
162    /// Returns the time range spanned by all items.
163    pub fn time_range(&self) -> Option<TimeRange> {
164        let first_key = self.tree.keys().next()?;
165        let last_key = self.tree.keys().next_back()?;
166
167        let start = Timestamp::from_unix_millis(*first_key)?;
168        let end = Timestamp::from_unix_millis(*last_key)?;
169
170        Some(TimeRange::new(start, end))
171    }
172
173    /// Create a sliding window iterator over the items.
174    pub fn sliding_window(&self, window_size: chrono::Duration) -> SlidingWindowIter<'_, T> {
175        SlidingWindowIter {
176            index: self,
177            window_millis: window_size.num_milliseconds(),
178            current_start: self.tree.keys().next().copied(),
179        }
180    }
181
182    /// Returns the number of indexed items.
183    pub fn len(&self) -> usize {
184        self.items.len()
185    }
186
187    /// Returns true if the index is empty.
188    pub fn is_empty(&self) -> bool {
189        self.items.is_empty()
190    }
191
192    /// Get all items in the index.
193    pub fn items(&self) -> &[T] {
194        &self.items
195    }
196}
197
198impl<T: Clone> Default for TemporalIndex<T> {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204/// Iterator over sliding time windows.
205pub struct SlidingWindowIter<'a, T> {
206    index: &'a TemporalIndex<T>,
207    window_millis: i64,
208    current_start: Option<i64>,
209}
210
211impl<'a, T: Clone> Iterator for SlidingWindowIter<'a, T> {
212    type Item = Vec<&'a T>;
213
214    fn next(&mut self) -> Option<Self::Item> {
215        let start = self.current_start?;
216        let end = start + self.window_millis;
217
218        // Find next window start
219        self.current_start = self.index.tree.range((start + 1)..).next().map(|(k, _)| *k);
220
221        let items: Vec<_> = self
222            .index
223            .tree
224            .range(start..end)
225            .flat_map(|(_, indices)| indices.iter().map(|&i| &self.index.items[i]))
226            .collect();
227
228        if items.is_empty() && self.current_start.is_some() {
229            self.next() // Skip empty windows
230        } else if items.is_empty() {
231            None
232        } else {
233            Some(items)
234        }
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    fn make_timestamp(hour: u32) -> Timestamp {
243        Timestamp::parse(&format!("2024-01-01T{:02}:00:00Z", hour)).unwrap()
244    }
245
246    #[test]
247    fn test_temporal_index_new() {
248        let index: TemporalIndex<String> = TemporalIndex::new();
249        assert!(index.is_empty());
250    }
251
252    #[test]
253    fn test_temporal_index_insert() {
254        let mut index = TemporalIndex::new();
255        index.insert("Morning", &make_timestamp(9));
256        index.insert("Noon", &make_timestamp(12));
257        index.insert("Evening", &make_timestamp(18));
258
259        assert_eq!(index.len(), 3);
260    }
261
262    #[test]
263    fn test_temporal_index_query_range() {
264        let mut index = TemporalIndex::new();
265        index.insert("9am", &make_timestamp(9));
266        index.insert("12pm", &make_timestamp(12));
267        index.insert("3pm", &make_timestamp(15));
268        index.insert("6pm", &make_timestamp(18));
269
270        let range = TimeRange::new(make_timestamp(11), make_timestamp(16));
271        let results = index.query_range(&range);
272
273        assert_eq!(results.len(), 2);
274    }
275
276    #[test]
277    fn test_temporal_index_before_after() {
278        let mut index = TemporalIndex::new();
279        index.insert("9am", &make_timestamp(9));
280        index.insert("12pm", &make_timestamp(12));
281        index.insert("3pm", &make_timestamp(15));
282
283        let before = index.before(&make_timestamp(12));
284        assert_eq!(before.len(), 1);
285        assert_eq!(*before[0], "9am");
286
287        let after = index.after(&make_timestamp(12));
288        assert_eq!(after.len(), 1);
289        assert_eq!(*after[0], "3pm");
290    }
291
292    #[test]
293    fn test_temporal_index_first_last() {
294        let mut index = TemporalIndex::new();
295        index.insert("First", &make_timestamp(8));
296        index.insert("Middle", &make_timestamp(12));
297        index.insert("Last", &make_timestamp(20));
298
299        assert_eq!(index.first(), Some(&"First"));
300        assert_eq!(index.last(), Some(&"Last"));
301    }
302
303    #[test]
304    fn test_temporal_index_chronological() {
305        let mut index = TemporalIndex::new();
306        // Insert out of order
307        index.insert("C", &make_timestamp(15));
308        index.insert("A", &make_timestamp(9));
309        index.insert("B", &make_timestamp(12));
310
311        let ordered: Vec<_> = index.chronological();
312        assert_eq!(ordered, vec![&"A", &"B", &"C"]);
313    }
314}