weavatrix_memory/extraction/linker/
catalog.rs1use super::{EntityLinker, scoring::exact_key};
2use crate::{
3 domain::MemoryView,
4 error::{MemoryError, Result},
5 extraction::key::normalized,
6};
7use std::{collections::HashMap, hash::Hash};
8
9#[derive(Debug, Clone)]
10pub(super) enum IndexBucket {
11 One(usize),
12 Many(Vec<usize>),
13}
14
15impl EntityLinker {
16 pub fn from_view(view: &MemoryView) -> Result<Self> {
23 let mut linker = Self {
24 nodes: Vec::with_capacity(view.nodes.len()),
25 by_id: HashMap::with_capacity(view.nodes.len()),
26 by_label: HashMap::with_capacity(view.nodes.len()),
27 by_alias: HashMap::with_capacity(view.nodes.len()),
28 by_external_id: HashMap::with_capacity(view.nodes.len()),
29 };
30 for (index, node) in view.nodes.iter().enumerate() {
31 node.validate()?;
32 if linker.by_id.insert(node.id.clone(), index).is_some() {
33 return Err(MemoryError::InvalidValue {
34 field: "entity_linker.catalog",
35 reason: "entity identifiers must be unique",
36 });
37 }
38 let kind = normalized(&node.kind);
39 linker.nodes.push(super::CatalogEntity {
40 id: node.id.clone(),
41 kind: kind.clone(),
42 repository: node.repository.clone(),
43 branch: node.branch.clone(),
44 });
45 insert_index(
46 &mut linker.by_label,
47 (kind.clone(), normalized(&node.label)),
48 index,
49 );
50 for (key, value) in &node.attributes {
51 if key == "alias" || key.starts_with("alias.") {
52 crate::domain::validate_text("entity_linker.alias", value)?;
53 insert_index(
54 &mut linker.by_alias,
55 (kind.clone(), normalized(value)),
56 index,
57 );
58 } else if key.starts_with("external_id.") {
59 crate::domain::validate_text("entity_linker.external_id", value)?;
60 insert_index(
61 &mut linker.by_external_id,
62 (kind.clone(), key.clone(), exact_key(value)),
63 index,
64 );
65 }
66 }
67 }
68 Ok(linker)
69 }
70}
71
72fn insert_index<K: Eq + Hash>(index: &mut HashMap<K, IndexBucket>, key: K, value: usize) {
73 match index.entry(key) {
74 std::collections::hash_map::Entry::Vacant(entry) => {
75 entry.insert(IndexBucket::One(value));
76 }
77 std::collections::hash_map::Entry::Occupied(mut entry) => match entry.get_mut() {
78 IndexBucket::One(first) => {
79 let first = *first;
80 entry.insert(IndexBucket::Many(vec![first, value]));
81 }
82 IndexBucket::Many(values) => values.push(value),
83 },
84 }
85}