Skip to main content

ant_types/
graph.rs

1//! Graph data model: Vertex, Edge, PropertyValue, SubGraph.
2
3use std::collections::BTreeMap;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8use crate::evidence::EvidenceId;
9use crate::ids::{EdgeId, TypeName, VertexId};
10
11pub use crate::property::PropertyValue;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct Vertex {
15    pub id: VertexId,
16    pub name: String,
17    /// Namespace-qualified, e.g. "Antares.Deal".
18    pub label: TypeName,
19    pub properties: BTreeMap<String, PropertyValue>,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct Edge {
24    pub id: EdgeId,
25    pub src: VertexId,
26    pub src_type: TypeName,
27    pub dst: VertexId,
28    pub dst_type: TypeName,
29    /// Relation name (e.g. "hasStakeholder"), NOT namespace-qualified.
30    pub label: String,
31    pub properties: BTreeMap<String, PropertyValue>,
32
33    // --- Antares-native bitemporal annotations ---
34    //
35    // None on valid_from = valid from -infinity (always was).
36    // None on valid_to   = still holds (no end).
37    // observed_at        = wall-clock time we recorded this fact.
38    // extracted_at       = wall-clock time the extractor produced it.
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub valid_from: Option<DateTime<Utc>>,
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub valid_to: Option<DateTime<Utc>>,
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub observed_at: Option<DateTime<Utc>>,
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub extracted_at: Option<DateTime<Utc>>,
47
48    /// Confidence in [0,1] for the fact. `None` is treated as 1.0 for
49    /// matching purposes.
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub confidence: Option<f32>,
52
53    /// First-class evidence references. Empty by default
54    /// (backwards-compatible with older payloads).
55    #[serde(default, skip_serializing_if = "Vec::is_empty")]
56    pub evidenced_by: Vec<EvidenceId>,
57}
58
59impl Edge {
60    /// Returns true iff this edge is valid at the given instant.
61    ///
62    /// Edges with no `valid_from` are treated as valid from -infinity.
63    /// Edges with no `valid_to` are treated as still-holding.
64    pub fn valid_at(&self, t: DateTime<Utc>) -> bool {
65        match (self.valid_from, self.valid_to) {
66            (None, None) => true,
67            (Some(f), None) => t >= f,
68            (None, Some(u)) => t < u,
69            (Some(f), Some(u)) => t >= f && t < u,
70        }
71    }
72
73    /// Returns true iff this edge's validity interval overlaps `[t1, t2)`.
74    pub fn valid_between(&self, t1: DateTime<Utc>, t2: DateTime<Utc>) -> bool {
75        if t2 <= t1 {
76            return false;
77        }
78        let lo = self.valid_from.unwrap_or(DateTime::<Utc>::MIN_UTC);
79        let hi = self.valid_to.unwrap_or(DateTime::<Utc>::MAX_UTC);
80        lo < t2 && t1 < hi
81    }
82
83    /// True if the edge is currently active (valid_to is None).
84    pub fn still_holds(&self) -> bool {
85        self.valid_to.is_none()
86    }
87}
88
89#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
90pub struct SubGraph {
91    pub nodes: Vec<Vertex>,
92    pub edges: Vec<Edge>,
93}