Skip to main content

qualia_core_db/solvers/grounding/
evaluate.rs

1//! Turn the support arithmetic into a verdict, and resolve provenance citations into
2//! the facts the arithmetic needs.
3
4use super::claim_support::report;
5use super::{GroundingResolver, GroundingThresholds, GroundingVerdict};
6use crate::NQuin;
7
8/// Grounding score for `claim` against the resolved cited `facts`, as a verdict under
9/// `thresholds`. Empty evidence ⇒ `Ungrounded(0.0)` (fail closed).
10pub fn evaluate_grounding(
11    claim: &NQuin,
12    facts: &[NQuin],
13    thresholds: GroundingThresholds,
14) -> GroundingVerdict {
15    let score = report(claim, facts).score;
16    grounding_verdict(score, thresholds)
17}
18
19/// Partition a raw grounding score into a verdict.
20pub fn grounding_verdict(score: f64, thresholds: GroundingThresholds) -> GroundingVerdict {
21    if score >= thresholds.permit {
22        GroundingVerdict::Grounded { score }
23    } else if score >= thresholds.deny {
24        GroundingVerdict::Weak { score }
25    } else {
26        GroundingVerdict::Ungrounded { score }
27    }
28}
29
30/// Resolve a slice of provenance citation hashes to their fact quins via `resolver`,
31/// dropping any that do not resolve. The output is what [`evaluate_grounding`] consumes.
32pub fn resolve_citations(citations: &[u64], resolver: &dyn GroundingResolver) -> Vec<NQuin> {
33    citations
34        .iter()
35        .filter_map(|&h| resolver.resolve(h))
36        .collect()
37}
38
39/// End-to-end gate input: resolve the citations and grade the claim in one call. If no
40/// citation resolves (no evidence available), returns `Ungrounded(0.0)` — fail closed.
41pub fn evaluate_output_grounding(
42    claim: &NQuin,
43    citations: &[u64],
44    resolver: &dyn GroundingResolver,
45    thresholds: GroundingThresholds,
46) -> GroundingVerdict {
47    let facts = resolve_citations(citations, resolver);
48    if facts.is_empty() {
49        return GroundingVerdict::Ungrounded { score: 0.0 };
50    }
51    evaluate_grounding(claim, &facts, thresholds)
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use std::collections::HashMap;
58
59    fn quin(s: u64, p: u64, o: u64) -> NQuin {
60        NQuin {
61            subject: s,
62            predicate: p,
63            object: o,
64            context: 0,
65            metadata: 0,
66            parity: 0,
67        }
68    }
69
70    /// A stub store mapping citation hash → fact quin.
71    struct MapResolver(HashMap<u64, NQuin>);
72    impl GroundingResolver for MapResolver {
73        fn resolve(&self, h: u64) -> Option<NQuin> {
74            self.0.get(&h).copied()
75        }
76    }
77
78    #[test]
79    fn exact_attestation_is_grounded() {
80        let claim = quin(1, 2, 3);
81        let facts = [quin(1, 2, 3)];
82        let v = evaluate_grounding(&claim, &facts, GroundingThresholds::default());
83        assert!(v.is_grounded());
84    }
85
86    #[test]
87    fn endpoints_only_is_weak_review_band() {
88        let claim = quin(1, 2, 3);
89        let facts = [quin(1, 9, 9), quin(9, 9, 3)];
90        let v = evaluate_grounding(&claim, &facts, GroundingThresholds::default());
91        assert!(matches!(v, GroundingVerdict::Weak { .. }), "got {v:?}");
92    }
93
94    #[test]
95    fn unrelated_is_ungrounded() {
96        let claim = quin(1, 2, 3);
97        let facts = [quin(4, 5, 6)];
98        let v = evaluate_grounding(&claim, &facts, GroundingThresholds::default());
99        assert!(matches!(v, GroundingVerdict::Ungrounded { .. }));
100    }
101
102    #[test]
103    fn resolver_path_grounds_a_cited_claim() {
104        let mut m = HashMap::new();
105        m.insert(0xAA, quin(1, 2, 3)); // citation 0xAA resolves to the exact fact
106        m.insert(0xBB, quin(9, 9, 9));
107        let resolver = MapResolver(m);
108        let claim = quin(1, 2, 3);
109        let v = evaluate_output_grounding(
110            &claim,
111            &[0xAA, 0xBB],
112            &resolver,
113            GroundingThresholds::default(),
114        );
115        assert!(v.is_grounded());
116    }
117
118    #[test]
119    fn unresolvable_citations_fail_closed() {
120        let resolver = MapResolver(HashMap::new());
121        let claim = quin(1, 2, 3);
122        // Citation hash present but nothing resolves it → no evidence → ungrounded.
123        let v =
124            evaluate_output_grounding(&claim, &[0x123], &resolver, GroundingThresholds::default());
125        assert!(matches!(v, GroundingVerdict::Ungrounded { score } if score == 0.0));
126    }
127}