systemprompt-analytics 0.1.18

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::AgentListRow;

impl AgentAnalyticsRepository {
    pub async fn list_agents(
        &self,
        start: DateTime<Utc>,
        end: DateTime<Utc>,
        limit: i64,
        sort_order: &str,
    ) -> Result<Vec<AgentListRow>> {
        match sort_order {
            "success_rate" => self.list_by_success_rate(start, end, limit).await,
            "cost" => self.list_by_cost(start, end, limit).await,
            "last_active" => self.list_by_last_active(start, end, limit).await,
            _ => self.list_by_task_count(start, end, limit).await,
        }
    }

    async fn list_by_success_rate(
        &self,
        start: DateTime<Utc>,
        end: DateTime<Utc>,
        limit: i64,
    ) -> Result<Vec<AgentListRow>> {
        sqlx::query_as!(
            AgentListRow,
            r#"
            SELECT
                t.agent_name as "agent_name!",
                COUNT(*)::bigint as "task_count!",
                COUNT(*) FILTER (WHERE t.status = 'completed')::bigint as "completed_count!",
                COALESCE(AVG(t.execution_time_ms), 0)::bigint as "avg_execution_time_ms!",
                COALESCE(SUM(r.cost_microdollars), 0)::bigint as "total_cost_microdollars!",
                MAX(t.started_at) as "last_active!"
            FROM agent_tasks t
            LEFT JOIN ai_requests r ON r.task_id = t.task_id
            WHERE t.started_at >= $1 AND t.started_at < $2
              AND t.agent_name IS NOT NULL
            GROUP BY t.agent_name
            ORDER BY CASE WHEN COUNT(*) > 0
                THEN COUNT(*) FILTER (WHERE t.status = 'completed')::float / COUNT(*)::float
                ELSE 0 END DESC
            LIMIT $3
            "#,
            start,
            end,
            limit
        )
        .fetch_all(&*self.pool)
        .await
        .map_err(Into::into)
    }

    async fn list_by_cost(
        &self,
        start: DateTime<Utc>,
        end: DateTime<Utc>,
        limit: i64,
    ) -> Result<Vec<AgentListRow>> {
        sqlx::query_as!(
            AgentListRow,
            r#"
            SELECT
                t.agent_name as "agent_name!",
                COUNT(*)::bigint as "task_count!",
                COUNT(*) FILTER (WHERE t.status = 'completed')::bigint as "completed_count!",
                COALESCE(AVG(t.execution_time_ms), 0)::bigint as "avg_execution_time_ms!",
                COALESCE(SUM(r.cost_microdollars), 0)::bigint as "total_cost_microdollars!",
                MAX(t.started_at) as "last_active!"
            FROM agent_tasks t
            LEFT JOIN ai_requests r ON r.task_id = t.task_id
            WHERE t.started_at >= $1 AND t.started_at < $2
              AND t.agent_name IS NOT NULL
            GROUP BY t.agent_name
            ORDER BY COALESCE(SUM(r.cost_microdollars), 0) DESC
            LIMIT $3
            "#,
            start,
            end,
            limit
        )
        .fetch_all(&*self.pool)
        .await
        .map_err(Into::into)
    }

    async fn list_by_last_active(
        &self,
        start: DateTime<Utc>,
        end: DateTime<Utc>,
        limit: i64,
    ) -> Result<Vec<AgentListRow>> {
        sqlx::query_as!(
            AgentListRow,
            r#"
            SELECT
                t.agent_name as "agent_name!",
                COUNT(*)::bigint as "task_count!",
                COUNT(*) FILTER (WHERE t.status = 'completed')::bigint as "completed_count!",
                COALESCE(AVG(t.execution_time_ms), 0)::bigint as "avg_execution_time_ms!",
                COALESCE(SUM(r.cost_microdollars), 0)::bigint as "total_cost_microdollars!",
                MAX(t.started_at) as "last_active!"
            FROM agent_tasks t
            LEFT JOIN ai_requests r ON r.task_id = t.task_id
            WHERE t.started_at >= $1 AND t.started_at < $2
              AND t.agent_name IS NOT NULL
            GROUP BY t.agent_name
            ORDER BY MAX(t.started_at) DESC
            LIMIT $3
            "#,
            start,
            end,
            limit
        )
        .fetch_all(&*self.pool)
        .await
        .map_err(Into::into)
    }

    async fn list_by_task_count(
        &self,
        start: DateTime<Utc>,
        end: DateTime<Utc>,
        limit: i64,
    ) -> Result<Vec<AgentListRow>> {
        sqlx::query_as!(
            AgentListRow,
            r#"
            SELECT
                t.agent_name as "agent_name!",
                COUNT(*)::bigint as "task_count!",
                COUNT(*) FILTER (WHERE t.status = 'completed')::bigint as "completed_count!",
                COALESCE(AVG(t.execution_time_ms), 0)::bigint as "avg_execution_time_ms!",
                COALESCE(SUM(r.cost_microdollars), 0)::bigint as "total_cost_microdollars!",
                MAX(t.started_at) as "last_active!"
            FROM agent_tasks t
            LEFT JOIN ai_requests r ON r.task_id = t.task_id
            WHERE t.started_at >= $1 AND t.started_at < $2
              AND t.agent_name IS NOT NULL
            GROUP BY t.agent_name
            ORDER BY COUNT(*) DESC
            LIMIT $3
            "#,
            start,
            end,
            limit
        )
        .fetch_all(&*self.pool)
        .await
        .map_err(Into::into)
    }
}