systemprompt_models/events/
analytics_event.rs1use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13use systemprompt_identifiers::{ContentId, SessionId, UserId};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
17pub enum AnalyticsEvent {
18 SessionStarted {
19 timestamp: DateTime<Utc>,
20 #[serde(flatten)]
21 payload: SessionStartedPayload,
22 },
23 SessionEnded {
24 timestamp: DateTime<Utc>,
25 #[serde(flatten)]
26 payload: SessionEndedPayload,
27 },
28 PageView {
29 timestamp: DateTime<Utc>,
30 #[serde(flatten)]
31 payload: PageViewPayload,
32 },
33 EngagementUpdate {
34 timestamp: DateTime<Utc>,
35 #[serde(flatten)]
36 payload: EngagementUpdatePayload,
37 },
38 RealTimeStats {
39 timestamp: DateTime<Utc>,
40 #[serde(flatten)]
41 payload: RealTimeStatsPayload,
42 },
43 Heartbeat {
44 timestamp: DateTime<Utc>,
45 },
46}
47
48impl AnalyticsEvent {
49 pub const fn timestamp(&self) -> DateTime<Utc> {
50 match self {
51 Self::SessionStarted { timestamp, .. }
52 | Self::SessionEnded { timestamp, .. }
53 | Self::PageView { timestamp, .. }
54 | Self::EngagementUpdate { timestamp, .. }
55 | Self::RealTimeStats { timestamp, .. }
56 | Self::Heartbeat { timestamp } => *timestamp,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct SessionStartedPayload {
63 pub session_id: SessionId,
64 pub device_type: Option<String>,
65 pub browser: Option<String>,
66 pub os: Option<String>,
67 pub country: Option<String>,
68 pub referrer_source: Option<String>,
69 pub is_bot: bool,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct SessionEndedPayload {
74 pub session_id: SessionId,
75 pub duration_ms: i64,
76 pub page_count: i64,
77 pub request_count: i64,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct PageViewPayload {
82 pub session_id: SessionId,
83 pub user_id: Option<UserId>,
84 pub page_url: String,
85 pub content_id: Option<ContentId>,
86 pub referrer: Option<String>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct EngagementUpdatePayload {
91 pub session_id: SessionId,
92 pub page_url: String,
93 pub scroll_depth: i32,
94 pub time_on_page_ms: i64,
95 pub click_count: i32,
96}
97
98#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
99pub struct RealTimeStatsPayload {
100 pub active_sessions: i64,
101 pub active_users: i64,
102 pub requests_per_minute: i64,
103 pub page_views_last_5m: i64,
104 pub bot_requests_last_5m: i64,
105}
106
107#[derive(Debug, Clone, Copy)]
108pub struct AnalyticsEventBuilder;
109
110impl AnalyticsEventBuilder {
111 pub fn session_started(payload: SessionStartedPayload) -> AnalyticsEvent {
112 AnalyticsEvent::SessionStarted {
113 timestamp: Utc::now(),
114 payload,
115 }
116 }
117
118 pub fn session_ended(
119 session_id: SessionId,
120 duration_ms: i64,
121 page_count: i64,
122 request_count: i64,
123 ) -> AnalyticsEvent {
124 AnalyticsEvent::SessionEnded {
125 timestamp: Utc::now(),
126 payload: SessionEndedPayload {
127 session_id,
128 duration_ms,
129 page_count,
130 request_count,
131 },
132 }
133 }
134
135 pub fn page_view(
136 session_id: SessionId,
137 user_id: Option<UserId>,
138 page_url: String,
139 content_id: Option<ContentId>,
140 referrer: Option<String>,
141 ) -> AnalyticsEvent {
142 AnalyticsEvent::PageView {
143 timestamp: Utc::now(),
144 payload: PageViewPayload {
145 session_id,
146 user_id,
147 page_url,
148 content_id,
149 referrer,
150 },
151 }
152 }
153
154 pub fn engagement_update(
155 session_id: SessionId,
156 page_url: String,
157 scroll_depth: i32,
158 time_on_page_ms: i64,
159 click_count: i32,
160 ) -> AnalyticsEvent {
161 AnalyticsEvent::EngagementUpdate {
162 timestamp: Utc::now(),
163 payload: EngagementUpdatePayload {
164 session_id,
165 page_url,
166 scroll_depth,
167 time_on_page_ms,
168 click_count,
169 },
170 }
171 }
172
173 pub fn realtime_stats(
174 active_sessions: i64,
175 active_users: i64,
176 requests_per_minute: i64,
177 page_views_last_5m: i64,
178 bot_requests_last_5m: i64,
179 ) -> AnalyticsEvent {
180 AnalyticsEvent::RealTimeStats {
181 timestamp: Utc::now(),
182 payload: RealTimeStatsPayload {
183 active_sessions,
184 active_users,
185 requests_per_minute,
186 page_views_last_5m,
187 bot_requests_last_5m,
188 },
189 }
190 }
191
192 pub fn heartbeat() -> AnalyticsEvent {
193 AnalyticsEvent::Heartbeat {
194 timestamp: Utc::now(),
195 }
196 }
197}