1use crate::types::TrustTier;
5use oxibrain_ports::Timestamp;
6use serde::{Deserialize, Serialize};
7
8pub type EntityId = String;
9pub type EntityKeyId = String;
10pub type StatementId = String;
11pub type AssertionId = String;
12pub type MentionId = String;
13
14pub type EntityTypeRef = String;
15pub type PredicateRef = String;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct Entity {
20 pub id: EntityId,
21 pub space: String,
22 pub ty: EntityTypeRef,
23 pub canonical_key: Option<EntityKeyId>,
24 pub created_at: Timestamp,
25 pub merged_into: Option<EntityId>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct EntityKey {
31 pub id: EntityKeyId,
32 pub space: String,
33 pub entity: EntityId,
34 pub ty: EntityTypeRef,
35 pub normalized: String,
36 pub surface: String,
37 pub origin: KeyOrigin,
38}
39
40#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum KeyOrigin {
43 Extracted,
44 UserDeclared,
45 Imported,
46}
47
48impl KeyOrigin {
49 pub fn as_db(&self) -> &'static str {
50 match self {
51 Self::Extracted => "extracted",
52 Self::UserDeclared => "user_declared",
53 Self::Imported => "imported",
54 }
55 }
56 pub fn parse_db(s: &str) -> Option<Self> {
57 match s {
58 "extracted" => Some(Self::Extracted),
59 "user_declared" => Some(Self::UserDeclared),
60 "imported" => Some(Self::Imported),
61 _ => None,
62 }
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct EntityMerge {
68 pub id: String,
69 pub loser: EntityId,
70 pub winner: EntityId,
71 pub decided_by: MergeDecision,
72 pub provenance: String,
73 pub evidence: Vec<MentionId>,
74 pub decided_at: Timestamp,
75 pub undone_at: Option<Timestamp>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(tag = "kind", content = "data", rename_all = "snake_case")]
80pub enum MergeDecision {
81 Rule { score: f64 },
82 User,
83 Import,
84}
85
86impl MergeDecision {
87 pub fn db_columns(&self) -> (&'static str, Option<f64>) {
89 match self {
90 Self::Rule { score } => ("rule", Some(*score)),
91 Self::User => ("user", None),
92 Self::Import => ("import", None),
93 }
94 }
95 pub fn parse_db(kind: &str, score: Option<f64>) -> Option<Self> {
96 match kind {
97 "rule" => Some(Self::Rule {
98 score: score.unwrap_or(0.0),
99 }),
100 "user" => Some(Self::User),
101 "import" => Some(Self::Import),
102 _ => None,
103 }
104 }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct Statement {
110 pub id: StatementId,
111 pub space: String,
112 pub subject: EntityId,
113 pub predicate: PredicateRef,
114 pub object: Object,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118#[serde(tag = "kind", rename_all = "snake_case")]
119pub enum Object {
120 Entity(EntityId),
121 Literal(TypedValue),
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(tag = "type", content = "value", rename_all = "snake_case")]
126pub enum TypedValue {
127 Text(String),
128 Date(String),
129 DateTime(String),
130 Quantity { value: f64, unit: String },
131 Number(f64),
132 Bool(bool),
133 Enum(String),
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct Assertion {
139 pub id: AssertionId,
140 pub statement: StatementId,
141 pub episode: String,
142 pub extractor: Option<String>,
143 pub polarity: Polarity,
144 pub claimed_from: Timestamp,
145 pub claimed_to: Timestamp,
146 pub confidence: f32,
147 pub recorded_at: Timestamp,
148 pub retracted_at: Option<Timestamp>,
149}
150
151#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153pub enum Polarity {
154 Affirm,
155 Deny,
156}
157
158impl Polarity {
159 pub fn as_db(&self) -> i64 {
160 match self {
161 Self::Affirm => 1,
162 Self::Deny => -1,
163 }
164 }
165 pub fn parse_db(v: i64) -> Option<Self> {
166 match v {
167 1 => Some(Self::Affirm),
168 -1 => Some(Self::Deny),
169 _ => None,
170 }
171 }
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct Mention {
177 pub id: MentionId,
178 pub assertion: AssertionId,
179 pub role: MentionRole,
180 pub surface: String,
181 pub span: (u32, u32),
182 pub resolved_to: Option<EntityId>,
183 pub method: ResolutionMethod,
184}
185
186#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
187#[serde(rename_all = "snake_case")]
188pub enum MentionRole {
189 Subject,
190 Object,
191}
192
193impl MentionRole {
194 pub fn as_db(&self) -> &'static str {
195 match self {
196 Self::Subject => "subject",
197 Self::Object => "object",
198 }
199 }
200 pub fn parse_db(s: &str) -> Option<Self> {
201 match s {
202 "subject" => Some(Self::Subject),
203 "object" => Some(Self::Object),
204 _ => None,
205 }
206 }
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210#[serde(tag = "method", rename_all = "snake_case")]
211pub enum ResolutionMethod {
212 ExactKey,
213 Alias,
214 Lexical { score: f64 },
215 Embedding { score: f64 },
216 New,
217 User,
218}
219
220impl ResolutionMethod {
221 pub fn db_columns(&self) -> (&'static str, Option<f64>) {
223 match self {
224 Self::ExactKey => ("exact_key", None),
225 Self::Alias => ("alias", None),
226 Self::Lexical { score } => ("lexical", Some(*score)),
227 Self::Embedding { score } => ("embedding", Some(*score)),
228 Self::New => ("new", None),
229 Self::User => ("user", None),
230 }
231 }
232 pub fn parse_db(method: &str, score: Option<f64>) -> Option<Self> {
233 match method {
234 "exact_key" => Some(Self::ExactKey),
235 "alias" => Some(Self::Alias),
236 "lexical" => Some(Self::Lexical {
237 score: score.unwrap_or(0.0),
238 }),
239 "embedding" => Some(Self::Embedding {
240 score: score.unwrap_or(0.0),
241 }),
242 "new" => Some(Self::New),
243 "user" => Some(Self::User),
244 _ => None,
245 }
246 }
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct Belief {
252 pub statement: StatementId,
253 pub valid_from: Timestamp,
254 pub valid_to: Timestamp,
255 pub support: Support,
256 pub confidence: f32,
257 pub status: BeliefStatus,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct Support {
262 pub affirm_count: u32,
263 pub deny_count: u32,
264 pub distinct_episodes: u32,
265 pub trust_weights: Vec<(TrustTier, u32)>,
268}
269
270#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
271#[serde(rename_all = "snake_case")]
272pub enum BeliefStatus {
273 Active,
274 Superseded,
275 Contradicted,
276 Retracted,
277}
278
279impl BeliefStatus {
280 pub fn as_db(&self) -> &'static str {
281 match self {
282 Self::Active => "active",
283 Self::Superseded => "superseded",
284 Self::Contradicted => "contradicted",
285 Self::Retracted => "retracted",
286 }
287 }
288 pub fn parse_db(s: &str) -> Option<Self> {
289 match s {
290 "active" => Some(Self::Active),
291 "superseded" => Some(Self::Superseded),
292 "contradicted" => Some(Self::Contradicted),
293 "retracted" => Some(Self::Retracted),
294 _ => None,
295 }
296 }
297}
298
299pub fn object_repr(object: &Object) -> String {
302 match object {
303 Object::Entity(id) => format!("e:{id}"),
304 Object::Literal(TypedValue::Text(s)) => format!("t:{s}"),
305 Object::Literal(TypedValue::Date(s)) => format!("d:{s}"),
306 Object::Literal(TypedValue::DateTime(s)) => format!("dt:{s}"),
307 Object::Literal(TypedValue::Number(n)) => format!("n:{}", canonical_f64(*n)),
308 Object::Literal(TypedValue::Bool(b)) => format!("b:{b}"),
309 Object::Literal(TypedValue::Quantity { value, unit }) => {
310 format!("q:{}:{unit}", canonical_f64(*value))
311 }
312 Object::Literal(TypedValue::Enum(s)) => format!("en:{s}"),
313 }
314}
315
316fn canonical_f64(n: f64) -> String {
318 if n == 0.0 {
319 "0".to_string()
320 } else if n.fract() == 0.0 && n.abs() < 1e15 {
321 format!("{}", n as i64)
322 } else {
323 format!("{n}")
324 }
325}
326
327pub fn claim_repr(
330 polarity: Polarity,
331 claimed_from: Timestamp,
332 claimed_to: Timestamp,
333 confidence: f32,
334) -> String {
335 format!(
336 "{}:{}:{}:{}",
337 match polarity {
338 Polarity::Affirm => "a",
339 Polarity::Deny => "d",
340 },
341 claimed_from.millis(),
342 claimed_to.millis(),
343 confidence.to_bits(),
344 )
345}