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}