1use crate::models::graph::{
25 Entity, EntityId, EntityMention, EntityQuery, EntityType, Relationship, RelationshipQuery,
26 RelationshipType, TraversalResult,
27};
28use crate::models::temporal::BitemporalPoint;
29use crate::models::{Domain, MemoryId};
30use crate::storage::traits::graph::{GraphBackend, GraphStats};
31use crate::{Error, Result};
32use std::sync::Arc;
33
34pub struct GraphService<B: GraphBackend> {
48 backend: Arc<B>,
49}
50
51impl<B: GraphBackend> GraphService<B> {
52 pub fn new(backend: B) -> Self {
54 Self {
55 backend: Arc::new(backend),
56 }
57 }
58
59 #[must_use]
61 pub const fn with_shared_backend(backend: Arc<B>) -> Self {
62 Self { backend }
63 }
64
65 #[must_use]
67 pub fn backend(&self) -> &B {
68 &self.backend
69 }
70
71 pub fn store_entity(&self, entity: &Entity) -> Result<()> {
83 self.backend.store_entity(entity)
84 }
85
86 pub fn store_entity_deduped(&self, entity: &Entity) -> Result<EntityId> {
108 let existing = self.backend.find_entities_by_name(
110 &entity.name,
111 Some(entity.entity_type),
112 Some(&entity.domain),
113 10, )?;
115
116 let name_lower = entity.name.to_lowercase();
118 let exact_match = existing
119 .into_iter()
120 .find(|e| e.name.to_lowercase() == name_lower);
121
122 if let Some(mut existing_entity) = exact_match {
123 if entity.confidence > existing_entity.confidence {
125 existing_entity.confidence = entity.confidence;
126 }
127
128 let existing_lower: std::collections::HashSet<String> = existing_entity
131 .aliases
132 .iter()
133 .map(|a| a.to_lowercase())
134 .chain(std::iter::once(existing_entity.name.to_lowercase()))
135 .collect();
136
137 let new_aliases: Vec<String> = entity
138 .aliases
139 .iter()
140 .filter(|alias| !existing_lower.contains(&alias.to_lowercase()))
141 .cloned()
142 .collect();
143 existing_entity.aliases.extend(new_aliases);
144
145 existing_entity.mention_count = existing_entity.mention_count.saturating_add(1);
147
148 self.backend.store_entity(&existing_entity)?;
150
151 Ok(existing_entity.id)
152 } else {
153 self.backend.store_entity(entity)?;
155 Ok(entity.id.clone())
156 }
157 }
158
159 pub fn get_entity(&self, id: &EntityId) -> Result<Option<Entity>> {
169 self.backend.get_entity(id)
170 }
171
172 pub fn query_entities(&self, query: &EntityQuery) -> Result<Vec<Entity>> {
178 self.backend.query_entities(query)
179 }
180
181 pub fn find_by_type(&self, entity_type: EntityType, limit: usize) -> Result<Vec<Entity>> {
189 let query = EntityQuery::new().with_type(entity_type).with_limit(limit);
190 self.backend.query_entities(&query)
191 }
192
193 pub fn find_by_name(
201 &self,
202 name: &str,
203 entity_type: Option<EntityType>,
204 domain: Option<&Domain>,
205 limit: usize,
206 ) -> Result<Vec<Entity>> {
207 self.backend
208 .find_entities_by_name(name, entity_type, domain, limit)
209 }
210
211 pub fn delete_entity(&self, id: &EntityId) -> Result<bool> {
221 self.backend.delete_entity(id)
222 }
223
224 pub fn merge_entities(&self, entity_ids: &[EntityId], canonical_name: &str) -> Result<Entity> {
245 if entity_ids.is_empty() {
246 return Err(Error::OperationFailed {
247 operation: "merge_entities".to_string(),
248 cause: "No entity IDs provided".to_string(),
249 });
250 }
251
252 self.backend.merge_entities(entity_ids, canonical_name)
253 }
254
255 pub fn find_duplicates(&self, entity: &Entity, threshold: f32) -> Result<Vec<Entity>> {
264 let candidates = self.backend.find_entities_by_name(
266 &entity.name,
267 Some(entity.entity_type),
268 Some(&entity.domain),
269 20,
270 )?;
271
272 let duplicates: Vec<Entity> = candidates
274 .into_iter()
275 .filter(|e| e.id != entity.id && name_similarity(&e.name, &entity.name) >= threshold)
276 .collect();
277
278 Ok(duplicates)
279 }
280
281 pub fn store_relationship(&self, relationship: &Relationship) -> Result<()> {
293 self.backend.store_relationship(relationship)
294 }
295
296 pub fn relate(
306 &self,
307 from: &EntityId,
308 to: &EntityId,
309 relationship_type: RelationshipType,
310 ) -> Result<Relationship> {
311 if self.backend.get_entity(from)?.is_none() {
313 return Err(Error::OperationFailed {
314 operation: "relate".to_string(),
315 cause: format!("From entity not found: {}", from.as_str()),
316 });
317 }
318 if self.backend.get_entity(to)?.is_none() {
319 return Err(Error::OperationFailed {
320 operation: "relate".to_string(),
321 cause: format!("To entity not found: {}", to.as_str()),
322 });
323 }
324
325 let relationship = Relationship::new(from.clone(), to.clone(), relationship_type);
326 self.backend.store_relationship(&relationship)?;
327 Ok(relationship)
328 }
329
330 pub fn query_relationships(&self, query: &RelationshipQuery) -> Result<Vec<Relationship>> {
336 self.backend.query_relationships(query)
337 }
338
339 pub fn get_outgoing_relationships(&self, entity_id: &EntityId) -> Result<Vec<Relationship>> {
345 let query = RelationshipQuery::new().from(entity_id.clone());
346 self.backend.query_relationships(&query)
347 }
348
349 pub fn get_incoming_relationships(&self, entity_id: &EntityId) -> Result<Vec<Relationship>> {
355 let query = RelationshipQuery::new().to(entity_id.clone());
356 self.backend.query_relationships(&query)
357 }
358
359 pub fn delete_relationships(&self, query: &RelationshipQuery) -> Result<usize> {
369 self.backend.delete_relationships(query)
370 }
371
372 pub fn get_relationship_types(
378 &self,
379 from: &EntityId,
380 to: &EntityId,
381 ) -> Result<Vec<RelationshipType>> {
382 self.backend.get_relationship_types(from, to)
383 }
384
385 pub fn record_mention(&self, entity_id: &EntityId, memory_id: &MemoryId) -> Result<()> {
395 let mention = EntityMention::new(entity_id.clone(), memory_id.clone());
396 self.backend.store_mention(&mention)
397 }
398
399 pub fn get_mentions(&self, entity_id: &EntityId) -> Result<Vec<EntityMention>> {
405 self.backend.get_mentions_for_entity(entity_id)
406 }
407
408 pub fn get_entities_in_memory(&self, memory_id: &MemoryId) -> Result<Vec<Entity>> {
414 self.backend.get_entities_in_memory(memory_id)
415 }
416
417 pub fn remove_mentions_for_memory(&self, memory_id: &MemoryId) -> Result<usize> {
427 self.backend.delete_mentions_for_memory(memory_id)
428 }
429
430 pub fn traverse(
453 &self,
454 start: &EntityId,
455 max_depth: u32,
456 relationship_types: Option<&[RelationshipType]>,
457 min_confidence: Option<f32>,
458 ) -> Result<TraversalResult> {
459 self.backend
460 .traverse(start, max_depth, relationship_types, min_confidence)
461 }
462
463 pub fn find_path(
479 &self,
480 from: &EntityId,
481 to: &EntityId,
482 max_depth: u32,
483 ) -> Result<Option<TraversalResult>> {
484 self.backend.find_path(from, to, max_depth)
485 }
486
487 pub fn get_neighbors(&self, entity_id: &EntityId, depth: u32) -> Result<Vec<Entity>> {
495 let result = self.backend.traverse(entity_id, depth, None, None)?;
496 Ok(result
498 .entities
499 .into_iter()
500 .filter(|e| e.id != *entity_id)
501 .collect())
502 }
503
504 pub fn query_entities_at(
518 &self,
519 query: &EntityQuery,
520 point: &BitemporalPoint,
521 ) -> Result<Vec<Entity>> {
522 self.backend.query_entities_at(query, point)
523 }
524
525 pub fn query_relationships_at(
531 &self,
532 query: &RelationshipQuery,
533 point: &BitemporalPoint,
534 ) -> Result<Vec<Relationship>> {
535 self.backend.query_relationships_at(query, point)
536 }
537
538 pub fn close_entity_valid_time(&self, id: &EntityId, end_time: i64) -> Result<()> {
546 self.backend.close_entity_valid_time(id, end_time)
547 }
548
549 pub fn close_relationship_valid_time(
555 &self,
556 from: &EntityId,
557 to: &EntityId,
558 relationship_type: RelationshipType,
559 end_time: i64,
560 ) -> Result<()> {
561 self.backend
562 .close_relationship_valid_time(from, to, relationship_type, end_time)
563 }
564
565 pub fn get_stats(&self) -> Result<GraphStats> {
575 self.backend.get_stats()
576 }
577
578 pub fn clear(&self) -> Result<()> {
586 self.backend.clear()
587 }
588}
589
590fn name_similarity(a: &str, b: &str) -> f32 {
592 let a_lower = a.to_lowercase();
593 let b_lower = b.to_lowercase();
594
595 if a_lower == b_lower {
596 return 1.0;
597 }
598
599 let a_bigrams: std::collections::HashSet<_> = a_lower
600 .chars()
601 .collect::<Vec<_>>()
602 .windows(2)
603 .map(|w| (w[0], w[1]))
604 .collect();
605
606 let b_bigrams: std::collections::HashSet<_> = b_lower
607 .chars()
608 .collect::<Vec<_>>()
609 .windows(2)
610 .map(|w| (w[0], w[1]))
611 .collect();
612
613 if a_bigrams.is_empty() || b_bigrams.is_empty() {
614 return 0.0;
615 }
616
617 let intersection = a_bigrams.intersection(&b_bigrams).count();
618 let union = a_bigrams.union(&b_bigrams).count();
619
620 if union == 0 {
621 0.0
622 } else {
623 intersection as f32 / union as f32
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630 use crate::storage::graph::InMemoryGraphBackend;
631
632 fn create_service() -> GraphService<InMemoryGraphBackend> {
633 GraphService::new(InMemoryGraphBackend::new())
634 }
635
636 fn create_entity(name: &str, entity_type: EntityType) -> Entity {
637 Entity::new(entity_type, name, Domain::for_user())
638 }
639
640 #[test]
641 fn test_store_and_get_entity() {
642 let service = create_service();
643 let entity = create_entity("Alice", EntityType::Person);
644
645 service.store_entity(&entity).unwrap();
646 let retrieved = service.get_entity(&entity.id).unwrap();
647
648 assert!(retrieved.is_some());
649 assert_eq!(retrieved.unwrap().name, "Alice");
650 }
651
652 #[test]
653 fn test_find_by_type() {
654 let service = create_service();
655
656 service
657 .store_entity(&create_entity("Alice", EntityType::Person))
658 .unwrap();
659 service
660 .store_entity(&create_entity("Bob", EntityType::Person))
661 .unwrap();
662 service
663 .store_entity(&create_entity("Acme", EntityType::Organization))
664 .unwrap();
665
666 let people = service.find_by_type(EntityType::Person, 10).unwrap();
667 assert_eq!(people.len(), 2);
668
669 let orgs = service.find_by_type(EntityType::Organization, 10).unwrap();
670 assert_eq!(orgs.len(), 1);
671 }
672
673 #[test]
674 fn test_relate_entities() {
675 let service = create_service();
676
677 let alice = create_entity("Alice", EntityType::Person);
678 let acme = create_entity("Acme", EntityType::Organization);
679
680 service.store_entity(&alice).unwrap();
681 service.store_entity(&acme).unwrap();
682
683 let rel = service
684 .relate(&alice.id, &acme.id, RelationshipType::WorksAt)
685 .unwrap();
686
687 assert_eq!(rel.from_entity, alice.id);
688 assert_eq!(rel.to_entity, acme.id);
689 assert_eq!(rel.relationship_type, RelationshipType::WorksAt);
690 }
691
692 #[test]
693 fn test_relate_nonexistent_entity() {
694 let service = create_service();
695
696 let alice = create_entity("Alice", EntityType::Person);
697 service.store_entity(&alice).unwrap();
698
699 let fake_id = EntityId::generate();
700 let result = service.relate(&alice.id, &fake_id, RelationshipType::WorksAt);
701
702 assert!(result.is_err());
703 }
704
705 #[test]
706 fn test_get_neighbors() {
707 let service = create_service();
708
709 let alice = create_entity("Alice", EntityType::Person);
710 let bob = create_entity("Bob", EntityType::Person);
711 let acme = create_entity("Acme", EntityType::Organization);
712
713 service.store_entity(&alice).unwrap();
714 service.store_entity(&bob).unwrap();
715 service.store_entity(&acme).unwrap();
716
717 service
718 .relate(&alice.id, &bob.id, RelationshipType::RelatesTo)
719 .unwrap();
720 service
721 .relate(&alice.id, &acme.id, RelationshipType::WorksAt)
722 .unwrap();
723
724 let neighbors = service.get_neighbors(&alice.id, 1).unwrap();
725 assert_eq!(neighbors.len(), 2);
726 }
727
728 #[test]
729 fn test_find_path() {
730 let service = create_service();
731
732 let a = create_entity("A", EntityType::Concept);
733 let b = create_entity("B", EntityType::Concept);
734 let c = create_entity("C", EntityType::Concept);
735
736 service.store_entity(&a).unwrap();
737 service.store_entity(&b).unwrap();
738 service.store_entity(&c).unwrap();
739
740 service
741 .relate(&a.id, &b.id, RelationshipType::RelatesTo)
742 .unwrap();
743 service
744 .relate(&b.id, &c.id, RelationshipType::RelatesTo)
745 .unwrap();
746
747 let path = service.find_path(&a.id, &c.id, 5).unwrap();
748 assert!(path.is_some());
749 assert_eq!(path.unwrap().entities.len(), 3);
750 }
751
752 #[test]
753 fn test_record_and_get_mentions() {
754 let service = create_service();
755
756 let alice = create_entity("Alice", EntityType::Person);
757 service.store_entity(&alice).unwrap();
758
759 let mem1 = MemoryId::new("mem_1");
760 let mem2 = MemoryId::new("mem_2");
761
762 service.record_mention(&alice.id, &mem1).unwrap();
763 service.record_mention(&alice.id, &mem2).unwrap();
764
765 let mentions = service.get_mentions(&alice.id).unwrap();
766 assert_eq!(mentions.len(), 2);
767 }
768
769 #[test]
770 fn test_get_stats() {
771 let service = create_service();
772
773 service
774 .store_entity(&create_entity("Alice", EntityType::Person))
775 .unwrap();
776 service
777 .store_entity(&create_entity("Bob", EntityType::Person))
778 .unwrap();
779
780 let stats = service.get_stats().unwrap();
781 assert_eq!(stats.entity_count, 2);
782 }
783
784 #[test]
785 fn test_name_similarity() {
786 assert!((name_similarity("Alice", "Alice") - 1.0).abs() < f32::EPSILON);
787 assert!((name_similarity("alice", "ALICE") - 1.0).abs() < f32::EPSILON);
788 assert!(name_similarity("Alice", "Alicia") >= 0.5);
790 assert!(name_similarity("Alice", "Bob") < 0.3);
791 }
792
793 #[test]
794 fn test_find_duplicates() {
795 let service = create_service();
796
797 let alice1 = create_entity("Alice Smith", EntityType::Person);
798 let alice2 = create_entity("Alice Smithson", EntityType::Person);
799 let bob = create_entity("Bob Jones", EntityType::Person);
800
801 service.store_entity(&alice1).unwrap();
802 service.store_entity(&alice2).unwrap();
803 service.store_entity(&bob).unwrap();
804
805 let duplicates = service.find_duplicates(&alice1, 0.5).unwrap();
806 assert_eq!(duplicates.len(), 1);
807 assert_eq!(duplicates[0].name, "Alice Smithson");
808 }
809
810 #[test]
811 fn test_store_entity_upsert() {
812 let service = create_service();
813 let mut entity = create_entity("Alice", EntityType::Person);
814 service.store_entity(&entity).unwrap();
815
816 entity.aliases = vec!["Ali".to_string()];
818 entity.confidence = 0.95;
819 service.store_entity(&entity).unwrap();
820
821 let retrieved = service.get_entity(&entity.id).unwrap().unwrap();
822 assert_eq!(retrieved.aliases, vec!["Ali".to_string()]);
823 assert!((retrieved.confidence - 0.95).abs() < f32::EPSILON);
824 }
825
826 #[test]
827 fn test_delete_entity() {
828 let service = create_service();
829 let entity = create_entity("Alice", EntityType::Person);
830 service.store_entity(&entity).unwrap();
831
832 service.delete_entity(&entity.id).unwrap();
833 let retrieved = service.get_entity(&entity.id).unwrap();
834 assert!(retrieved.is_none());
835 }
836
837 #[test]
838 fn test_relationship_with_properties() {
839 let service = create_service();
840
841 let rust = create_entity("Rust", EntityType::Technology);
842 let cargo = create_entity("Cargo", EntityType::Technology);
843
844 service.store_entity(&rust).unwrap();
845 service.store_entity(&cargo).unwrap();
846
847 let rel = service
848 .relate(&rust.id, &cargo.id, RelationshipType::Uses)
849 .unwrap();
850
851 assert_eq!(rel.relationship_type, RelationshipType::Uses);
853 assert_eq!(rel.from_entity, rust.id);
854 assert_eq!(rel.to_entity, cargo.id);
855 }
856
857 #[test]
858 fn test_get_outgoing_relationships() {
859 let service = create_service();
860
861 let alice = create_entity("Alice", EntityType::Person);
862 let bob = create_entity("Bob", EntityType::Person);
863 let acme = create_entity("Acme", EntityType::Organization);
864
865 service.store_entity(&alice).unwrap();
866 service.store_entity(&bob).unwrap();
867 service.store_entity(&acme).unwrap();
868
869 service
870 .relate(&alice.id, &bob.id, RelationshipType::RelatesTo)
871 .unwrap();
872 service
873 .relate(&alice.id, &acme.id, RelationshipType::WorksAt)
874 .unwrap();
875
876 let rels = service.get_outgoing_relationships(&alice.id).unwrap();
877 assert_eq!(rels.len(), 2);
878 }
879
880 #[test]
881 fn test_no_path_found() {
882 let service = create_service();
883
884 let a = create_entity("A", EntityType::Concept);
885 let b = create_entity("B", EntityType::Concept);
886
887 service.store_entity(&a).unwrap();
888 service.store_entity(&b).unwrap();
889
890 let path = service.find_path(&a.id, &b.id, 5).unwrap();
892 assert!(path.is_none());
893 }
894
895 #[test]
896 fn test_traversal_depth_limit() {
897 let service = create_service();
898
899 let a = create_entity("A", EntityType::Concept);
901 let b = create_entity("B", EntityType::Concept);
902 let c = create_entity("C", EntityType::Concept);
903 let d = create_entity("D", EntityType::Concept);
904
905 service.store_entity(&a).unwrap();
906 service.store_entity(&b).unwrap();
907 service.store_entity(&c).unwrap();
908 service.store_entity(&d).unwrap();
909
910 service
911 .relate(&a.id, &b.id, RelationshipType::RelatesTo)
912 .unwrap();
913 service
914 .relate(&b.id, &c.id, RelationshipType::RelatesTo)
915 .unwrap();
916 service
917 .relate(&c.id, &d.id, RelationshipType::RelatesTo)
918 .unwrap();
919
920 let neighbors = service.get_neighbors(&a.id, 1).unwrap();
922 assert_eq!(neighbors.len(), 1);
923 assert_eq!(neighbors[0].name, "B");
924
925 let neighbors = service.get_neighbors(&a.id, 2).unwrap();
927 assert_eq!(neighbors.len(), 2);
928 }
929
930 #[test]
931 fn test_merge_entities() {
932 let service = create_service();
933
934 let alice1 = create_entity("Alice Smith", EntityType::Person);
935 let alice2 = create_entity("A. Smith", EntityType::Person);
936 let bob = create_entity("Bob", EntityType::Person);
937
938 service.store_entity(&alice1).unwrap();
939 service.store_entity(&alice2).unwrap();
940 service.store_entity(&bob).unwrap();
941
942 service
944 .relate(&alice2.id, &bob.id, RelationshipType::RelatesTo)
945 .unwrap();
946
947 let merged = service
949 .merge_entities(&[alice1.id, alice2.id.clone()], "Alice Smith")
950 .unwrap();
951
952 assert_eq!(merged.name, "Alice Smith");
954
955 let retrieved = service.get_entity(&alice2.id).unwrap();
957 assert!(retrieved.is_none());
958 }
959
960 #[test]
961 fn test_find_by_name() {
962 let service = create_service();
963
964 let alice = create_entity("Alice", EntityType::Person);
965 let bob = create_entity("Bob", EntityType::Person);
966
967 service.store_entity(&alice).unwrap();
968 service.store_entity(&bob).unwrap();
969
970 let found = service.find_by_name("Alice", None, None, 10).unwrap();
971 assert_eq!(found.len(), 1);
972 assert_eq!(found[0].name, "Alice");
973 }
974
975 #[test]
976 fn test_find_by_name_with_type_filter() {
977 let service = create_service();
978
979 let alice_person = create_entity("Alice", EntityType::Person);
980 let alice_tech = create_entity("Alice", EntityType::Technology);
981
982 service.store_entity(&alice_person).unwrap();
983 service.store_entity(&alice_tech).unwrap();
984
985 let found = service
986 .find_by_name("Alice", Some(EntityType::Person), None, 10)
987 .unwrap();
988 assert_eq!(found.len(), 1);
989 assert_eq!(found[0].entity_type, EntityType::Person);
990 }
991
992 #[test]
993 fn test_empty_graph_stats() {
994 let service = create_service();
995 let stats = service.get_stats().unwrap();
996
997 assert_eq!(stats.entity_count, 0);
998 assert_eq!(stats.relationship_count, 0);
999 }
1000
1001 #[test]
1002 fn test_record_duplicate_mention() {
1003 let service = create_service();
1004
1005 let alice = create_entity("Alice", EntityType::Person);
1006 service.store_entity(&alice).unwrap();
1007
1008 let mem = MemoryId::new("mem_1");
1009
1010 service.record_mention(&alice.id, &mem).unwrap();
1012 service.record_mention(&alice.id, &mem).unwrap();
1013
1014 let mentions = service.get_mentions(&alice.id).unwrap();
1016 assert!(!mentions.is_empty());
1017 }
1018}