Skip to main content

systemprompt_cli/commands/analytics/requests/
mod.rs

1//! AI request analytics: aggregate stats, individual listings, trends, and
2//! model usage.
3//!
4//! Defines the [`RequestsCommands`] subcommand tree and the typed output shapes
5//! ([`RequestStatsOutput`], [`RequestTrendsOutput`], [`ModelsOutput`]) rendered
6//! by the `analytics requests` commands.
7
8mod list;
9mod models;
10mod stats;
11mod trends;
12
13use anyhow::Result;
14use clap::Subcommand;
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17
18use crate::context::CommandContext;
19use crate::shared::render_result;
20
21#[derive(Debug, Subcommand)]
22pub enum RequestsCommands {
23    #[command(
24        about = "Dashboard request metrics: time range, model filter, cache-hit rate, CSV export. For a quick operational aggregate, use `infra logs request stats`"
25    )]
26    Stats(stats::StatsArgs),
27
28    #[command(
29        about = "Dashboard list of AI requests with time range, model filter, and CSV export. For a quick operational list, use `infra logs request list`"
30    )]
31    List(list::ListArgs),
32
33    #[command(about = "AI request trends over time")]
34    Trends(trends::TrendsArgs),
35
36    #[command(about = "Model usage breakdown")]
37    Models(models::ModelsArgs),
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
41pub struct RequestStatsOutput {
42    pub period: String,
43    pub total_requests: i64,
44    pub total_tokens: i64,
45    pub input_tokens: i64,
46    pub output_tokens: i64,
47    pub total_cost_microdollars: i64,
48    pub avg_latency_ms: i64,
49    pub cache_hit_rate: f64,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
53pub struct RequestTrendPoint {
54    pub timestamp: String,
55    pub request_count: i64,
56    pub total_tokens: i64,
57    pub cost_microdollars: i64,
58    pub avg_latency_ms: i64,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
62pub struct RequestTrendsOutput {
63    pub period: String,
64    pub group_by: String,
65    pub points: Vec<RequestTrendPoint>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
69pub struct ModelUsageRow {
70    pub provider: String,
71    pub model: String,
72    pub request_count: i64,
73    pub total_tokens: i64,
74    pub total_cost_microdollars: i64,
75    pub avg_latency_ms: i64,
76    pub percentage: f64,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
80pub struct ModelsOutput {
81    pub period: String,
82    pub models: Vec<ModelUsageRow>,
83    pub total_requests: i64,
84}
85
86pub async fn execute(command: RequestsCommands, ctx: &CommandContext) -> Result<()> {
87    let db_ctx = ctx.database().await?;
88    match command {
89        RequestsCommands::Stats(args) => {
90            let result = stats::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
91            render_result(&result, &ctx.cli);
92            Ok(())
93        },
94        RequestsCommands::List(args) => {
95            let result = list::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
96            render_result(&result, &ctx.cli);
97            Ok(())
98        },
99        RequestsCommands::Trends(args) => {
100            let result = trends::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
101            render_result(&result, &ctx.cli);
102            Ok(())
103        },
104        RequestsCommands::Models(args) => {
105            let result = models::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
106            render_result(&result, &ctx.cli);
107            Ok(())
108        },
109    }
110}