weavatrix_memory/
graph_projection.rs1use crate::{
2 domain::{Confidence as MemoryConfidence, MemoryView},
3 error::{MemoryError, Result},
4 id::EntityId,
5};
6use std::{collections::BTreeMap, str::FromStr};
7use weavatrix_graph::{
8 AttributeValue, Confidence, Edge, EdgeKind, EvidenceKind, Graph, Node, NodeId, NodeKind,
9 Provenance,
10};
11
12pub fn project_graph(view: &MemoryView) -> Result<Graph> {
18 let mut ids = BTreeMap::new();
19 let mut nodes = Vec::with_capacity(view.nodes.len());
20 for memory_node in &view.nodes {
21 let mut node = Node::new(
22 memory_node.id.as_str(),
23 memory_node.label.clone(),
24 NodeKind::from_str(&memory_node.kind)?,
25 )?;
26 if let Some(repository) = &memory_node.repository {
27 node = node.with_attribute("memory.repository", repository.clone());
28 }
29 if let Some(branch) = &memory_node.branch {
30 node = node.with_attribute("memory.branch", branch.clone());
31 }
32 for (key, value) in &memory_node.attributes {
33 node = node.with_attribute(format!("memory.{key}"), value.clone());
34 }
35 ids.insert(memory_node.id.clone(), node.id.clone());
36 nodes.push(node);
37 }
38
39 let mut edges = Vec::with_capacity(view.facts.len());
40 for fact in &view.facts {
41 let source = endpoint(&ids, &fact.source)?;
42 let target = endpoint(&ids, &fact.target)?;
43 let primary = &fact.evidence[0];
44 let provenance = Provenance::new(
45 primary.source.clone(),
46 EvidenceKind::from_str(&primary.kind)?,
47 graph_confidence(fact.confidence),
48 )?
49 .with_detail(
50 primary
51 .locator
52 .clone()
53 .unwrap_or_else(|| fact.id.to_string()),
54 );
55 let evidence = fact
56 .evidence
57 .iter()
58 .map(|item| {
59 AttributeValue::String(format!(
60 "{}:{}:{}",
61 item.kind,
62 item.source,
63 item.locator.as_deref().unwrap_or("")
64 ))
65 })
66 .collect::<Vec<_>>();
67 let mut edge = Edge::new(
68 source,
69 target,
70 EdgeKind::from_str(&fact.relation)?,
71 provenance,
72 )
73 .with_attribute("memory.fact_id", fact.id.to_string())
74 .with_attribute("memory.valid_from", fact.valid_from.as_unix_micros())
75 .with_attribute("memory.recorded_at", fact.recorded_at.as_unix_micros())
76 .with_attribute(
77 "memory.confidence_bps",
78 u64::from(fact.confidence.basis_points()),
79 )
80 .with_attribute("memory.evidence", AttributeValue::List(evidence));
81 if let Some(valid_until) = fact.valid_until {
82 edge = edge.with_attribute("memory.valid_until", valid_until.as_unix_micros());
83 }
84 if let Some(supersedes) = &fact.supersedes {
85 edge = edge.with_attribute("memory.supersedes", supersedes.to_string());
86 }
87 edges.push(edge);
88 }
89 Ok(Graph::try_from_sorted_nodes(nodes, edges)?)
90}
91
92fn endpoint(ids: &BTreeMap<EntityId, NodeId>, id: &EntityId) -> Result<NodeId> {
93 ids.get(id)
94 .cloned()
95 .ok_or_else(|| MemoryError::MissingEntity { id: id.to_string() })
96}
97
98const fn graph_confidence(confidence: MemoryConfidence) -> Confidence {
99 match confidence.basis_points() {
100 9_500..=10_000 => Confidence::Exact,
101 8_000..=9_499 => Confidence::High,
102 5_000..=7_999 => Confidence::Medium,
103 _ => Confidence::Low,
104 }
105}