systemprompt_analytics/repository/
conversations.rs1use crate::Result;
8use chrono::{DateTime, Utc};
9use sqlx::PgPool;
10use std::sync::Arc;
11use systemprompt_database::DbPool;
12use systemprompt_models::ContextKind;
13
14use crate::models::cli::{ConversationListRow, GatewaySessionListRow, TimestampRow};
15
16#[derive(Debug)]
17pub struct ConversationAnalyticsRepository {
18 pool: Arc<PgPool>,
19}
20
21impl ConversationAnalyticsRepository {
22 pub fn new(db: &DbPool) -> Result<Self> {
23 let pool = db.pool_arc()?;
24 Ok(Self { pool })
25 }
26
27 pub async fn list_agent_contexts(
28 &self,
29 start: DateTime<Utc>,
30 end: DateTime<Utc>,
31 limit: i64,
32 ) -> Result<Vec<ConversationListRow>> {
33 sqlx::query_as!(
34 ConversationListRow,
35 r#"
36 SELECT
37 uc.context_id as "context_id!: systemprompt_identifiers::ContextId",
38 uc.name as "name?",
39 (SELECT COUNT(*) FROM agent_tasks at WHERE at.context_id = uc.context_id)::bigint as "task_count!",
40 (SELECT COUNT(*) FROM task_messages tm
41 JOIN agent_tasks at ON at.task_id = tm.task_id
42 WHERE at.context_id = uc.context_id)::bigint as "message_count!",
43 uc.created_at as "created_at!",
44 uc.updated_at as "updated_at!"
45 FROM user_contexts uc
46 WHERE uc.created_at >= $1 AND uc.created_at < $2 AND uc.kind = $4
47 ORDER BY uc.updated_at DESC
48 LIMIT $3
49 "#,
50 start,
51 end,
52 limit,
53 ContextKind::User.as_str()
54 )
55 .fetch_all(&*self.pool)
56 .await
57 .map_err(Into::into)
58 }
59
60 pub async fn list_gateway_sessions(
61 &self,
62 start: DateTime<Utc>,
63 end: DateTime<Utc>,
64 limit: i64,
65 ) -> Result<Vec<GatewaySessionListRow>> {
66 sqlx::query_as!(
67 GatewaySessionListRow,
68 r#"
69 SELECT
70 ar.session_id as "session_id!: systemprompt_identifiers::SessionId",
71 COUNT(arm.id)::bigint as "message_count!",
72 MIN(ar.created_at) as "created_at!",
73 MAX(ar.created_at) as "updated_at!"
74 FROM ai_requests ar
75 LEFT JOIN ai_request_messages arm ON arm.request_id = ar.id
76 WHERE ar.task_id IS NULL
77 AND ar.session_id IS NOT NULL
78 AND ar.created_at >= $1 AND ar.created_at < $2
79 AND NOT EXISTS (
80 SELECT 1 FROM user_contexts uc2 WHERE uc2.context_id::text = ar.session_id
81 )
82 GROUP BY ar.session_id
83 ORDER BY MAX(ar.created_at) DESC
84 LIMIT $3
85 "#,
86 start,
87 end,
88 limit
89 )
90 .fetch_all(&*self.pool)
91 .await
92 .map_err(Into::into)
93 }
94
95 pub async fn get_context_count(&self, start: DateTime<Utc>, end: DateTime<Utc>) -> Result<i64> {
96 let count = sqlx::query_scalar!(
97 r#"SELECT COUNT(*)::bigint as "count!" FROM user_contexts WHERE created_at >= $1 AND created_at < $2 AND kind = $3"#,
98 start,
99 end,
100 ContextKind::User.as_str()
101 )
102 .fetch_one(&*self.pool)
103 .await?;
104 Ok(count)
105 }
106
107 pub async fn get_task_stats(
108 &self,
109 start: DateTime<Utc>,
110 end: DateTime<Utc>,
111 ) -> Result<(i64, Option<f64>)> {
112 let row = sqlx::query!(
113 r#"
114 SELECT COUNT(*)::bigint as "count!", AVG(execution_time_ms)::float8 as avg_time
115 FROM agent_tasks
116 WHERE started_at >= $1 AND started_at < $2
117 "#,
118 start,
119 end
120 )
121 .fetch_one(&*self.pool)
122 .await?;
123 Ok((row.count, row.avg_time))
124 }
125
126 pub async fn get_message_count(&self, start: DateTime<Utc>, end: DateTime<Utc>) -> Result<i64> {
127 let count = sqlx::query_scalar!(
128 r#"SELECT COUNT(*)::bigint as "count!" FROM task_messages WHERE created_at >= $1 AND created_at < $2"#,
129 start,
130 end
131 )
132 .fetch_one(&*self.pool)
133 .await?;
134 Ok(count)
135 }
136
137 pub async fn get_context_timestamps(
138 &self,
139 start: DateTime<Utc>,
140 end: DateTime<Utc>,
141 ) -> Result<Vec<TimestampRow>> {
142 sqlx::query_as!(
143 TimestampRow,
144 r#"
145 SELECT created_at as "timestamp!"
146 FROM user_contexts
147 WHERE created_at >= $1 AND created_at < $2 AND kind = $3
148 "#,
149 start,
150 end,
151 ContextKind::User.as_str()
152 )
153 .fetch_all(&*self.pool)
154 .await
155 .map_err(Into::into)
156 }
157
158 pub async fn get_task_timestamps(
159 &self,
160 start: DateTime<Utc>,
161 end: DateTime<Utc>,
162 ) -> Result<Vec<TimestampRow>> {
163 sqlx::query_as!(
164 TimestampRow,
165 r#"
166 SELECT started_at as "timestamp!"
167 FROM agent_tasks
168 WHERE started_at >= $1 AND started_at < $2
169 "#,
170 start,
171 end
172 )
173 .fetch_all(&*self.pool)
174 .await
175 .map_err(Into::into)
176 }
177
178 pub async fn get_message_timestamps(
179 &self,
180 start: DateTime<Utc>,
181 end: DateTime<Utc>,
182 ) -> Result<Vec<TimestampRow>> {
183 sqlx::query_as!(
184 TimestampRow,
185 r#"
186 SELECT created_at as "timestamp!"
187 FROM task_messages
188 WHERE created_at >= $1 AND created_at < $2
189 "#,
190 start,
191 end
192 )
193 .fetch_all(&*self.pool)
194 .await
195 .map_err(Into::into)
196 }
197}