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 #[serde(default)]
151 pub trust: TrustTier,
152}
153
154#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
155#[serde(rename_all = "snake_case")]
156pub enum Polarity {
157 Affirm,
158 Deny,
159}
160
161impl Polarity {
162 pub fn as_db(&self) -> i64 {
163 match self {
164 Self::Affirm => 1,
165 Self::Deny => -1,
166 }
167 }
168 pub fn parse_db(v: i64) -> Option<Self> {
169 match v {
170 1 => Some(Self::Affirm),
171 -1 => Some(Self::Deny),
172 _ => None,
173 }
174 }
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct Mention {
180 pub id: MentionId,
181 pub assertion: AssertionId,
182 pub role: MentionRole,
183 pub surface: String,
184 pub span: (u32, u32),
185 pub resolved_to: Option<EntityId>,
186 pub method: ResolutionMethod,
187}
188
189#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
190#[serde(rename_all = "snake_case")]
191pub enum MentionRole {
192 Subject,
193 Object,
194}
195
196impl MentionRole {
197 pub fn as_db(&self) -> &'static str {
198 match self {
199 Self::Subject => "subject",
200 Self::Object => "object",
201 }
202 }
203 pub fn parse_db(s: &str) -> Option<Self> {
204 match s {
205 "subject" => Some(Self::Subject),
206 "object" => Some(Self::Object),
207 _ => None,
208 }
209 }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
213#[serde(tag = "method", rename_all = "snake_case")]
214pub enum ResolutionMethod {
215 ExactKey,
216 Alias,
217 Lexical { score: f64 },
218 Embedding { score: f64 },
219 New,
220 User,
221}
222
223impl ResolutionMethod {
224 pub fn db_columns(&self) -> (&'static str, Option<f64>) {
226 match self {
227 Self::ExactKey => ("exact_key", None),
228 Self::Alias => ("alias", None),
229 Self::Lexical { score } => ("lexical", Some(*score)),
230 Self::Embedding { score } => ("embedding", Some(*score)),
231 Self::New => ("new", None),
232 Self::User => ("user", None),
233 }
234 }
235 pub fn parse_db(method: &str, score: Option<f64>) -> Option<Self> {
236 match method {
237 "exact_key" => Some(Self::ExactKey),
238 "alias" => Some(Self::Alias),
239 "lexical" => Some(Self::Lexical {
240 score: score.unwrap_or(0.0),
241 }),
242 "embedding" => Some(Self::Embedding {
243 score: score.unwrap_or(0.0),
244 }),
245 "new" => Some(Self::New),
246 "user" => Some(Self::User),
247 _ => None,
248 }
249 }
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct Belief {
255 pub statement: StatementId,
256 pub valid_from: Timestamp,
257 pub valid_to: Timestamp,
258 pub support: Support,
259 pub confidence: f32,
260 pub status: BeliefStatus,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct Support {
265 pub affirm_count: u32,
266 pub deny_count: u32,
267 pub distinct_episodes: u32,
268 pub trust_weights: Vec<(TrustTier, u32)>,
271}
272
273#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
274#[serde(rename_all = "snake_case")]
275pub enum BeliefStatus {
276 Active,
277 Superseded,
278 Contradicted,
279 Retracted,
280}
281
282impl BeliefStatus {
283 pub fn as_db(&self) -> &'static str {
284 match self {
285 Self::Active => "active",
286 Self::Superseded => "superseded",
287 Self::Contradicted => "contradicted",
288 Self::Retracted => "retracted",
289 }
290 }
291 pub fn parse_db(s: &str) -> Option<Self> {
292 match s {
293 "active" => Some(Self::Active),
294 "superseded" => Some(Self::Superseded),
295 "contradicted" => Some(Self::Contradicted),
296 "retracted" => Some(Self::Retracted),
297 _ => None,
298 }
299 }
300}
301
302pub fn object_repr(object: &Object) -> String {
305 match object {
306 Object::Entity(id) => format!("e:{id}"),
307 Object::Literal(TypedValue::Text(s)) => format!("t:{s}"),
308 Object::Literal(TypedValue::Date(s)) => format!("d:{s}"),
309 Object::Literal(TypedValue::DateTime(s)) => format!("dt:{s}"),
310 Object::Literal(TypedValue::Number(n)) => format!("n:{}", canonical_f64(*n)),
311 Object::Literal(TypedValue::Bool(b)) => format!("b:{b}"),
312 Object::Literal(TypedValue::Quantity { value, unit }) => {
313 format!("q:{}:{unit}", canonical_f64(*value))
314 }
315 Object::Literal(TypedValue::Enum(s)) => format!("en:{s}"),
316 }
317}
318
319fn canonical_f64(n: f64) -> String {
321 if n == 0.0 {
322 "0".to_string()
323 } else if n.fract() == 0.0 && n.abs() < 1e15 {
324 format!("{}", n as i64)
325 } else {
326 format!("{n}")
327 }
328}
329
330pub fn claim_repr(
333 polarity: Polarity,
334 claimed_from: Timestamp,
335 claimed_to: Timestamp,
336 confidence: f32,
337) -> String {
338 format!(
339 "{}:{}:{}:{}",
340 match polarity {
341 Polarity::Affirm => "a",
342 Polarity::Deny => "d",
343 },
344 claimed_from.millis(),
345 claimed_to.millis(),
346 confidence.to_bits(),
347 )
348}