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