Skip to main content

termivar_scanner/knowledge/
index.rs

1//! Stable secondary indexes and owned read projections.
2
3use std::{
4    collections::{BTreeSet, HashMap},
5    hash::Hash,
6};
7
8use termivar_core::{EntityId, Evidence, EvidenceId, Fact, Hypothesis, KnowledgePredicate};
9
10use crate::knowledge::KnowledgeBase;
11
12impl KnowledgeBase {
13    /// Returns the derived evidence records computed directly from `parent`,
14    /// ordered by evidence ID. Forward lineage (a derived record's exact
15    /// parents) is read from that record's [`termivar_core::EvidenceOrigin`].
16    pub fn derivation_children(&self, parent: &EvidenceId) -> BTreeSet<EvidenceId> {
17        self.read_state()
18            .derivation_children
19            .get(parent)
20            .cloned()
21            .unwrap_or_default()
22    }
23
24    /// Returns an evidence snapshot by ID.
25    pub fn evidence(&self, id: &EvidenceId) -> Option<Evidence> {
26        self.read_state().evidence.get(id).cloned()
27    }
28
29    /// Inspects one evidence record while it remains borrowed from the store.
30    ///
31    /// The callback runs under the knowledge-base read lock and therefore must
32    /// remain short and must not attempt a write through this knowledge base.
33    pub(crate) fn inspect_evidence<R>(
34        &self,
35        id: &EvidenceId,
36        inspect: impl FnOnce(&Evidence) -> R,
37    ) -> Option<R> {
38        let state = self.read_state();
39        state.evidence.get(id).map(inspect)
40    }
41
42    /// Returns a fact snapshot by ID.
43    pub fn fact(&self, id: &str) -> Option<Fact> {
44        self.read_state().facts.get(id).cloned()
45    }
46
47    /// Returns a hypothesis snapshot by ID.
48    pub fn hypothesis(&self, id: &str) -> Option<Hypothesis> {
49        self.read_state().hypotheses.get(id).cloned()
50    }
51
52    /// Inspects one hypothesis while it remains borrowed from the store.
53    ///
54    /// The callback runs under the knowledge-base read lock and therefore must
55    /// remain short and must not attempt a write through this knowledge base.
56    pub(crate) fn inspect_hypothesis<R>(
57        &self,
58        id: &str,
59        inspect: impl FnOnce(&Hypothesis) -> R,
60    ) -> Option<R> {
61        let state = self.read_state();
62        state.hypotheses.get(id).map(inspect)
63    }
64
65    /// Returns evidence describing a subject, ordered by evidence ID.
66    pub fn evidence_for_subject(&self, subject: &EntityId) -> Vec<Evidence> {
67        let state = self.read_state();
68        collect_indexed(state.evidence_by_subject.get(subject), &state.evidence)
69    }
70
71    /// Returns evidence matching a predicate, ordered by evidence ID.
72    pub fn evidence_for_predicate(&self, predicate: &KnowledgePredicate) -> Vec<Evidence> {
73        let state = self.read_state();
74        collect_indexed(state.evidence_by_predicate.get(predicate), &state.evidence)
75    }
76
77    /// Returns facts describing a subject, ordered by fact ID.
78    pub fn facts_for_subject(&self, subject: &EntityId) -> Vec<Fact> {
79        let state = self.read_state();
80        collect_indexed(state.facts_by_subject.get(subject), &state.facts)
81    }
82
83    /// Returns facts matching a predicate, ordered by fact ID.
84    pub fn facts_for_predicate(&self, predicate: &KnowledgePredicate) -> Vec<Fact> {
85        let state = self.read_state();
86        collect_indexed(state.facts_by_predicate.get(predicate), &state.facts)
87    }
88
89    /// Returns hypotheses describing a subject, ordered by hypothesis ID.
90    pub fn hypotheses_for_subject(&self, subject: &EntityId) -> Vec<Hypothesis> {
91        let state = self.read_state();
92        collect_indexed(state.hypotheses_by_subject.get(subject), &state.hypotheses)
93    }
94
95    /// Returns hypotheses matching a predicate, ordered by hypothesis ID.
96    pub fn hypotheses_for_predicate(&self, predicate: &KnowledgePredicate) -> Vec<Hypothesis> {
97        let state = self.read_state();
98        collect_indexed(
99            state.hypotheses_by_predicate.get(predicate),
100            &state.hypotheses,
101        )
102    }
103}
104
105pub(super) fn index<K, I>(index: &mut HashMap<K, BTreeSet<I>>, key: K, id: I)
106where
107    K: Eq + Hash,
108    I: Ord,
109{
110    index.entry(key).or_default().insert(id);
111}
112
113pub(super) fn collect_indexed<K, V>(ids: Option<&BTreeSet<K>>, values: &HashMap<K, V>) -> Vec<V>
114where
115    K: Eq + Hash + Ord,
116    V: Clone,
117{
118    ids.into_iter()
119        .flatten()
120        .filter_map(|id| values.get(id).cloned())
121        .collect()
122}