Skip to main content

tpt_eve_core/
fact.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2use serde::{Deserialize, Serialize};
3
4/// A single term in a fact triple. `Symbol` is an identifier-like token
5/// (e.g. `TypeScript`, `has`); `Text` is a free-form quoted phrase
6/// (e.g. `"programming language"`).
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub enum Value {
9    Symbol(String),
10    Text(String),
11}
12
13impl Value {
14    pub fn as_str(&self) -> &str {
15        match self {
16            Value::Symbol(s) => s,
17            Value::Text(s) => s,
18        }
19    }
20}
21
22impl std::fmt::Display for Value {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            Value::Symbol(s) => write!(f, "{s}"),
26            Value::Text(s) => write!(f, "\"{s}\""),
27        }
28    }
29}
30
31/// Where a fact came from, for provenance and confidence tracking.
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub enum FactSource {
34    /// Hand-authored / loaded from a `.eve` source file.
35    Asserted,
36    /// Produced by a `NeuralPatternLayer::extract_facts` call.
37    Extracted { confidence: f32 },
38    /// Derived by the forward-chaining inference engine.
39    Inferred { rule_name: Option<String> },
40}
41
42/// A subject-predicate-object triple held in working memory.
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub struct Fact {
45    pub subject: Value,
46    pub predicate: Value,
47    pub object: Value,
48    pub source: FactSource,
49    pub confidence: f32,
50}
51
52impl Fact {
53    pub fn asserted(subject: Value, predicate: Value, object: Value) -> Self {
54        Fact {
55            subject,
56            predicate,
57            object,
58            source: FactSource::Asserted,
59            confidence: 1.0,
60        }
61    }
62
63    /// Whether two facts share the same (subject, predicate, object) triple,
64    /// ignoring source/confidence — used for working-memory deduplication.
65    pub fn same_triple(&self, other: &Fact) -> bool {
66        self.subject == other.subject
67            && self.predicate == other.predicate
68            && self.object == other.object
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn same_triple_ignores_source_and_confidence() {
78        let a = Fact::asserted(
79            Value::Symbol("TypeScript".into()),
80            Value::Symbol("is".into()),
81            Value::Text("programming language".into()),
82        );
83        let mut b = a.clone();
84        b.source = FactSource::Extracted { confidence: 0.5 };
85        b.confidence = 0.5;
86        assert!(a.same_triple(&b));
87    }
88
89    #[test]
90    fn different_object_is_not_same_triple() {
91        let a = Fact::asserted(
92            Value::Symbol("TypeScript".into()),
93            Value::Symbol("is".into()),
94            Value::Text("programming language".into()),
95        );
96        let b = Fact::asserted(
97            Value::Symbol("TypeScript".into()),
98            Value::Symbol("is".into()),
99            Value::Text("markup language".into()),
100        );
101        assert!(!a.same_triple(&b));
102    }
103
104    #[test]
105    fn serde_round_trip() {
106        let fact = Fact::asserted(
107            Value::Symbol("TypeScript".into()),
108            Value::Symbol("has".into()),
109            Value::Text("type system".into()),
110        );
111        let json = serde_json::to_string(&fact).unwrap();
112        let back: Fact = serde_json::from_str(&json).unwrap();
113        assert_eq!(fact, back);
114    }
115
116    #[test]
117    fn value_display() {
118        assert_eq!(Value::Symbol("TypeScript".into()).to_string(), "TypeScript");
119        assert_eq!(
120            Value::Text("type system".into()).to_string(),
121            "\"type system\""
122        );
123    }
124}