systemprompt-analytics 0.1.22

Analytics module for systemprompt.io - session tracking, metrics, and reporting
Documentation
use anyhow::Result;
use chrono::{DateTime, Utc};

use super::AgentAnalyticsRepository;
use crate::models::cli::{AgentAiStatsRow, AgentStatsRow, AgentTaskRow};

impl AgentAnalyticsRepository {
    pub async fn get_stats(
        &self,
        start: DateTime<Utc>,
        end: DateTime<Utc>,
        agent_filter: Option<&str>,
    ) -> Result<AgentStatsRow> {
        if let Some(agent) = agent_filter {
            let pattern = format!("%{}%", agent);
            sqlx::query_as!(
                AgentStatsRow,
                r#"
                SELECT
                    COUNT(DISTINCT agent_name)::bigint as "total_agents!",
                    COUNT(*)::bigint as "total_tasks!",
                    COUNT(*) FILTER (WHERE status = 'completed')::bigint as "completed_tasks!",
                    COUNT(*) FILTER (WHERE status = 'failed')::bigint as "failed_tasks!",
                    COALESCE(AVG(execution_time_ms)::float8, 0) as "avg_execution_time_ms!"
                FROM agent_tasks
                WHERE started_at >= $1 AND started_at < $2
                  AND agent_name ILIKE $3
                "#,
                start,
                end,
                pattern
            )
            .fetch_one(&*self.pool)
            .await
            .map_err(Into::into)
        } else {
            sqlx::query_as!(
                AgentStatsRow,
                r#"
                SELECT
                    COUNT(DISTINCT agent_name)::bigint as "total_agents!",
                    COUNT(*)::bigint as "total_tasks!",
                    COUNT(*) FILTER (WHERE status = 'completed')::bigint as "completed_tasks!",
                    COUNT(*) FILTER (WHERE status = 'failed')::bigint as "failed_tasks!",
                    COALESCE(AVG(execution_time_ms)::float8, 0) as "avg_execution_time_ms!"
                FROM agent_tasks
                WHERE started_at >= $1 AND started_at < $2
                "#,
                start,
                end
            )
            .fetch_one(&*self.pool)
            .await
            .map_err(Into::into)
        }
    }

    pub async fn get_ai_stats(
        &self,
        start: DateTime<Utc>,
        end: DateTime<Utc>,
    ) -> Result<AgentAiStatsRow> {
        sqlx::query_as!(
            AgentAiStatsRow,
            r#"
            SELECT
                COUNT(*)::bigint as "total_ai_requests!",
                COALESCE(SUM(cost_microdollars), 0)::bigint as "total_cost_microdollars!"
            FROM ai_requests
            WHERE created_at >= $1 AND created_at < $2
            "#,
            start,
            end
        )
        .fetch_one(&*self.pool)
        .await
        .map_err(Into::into)
    }

    pub async fn get_tasks_for_trends(
        &self,
        start: DateTime<Utc>,
        end: DateTime<Utc>,
        agent_filter: Option<&str>,
    ) -> Result<Vec<AgentTaskRow>> {
        if let Some(agent) = agent_filter {
            let pattern = format!("%{}%", agent);
            sqlx::query_as!(
                AgentTaskRow,
                r#"
                SELECT
                    started_at as "started_at!",
                    status,
                    execution_time_ms
                FROM agent_tasks
                WHERE started_at >= $1 AND started_at < $2
                  AND agent_name ILIKE $3
                ORDER BY started_at
                "#,
                start,
                end,
                pattern
            )
            .fetch_all(&*self.pool)
            .await
            .map_err(Into::into)
        } else {
            sqlx::query_as!(
                AgentTaskRow,
                r#"
                SELECT
                    started_at as "started_at!",
                    status,
                    execution_time_ms
                FROM agent_tasks
                WHERE started_at >= $1 AND started_at < $2
                ORDER BY started_at
                "#,
                start,
                end
            )
            .fetch_all(&*self.pool)
            .await
            .map_err(Into::into)
        }
    }
}