Skip to main content

ant_types/
observation.rs

1//! Antares-native observations.
2//!
3//! An **Observation** is an immutable, source-bound, evidence-backed
4//! atomic fact extracted from a real event/source. Observations live
5//! in their own append-only plane and feed the Belief layer.
6//!
7//! Examples:
8//!   - "Marcus from legal joined the review" → subject=person_marcus,
9//!     predicate=joined_review, source_event_id=meeting_001
10//!   - "SOC2 was mentioned in the call" → subject=meeting_001,
11//!     predicate=mentioned_topic, object_value="SOC2"
12//!   - "pricing page opened 5×" → subject=person_marcus,
13//!     predicate=page_open_count, object_value=5
14//!
15//! This module ships ONLY the observation plane; beliefs are their
16//! own record kind.
17
18use chrono::{DateTime, Utc};
19use serde::{Deserialize, Serialize};
20
21use crate::author::AuthorStamp;
22use crate::evidence::EvidenceId;
23use crate::ids::{ProjectId, TenantId, VertexId};
24
25/// Stable identifier for an observation.
26#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
27#[serde(transparent)]
28pub struct ObservationId(pub String);
29
30/// An atomic, source-bound fact about a subject.
31///
32/// Append-only: once an observation is stored, it cannot be modified.
33/// Re-submitting identical content under the same id is a no-op
34/// (idempotent). Re-submitting different content under the same id is
35/// a conflict.
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37pub struct Observation {
38    /// Observation id, unique within the scope.
39    pub id: ObservationId,
40    /// Owning tenant.
41    pub tenant_id: TenantId,
42    /// Owning project.
43    pub project_id: ProjectId,
44
45    /// Identifier of the source event (e.g. "meeting_001",
46    /// "email_002", "crm_webhook_2026-04-29T18:02"). Optional because
47    /// some observations are aggregated (e.g. "champion silent for 14
48    /// days") and don't tie to a single event.
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub source_event_id: Option<String>,
51
52    /// URI of the source artifact (e.g. "antares://transcripts/m1#1240-1295").
53    /// Optional and may duplicate `evidence_ids[0].source_uri`.
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub source_uri: Option<String>,
56
57    /// Subject of the observation — typically a deal, person, meeting,
58    /// or email vertex. Optional for observations that aren't anchored
59    /// to a specific entity (e.g. aggregate behavioral signals).
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub subject_id: Option<VertexId>,
62
63    /// What was observed about the subject. Free-form string — common
64    /// values include "joined_review", "mentioned_topic", "viewed",
65    /// "opened_email", "forwarded_to", "went_silent", "usage_dropped".
66    pub predicate: String,
67
68    /// Object of the predicate when the observation is relational.
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub object_id: Option<VertexId>,
71
72    /// Object as a literal value when the observation isn't relational
73    /// (e.g. "SOC2 was mentioned" → object_value = "SOC2";
74    /// "page opens count" → object_value = 5).
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub object_value: Option<serde_json::Value>,
77
78    /// Wall-clock time the underlying event happened.
79    pub observed_at: DateTime<Utc>,
80    /// Wall-clock time the extractor produced this observation.
81    pub extracted_at: DateTime<Utc>,
82
83    /// Confidence in `[0,1]`; `None` is treated as 1.0.
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub confidence: Option<f32>,
86
87    /// First-class evidence references. Each Observation should point
88    /// at one or more Evidence records that back it.
89    #[serde(default, skip_serializing_if = "Vec::is_empty")]
90    pub evidence_ids: Vec<EvidenceId>,
91
92    /// Version tag of the producing extractor, verbatim.
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub extractor_version: Option<String>,
95
96    /// Free-form metadata for extractor-specific extras.
97    #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
98    pub metadata: serde_json::Value,
99
100    /// Which user authored this observation. `None` for older
101    /// records and for anonymous calls. Set by the writer from the
102    /// resolved authentication context at write time.
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub author: Option<AuthorStamp>,
105}