1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Graph domain: knowledge graph nodes, edges and thesauri.
use ahash::AHashMap;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::collections::hash_map::Iter;
use std::iter::IntoIterator;
#[cfg(feature = "medical")]
use crate::medical_types;
use crate::term::{NormalizedTerm, NormalizedTermValue};
/// A directed relationship between two nodes in the knowledge graph.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Edge {
/// ID of the edge (u64)
pub id: u64,
/// Rank of the edge
pub rank: u64,
/// A hashmap of `document_id` to `rank`
pub doc_hash: AHashMap<String, u64>,
/// Medical edge type (only available with the `medical` feature)
#[cfg(feature = "medical")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub edge_type: Option<medical_types::MedicalEdgeType>,
}
impl Edge {
/// Creates an edge with the given ID pointing to `document_id` with an initial rank of 1.
pub fn new(id: u64, document_id: String) -> Self {
let mut doc_hash = AHashMap::new();
doc_hash.insert(document_id, 1);
Self {
id,
rank: 1,
doc_hash,
#[cfg(feature = "medical")]
edge_type: None,
}
}
}
/// A `Node` represents single concept and its connections to other concepts.
///
/// Each node can have multiple edges to other nodes
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Node {
/// Unique identifier of the node (u64)
pub id: u64,
/// Number of co-occurrences
pub rank: u64,
/// List of connected edges
pub connected_with: HashSet<u64>,
/// Medical node type (only available with the `medical` feature)
#[cfg(feature = "medical")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub node_type: Option<medical_types::MedicalNodeType>,
/// Human-readable term for this node (only available with the `medical` feature)
#[cfg(feature = "medical")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub term: Option<String>,
/// SNOMED CT concept identifier (only available with the `medical` feature)
#[cfg(feature = "medical")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub snomed_id: Option<u64>,
}
impl Node {
/// Create a new node with a given id and edge
pub fn new(id: u64, edge: Edge) -> Self {
let mut connected_with = HashSet::new();
connected_with.insert(edge.id);
Self {
id,
rank: 1,
connected_with,
#[cfg(feature = "medical")]
node_type: None,
#[cfg(feature = "medical")]
term: None,
#[cfg(feature = "medical")]
snomed_id: None,
}
}
// pub fn sort_edges_by_value(&self) {
// // let count_b: BTreeMap<&u64, &Edge> =
// // self.connected_with.iter().map(|(k, v)| (v, k)).collect();
// // for (k, v) in self.connected_with.iter().map(|(k, v)| (v.rank, k)) {
// // log::warn!("k {:?} v {:?}", k, v);
// // }
// log::warn!("Connected with {:?}", self.connected_with);
// }
}
/// A thesaurus is a dictionary with synonyms which map to upper-level concepts.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct Thesaurus {
/// Name of the thesaurus
name: String,
/// The inner hashmap of normalized terms
data: AHashMap<NormalizedTermValue, NormalizedTerm>,
/// SHA-256 hash of the source markdown files used to build this thesaurus.
/// Used for cache invalidation: when the hash changes, the thesaurus is rebuilt.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_hash: Option<String>,
}
impl Thesaurus {
/// Create a new, empty thesaurus
pub fn new(name: String) -> Self {
Self {
name,
data: AHashMap::new(),
source_hash: None,
}
}
/// Set the source hash for cache invalidation tracking.
pub fn with_source_hash(mut self, hash: String) -> Self {
self.source_hash = Some(hash);
self
}
/// Get the name of the thesaurus
pub fn name(&self) -> &str {
&self.name
}
/// Inserts a key-value pair into the thesaurus.
pub fn insert(&mut self, key: NormalizedTermValue, value: NormalizedTerm) {
self.data.insert(key, value);
}
/// Get the length of the thesaurus
pub fn len(&self) -> usize {
self.data.len()
}
/// Check if the thesaurus is empty
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
/// Custom `get` method for the thesaurus, which accepts a
/// `NormalizedTermValue` and returns a reference to the
/// `NormalizedTerm`.
pub fn get(&self, key: &NormalizedTermValue) -> Option<&NormalizedTerm> {
self.data.get(key)
}
/// Returns an iterator over all normalised term keys in the thesaurus.
pub fn keys(
&self,
) -> std::collections::hash_map::Keys<'_, NormalizedTermValue, NormalizedTerm> {
self.data.keys()
}
}
// Implement `IntoIterator` for a reference to `Thesaurus`
impl<'a> IntoIterator for &'a Thesaurus {
type Item = (&'a NormalizedTermValue, &'a NormalizedTerm);
type IntoIter = Iter<'a, NormalizedTermValue, NormalizedTerm>;
fn into_iter(self) -> Self::IntoIter {
self.data.iter()
}
}