hippmem_engine/
traverse_api.rs1use crate::{EngineResult, TraverseDirection, TraverseInput, TraverseNode, TraverseOutput};
4use hippmem_core::ids::MemoryId;
5use std::collections::{HashMap, HashSet, VecDeque};
6
7impl crate::Engine {
8 pub fn traverse(&self, input: TraverseInput) -> EngineResult<TraverseOutput> {
10 let max_depth = input.max_depth.clamp(1, 5);
11
12 let units = crate::retrieve_api::load_all_units(self.store.db_arc());
13 let unit_map: HashMap<MemoryId, &hippmem_core::model::unit::MemoryUnit> =
14 units.iter().map(|u| (u.id, u)).collect();
15
16 if !unit_map.contains_key(&input.start_id) {
18 return Err(crate::EngineError::NotFound(input.start_id));
19 }
20
21 let mut visited: HashSet<MemoryId> = HashSet::new();
22 let mut nodes: Vec<TraverseNode> = Vec::new();
23 let mut edges: Vec<crate::EdgeView> = Vec::new();
24 let mut queue: VecDeque<(MemoryId, u8)> = VecDeque::new();
25
26 visited.insert(input.start_id);
27 queue.push_back((input.start_id, 0));
28
29 while let Some((current_id, depth)) = queue.pop_front() {
30 if depth >= max_depth {
31 continue;
32 }
33
34 let current_unit = match unit_map.get(¤t_id) {
35 Some(u) => *u,
36 None => continue,
37 };
38
39 let candidate_edges: Vec<(MemoryId, &hippmem_core::model::links::AssociationLink)> =
41 match input.direction {
42 TraverseDirection::Outgoing => current_unit
43 .links
44 .iter()
45 .map(|l| (l.target_id, l))
46 .collect(),
47 TraverseDirection::Incoming => units
48 .iter()
49 .flat_map(|other| {
50 other.links.iter().filter_map(move |l| {
51 if l.target_id == current_id {
52 Some((other.id, l))
53 } else {
54 None
55 }
56 })
57 })
58 .collect(),
59 TraverseDirection::Both => {
60 let mut both: Vec<(
61 MemoryId,
62 &hippmem_core::model::links::AssociationLink,
63 )> = current_unit
64 .links
65 .iter()
66 .map(|l| (l.target_id, l))
67 .collect();
68 for other in &units {
70 for l in &other.links {
71 if l.target_id == current_id {
72 both.push((other.id, l));
73 }
74 }
75 }
76 both
77 }
78 };
79
80 for (neighbor_id, link) in &candidate_edges {
81 if let Some(ref types) = input.link_types {
83 if !types.contains(&link.link_type) {
84 continue;
85 }
86 }
87
88 edges.push(crate::EdgeView {
90 from: current_id,
91 to: *neighbor_id,
92 link_type: link.link_type,
93 strength: link.strength.value(),
94 confidence: link.confidence.value(),
95 activation_count: link.activation_count,
96 evidence: link.evidence.note.clone().unwrap_or_default(),
97 });
98
99 if visited.insert(*neighbor_id) {
101 if let Some(nu) = unit_map.get(neighbor_id) {
102 nodes.push(TraverseNode {
103 id: *neighbor_id,
104 depth: depth + 1,
105 content_preview: nu.content.raw.chars().take(100).collect(),
106 content_type: nu.content.content_type,
107 importance: nu.understanding.importance.value(),
108 });
109 }
110 queue.push_back((*neighbor_id, depth + 1));
111 }
112 }
113 }
114
115 Ok(TraverseOutput { nodes, edges })
116 }
117}