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