Skip to main content

systemprompt_analytics/models/
engagement.rs

1//! Engagement-event input models.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use sqlx::FromRow;
9use systemprompt_identifiers::{ContentId, EngagementEventId, SessionId, UserId};
10
11#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
12pub struct EngagementEvent {
13    pub id: EngagementEventId,
14    pub session_id: SessionId,
15    pub user_id: UserId,
16    pub page_url: String,
17    pub content_id: Option<ContentId>,
18    pub event_type: String,
19    pub time_on_page_ms: i32,
20    pub time_to_first_interaction_ms: Option<i32>,
21    pub time_to_first_scroll_ms: Option<i32>,
22    pub max_scroll_depth: i32,
23    pub scroll_velocity_avg: Option<f32>,
24    pub scroll_direction_changes: Option<i32>,
25    pub click_count: i32,
26    pub mouse_move_distance_px: Option<i32>,
27    pub keyboard_events: Option<i32>,
28    pub copy_events: Option<i32>,
29    pub focus_time_ms: i32,
30    pub blur_count: i32,
31    pub tab_switches: i32,
32    pub visible_time_ms: i32,
33    pub hidden_time_ms: i32,
34    pub is_rage_click: Option<bool>,
35    pub is_dead_click: Option<bool>,
36    pub reading_pattern: Option<String>,
37    pub created_at: DateTime<Utc>,
38    pub updated_at: DateTime<Utc>,
39}
40
41#[derive(Debug, Clone, Deserialize)]
42#[serde(default)]
43pub struct CreateEngagementEventInput {
44    #[serde(default)]
45    pub page_url: String,
46    #[serde(default = "default_event_type")]
47    pub event_type: String,
48    #[serde(default)]
49    pub time_on_page_ms: i32,
50    #[serde(default)]
51    pub max_scroll_depth: i32,
52    #[serde(default)]
53    pub click_count: i32,
54    #[serde(default)]
55    pub event_data: Option<serde_json::Value>,
56    #[serde(flatten)]
57    pub optional_metrics: EngagementOptionalMetrics,
58}
59
60impl Default for CreateEngagementEventInput {
61    fn default() -> Self {
62        Self {
63            page_url: String::new(),
64            event_type: default_event_type(),
65            time_on_page_ms: 0,
66            max_scroll_depth: 0,
67            click_count: 0,
68            event_data: None,
69            optional_metrics: EngagementOptionalMetrics::default(),
70        }
71    }
72}
73
74#[derive(Debug, Clone, Default, Deserialize)]
75pub struct EngagementOptionalMetrics {
76    pub time_to_first_interaction_ms: Option<i32>,
77    pub time_to_first_scroll_ms: Option<i32>,
78    pub scroll_velocity_avg: Option<f32>,
79    pub scroll_direction_changes: Option<i32>,
80    pub mouse_move_distance_px: Option<i32>,
81    pub keyboard_events: Option<i32>,
82    pub copy_events: Option<i32>,
83    pub focus_time_ms: Option<i32>,
84    pub blur_count: Option<i32>,
85    pub tab_switches: Option<i32>,
86    pub visible_time_ms: Option<i32>,
87    pub hidden_time_ms: Option<i32>,
88    pub is_rage_click: Option<bool>,
89    pub is_dead_click: Option<bool>,
90    pub reading_pattern: Option<String>,
91}
92
93fn default_event_type() -> String {
94    "page_exit".to_owned()
95}