Skip to main content

systemprompt_cli/commands/analytics/costs/
mod.rs

1//! Cost analytics: spend summary, trends over time, and breakdown by model or
2//! agent.
3//!
4//! Defines the [`CostsCommands`] subcommand tree and the typed output shapes
5//! ([`CostSummaryOutput`], [`CostTrendsOutput`], [`CostBreakdownOutput`])
6//! rendered by the `analytics costs` commands.
7
8mod breakdown;
9mod summary;
10mod trends;
11
12use anyhow::Result;
13use clap::Subcommand;
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16use systemprompt_runtime::DatabaseContext;
17
18use crate::CliConfig;
19use crate::shared::render_result;
20
21#[derive(Debug, Subcommand)]
22pub enum CostsCommands {
23    #[command(about = "Cost summary", alias = "list")]
24    Summary(summary::SummaryArgs),
25
26    #[command(about = "Cost trends over time")]
27    Trends(trends::TrendsArgs),
28
29    #[command(about = "Cost breakdown by model/agent")]
30    Breakdown(breakdown::BreakdownArgs),
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
34pub struct CostSummaryOutput {
35    pub period: String,
36    pub total_cost_microdollars: i64,
37    pub total_requests: i64,
38    pub total_tokens: i64,
39    pub avg_cost_per_request_microdollars: f64,
40    pub change_percent: Option<f64>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
44pub struct CostTrendPoint {
45    pub timestamp: String,
46    pub cost_microdollars: i64,
47    pub request_count: i64,
48    pub tokens: i64,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
52pub struct CostTrendsOutput {
53    pub period: String,
54    pub group_by: String,
55    pub points: Vec<CostTrendPoint>,
56    pub total_cost_microdollars: i64,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60pub struct CostBreakdownItem {
61    pub name: String,
62    pub cost_microdollars: i64,
63    pub request_count: i64,
64    pub tokens: i64,
65    pub percentage: f64,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
69pub struct CostBreakdownOutput {
70    pub period: String,
71    pub breakdown_by: String,
72    pub items: Vec<CostBreakdownItem>,
73    pub total_cost_microdollars: i64,
74}
75
76pub async fn execute(command: CostsCommands, config: &CliConfig) -> Result<()> {
77    match command {
78        CostsCommands::Summary(args) => {
79            let result = summary::execute(args, config).await?;
80            render_result(&result);
81            Ok(())
82        },
83        CostsCommands::Trends(args) => {
84            let result = trends::execute(args, config).await?;
85            render_result(&result);
86            Ok(())
87        },
88        CostsCommands::Breakdown(args) => {
89            let result = breakdown::execute(args, config).await?;
90            render_result(&result);
91            Ok(())
92        },
93    }
94}
95
96pub async fn execute_with_pool(
97    command: CostsCommands,
98    db_ctx: &DatabaseContext,
99    config: &CliConfig,
100) -> Result<()> {
101    match command {
102        CostsCommands::Summary(args) => {
103            let result = summary::execute_with_pool(args, db_ctx, config).await?;
104            render_result(&result);
105            Ok(())
106        },
107        CostsCommands::Trends(args) => {
108            let result = trends::execute_with_pool(args, db_ctx, config).await?;
109            render_result(&result);
110            Ok(())
111        },
112        CostsCommands::Breakdown(args) => {
113            let result = breakdown::execute_with_pool(args, db_ctx, config).await?;
114            render_result(&result);
115            Ok(())
116        },
117    }
118}