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