systemprompt_analytics/repository/
overview.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::{OverviewAgentRow, OverviewCostRow, OverviewRequestRow, OverviewToolRow};
15
16#[derive(Debug)]
17pub struct OverviewAnalyticsRepository {
18 pool: Arc<PgPool>,
19}
20
21impl OverviewAnalyticsRepository {
22 pub fn new(db: &DbPool) -> Result<Self> {
23 let pool = db.pool_arc()?;
24 Ok(Self { pool })
25 }
26
27 pub async fn get_conversation_count(
28 &self,
29 start: DateTime<Utc>,
30 end: DateTime<Utc>,
31 ) -> Result<i64> {
32 let count = sqlx::query_scalar!(
33 r#"SELECT COUNT(*)::bigint as "count!" FROM user_contexts WHERE created_at >= $1 AND created_at < $2 AND kind = $3"#,
34 start,
35 end,
36 ContextKind::User.as_str()
37 )
38 .fetch_one(&*self.pool)
39 .await?;
40 Ok(count)
41 }
42
43 pub async fn get_agent_metrics(
44 &self,
45 start: DateTime<Utc>,
46 end: DateTime<Utc>,
47 ) -> Result<OverviewAgentRow> {
48 sqlx::query_as!(
49 OverviewAgentRow,
50 r#"
51 SELECT
52 COUNT(DISTINCT agent_name)::bigint as "active_agents!",
53 COUNT(*)::bigint as "total_tasks!",
54 COUNT(*) FILTER (WHERE status = 'TASK_STATE_COMPLETED')::bigint as "completed_tasks!"
55 FROM agent_tasks
56 WHERE started_at >= $1 AND started_at < $2
57 "#,
58 start,
59 end
60 )
61 .fetch_one(&*self.pool)
62 .await
63 .map_err(Into::into)
64 }
65
66 pub async fn get_request_metrics(
67 &self,
68 start: DateTime<Utc>,
69 end: DateTime<Utc>,
70 ) -> Result<OverviewRequestRow> {
71 sqlx::query_as!(
72 OverviewRequestRow,
73 r#"
74 SELECT
75 COUNT(*)::bigint as "total!",
76 SUM(tokens_used)::bigint as "total_tokens",
77 AVG(latency_ms)::float8 as "avg_latency"
78 FROM ai_requests
79 WHERE created_at >= $1 AND created_at < $2
80 "#,
81 start,
82 end
83 )
84 .fetch_one(&*self.pool)
85 .await
86 .map_err(Into::into)
87 }
88
89 pub async fn get_tool_metrics(
90 &self,
91 start: DateTime<Utc>,
92 end: DateTime<Utc>,
93 ) -> Result<OverviewToolRow> {
94 sqlx::query_as!(
95 OverviewToolRow,
96 r#"
97 SELECT
98 COUNT(*)::bigint as "total!",
99 COUNT(*) FILTER (WHERE status = 'success')::bigint as "successful!"
100 FROM mcp_tool_executions
101 WHERE created_at >= $1 AND created_at < $2
102 "#,
103 start,
104 end
105 )
106 .fetch_one(&*self.pool)
107 .await
108 .map_err(Into::into)
109 }
110
111 pub async fn get_active_session_count(&self, since: DateTime<Utc>) -> Result<i64> {
112 let count = sqlx::query_scalar!(
113 r#"
114 SELECT COUNT(*)::bigint as "count!"
115 FROM user_sessions
116 WHERE ended_at IS NULL
117 AND last_activity_at >= $1
118 AND is_bot = false AND is_behavioral_bot = false AND is_scanner = false
119 "#,
120 since
121 )
122 .fetch_one(&*self.pool)
123 .await?;
124 Ok(count)
125 }
126
127 pub async fn get_total_session_count(
128 &self,
129 start: DateTime<Utc>,
130 end: DateTime<Utc>,
131 ) -> Result<i64> {
132 let count = sqlx::query_scalar!(
133 r#"SELECT COUNT(*)::bigint as "count!" FROM user_sessions WHERE started_at >= $1 AND started_at < $2 AND is_bot = false AND is_behavioral_bot = false AND is_scanner = false"#,
134 start,
135 end
136 )
137 .fetch_one(&*self.pool)
138 .await?;
139 Ok(count)
140 }
141
142 pub async fn get_cost(
143 &self,
144 start: DateTime<Utc>,
145 end: DateTime<Utc>,
146 ) -> Result<OverviewCostRow> {
147 sqlx::query_as!(
148 OverviewCostRow,
149 r#"
150 SELECT SUM(cost_microdollars)::bigint as "cost"
151 FROM ai_requests
152 WHERE created_at >= $1 AND created_at < $2
153 "#,
154 start,
155 end
156 )
157 .fetch_one(&*self.pool)
158 .await
159 .map_err(Into::into)
160 }
161}