Skip to main content

mnemo_graph/
model.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// One bitemporal edge in the graph.
6///
7/// `valid_from` / `valid_to` are *fact validity* — when the relation
8/// is true in the world. `recorded_at` is when the row was written
9/// to the database. The two clocks lets us reconstruct an "as_of"
10/// view that asks "what did we believe at time T?" without losing
11/// later corrections.
12///
13/// `relation` is a free-form string today (`"works_at"`,
14/// `"located_in"`, `"reports_to"`). Once the LLM extractor lands we
15/// may pin it to a small enum, but doing that without real corpus
16/// data risks codifying the wrong relation set.
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct TemporalEdge {
19    pub id: Uuid,
20    pub src: Uuid,
21    pub dst: Uuid,
22    pub relation: String,
23    pub valid_from: DateTime<Utc>,
24    /// `None` means "still true" — open-ended interval.
25    pub valid_to: Option<DateTime<Utc>>,
26    /// In `[0.0, 1.0]`. Higher confidences supersede lower ones during
27    /// conflict resolution; ties go to the more recent `recorded_at`.
28    pub confidence: f32,
29    /// Audit-replay clock — when we wrote the row, regardless of the
30    /// fact's own validity window.
31    pub recorded_at: DateTime<Utc>,
32}
33
34impl TemporalEdge {
35    /// Convenience constructor; sets `id = Uuid::now_v7()` and
36    /// `recorded_at = Utc::now()`. Most call-sites should use this
37    /// rather than building the struct directly.
38    pub fn new(
39        src: Uuid,
40        dst: Uuid,
41        relation: impl Into<String>,
42        valid_from: DateTime<Utc>,
43        valid_to: Option<DateTime<Utc>>,
44        confidence: f32,
45    ) -> Self {
46        Self {
47            id: Uuid::now_v7(),
48            src,
49            dst,
50            relation: relation.into(),
51            valid_from,
52            valid_to,
53            confidence: confidence.clamp(0.0, 1.0),
54            recorded_at: Utc::now(),
55        }
56    }
57
58    /// Is this edge valid at `as_of`?
59    ///
60    /// `valid_from <= as_of` and (`valid_to is None` or `as_of < valid_to`).
61    pub fn valid_at(&self, as_of: DateTime<Utc>) -> bool {
62        if as_of < self.valid_from {
63            return false;
64        }
65        match self.valid_to {
66            None => true,
67            Some(end) => as_of < end,
68        }
69    }
70}