sightloom_core/envelope.rs
1//! Portable event envelope shared by index serialization and analytics.
2
3use crate::{ClassId, Direction, EventId, EvidenceRef, FrameStamp, SubjectId, TrackId, ZoneId};
4
5/// Coarse kind tag for an indexed event.
6///
7/// Payload-specific details live in [`EventPayload`]. Hosts match on `kind`
8/// for cheap filtering without decoding full payloads.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum EventKind {
11 /// Zone membership or line crossing.
12 Zone,
13 /// Dwell timing within a zone.
14 Dwell,
15 /// Occupancy change inside a zone.
16 Occupancy,
17 /// Identity / subject resolution lifecycle.
18 Identity,
19 /// Pattern detector output.
20 Pattern,
21 /// Anomaly detector output.
22 Anomaly,
23 /// Application-defined custom event.
24 Custom,
25}
26
27/// Compact payload attached to an [`EventEnvelope`].
28#[derive(Clone, Copy, Debug, PartialEq)]
29pub enum EventPayload {
30 /// No additional fields.
31 Empty,
32 /// Zone enter.
33 Entered {
34 /// Zone that was entered.
35 zone_id: ZoneId,
36 /// Optional class at the time of the event.
37 class_id: Option<ClassId>,
38 },
39 /// Zone exit.
40 Exited {
41 /// Zone that was exited.
42 zone_id: ZoneId,
43 /// Optional class at the time of the event.
44 class_id: Option<ClassId>,
45 },
46 /// Line crossing.
47 Crossed {
48 /// Line zone that was crossed.
49 zone_id: ZoneId,
50 /// Crossing direction.
51 direction: Direction,
52 },
53 /// Dwell started.
54 DwellStarted {
55 /// Zone under dwell.
56 zone_id: ZoneId,
57 },
58 /// Dwell ended.
59 DwellEnded {
60 /// Zone under dwell.
61 zone_id: ZoneId,
62 /// Duration in nanoseconds.
63 duration_ns: i64,
64 /// Visit count after this dwell.
65 visit_count: u32,
66 },
67 /// Occupancy snapshot.
68 Occupancy {
69 /// Zone whose occupancy changed.
70 zone_id: ZoneId,
71 /// Confirmed occupants.
72 occupancy: u32,
73 },
74 /// Free numeric payload for custom / analysis events.
75 Metrics {
76 /// Primary score or magnitude.
77 score: f32,
78 /// Optional secondary value.
79 aux: f32,
80 /// Application tag.
81 tag: u32,
82 },
83}
84
85/// Versioned, queryable event record for a `VisionIndex`.
86///
87/// This is the shared contract between tracking, analytics, storage, and host
88/// products. It does not embed pixels or edit instructions.
89#[derive(Clone, Copy, Debug, PartialEq)]
90pub struct EventEnvelope {
91 /// Stable event id within an index document.
92 pub event_id: EventId,
93 /// Temporal and source stamp for the event.
94 pub stamp: FrameStamp,
95 /// Coarse kind for filtering.
96 pub kind: EventKind,
97 /// Optional track association.
98 pub track_id: Option<TrackId>,
99 /// Optional long-lived subject association.
100 pub subject_id: Option<SubjectId>,
101 /// Optional zone association (also often present in payload).
102 pub zone_id: Option<ZoneId>,
103 /// Optional evidence handle for reels / audit.
104 pub evidence: Option<EvidenceRef>,
105 /// Kind-specific payload.
106 pub payload: EventPayload,
107}
108
109impl EventEnvelope {
110 /// Creates an envelope with empty payload and no optional associations.
111 #[must_use]
112 pub fn new(event_id: EventId, stamp: FrameStamp, kind: EventKind) -> Self {
113 Self {
114 event_id,
115 stamp,
116 kind,
117 track_id: None,
118 subject_id: None,
119 zone_id: None,
120 evidence: None,
121 payload: EventPayload::Empty,
122 }
123 }
124
125 /// Builder-style track association.
126 #[must_use]
127 pub fn with_track(mut self, track_id: TrackId) -> Self {
128 self.track_id = Some(track_id);
129 self
130 }
131
132 /// Builder-style subject association.
133 #[must_use]
134 pub fn with_subject(mut self, subject_id: SubjectId) -> Self {
135 self.subject_id = Some(subject_id);
136 self
137 }
138
139 /// Builder-style zone association.
140 #[must_use]
141 pub fn with_zone(mut self, zone_id: ZoneId) -> Self {
142 self.zone_id = Some(zone_id);
143 self
144 }
145
146 /// Builder-style evidence handle.
147 #[must_use]
148 pub fn with_evidence(mut self, evidence: EvidenceRef) -> Self {
149 self.evidence = Some(evidence);
150 self
151 }
152
153 /// Builder-style payload.
154 #[must_use]
155 pub fn with_payload(mut self, payload: EventPayload) -> Self {
156 self.payload = payload;
157 self
158 }
159}