Skip to main content

systemprompt_cli/commands/analytics/traffic/
mod.rs

1//! Traffic analytics: source, geographic, device, and bot-traffic breakdowns.
2//!
3//! Defines the [`TrafficCommands`] subcommand tree and the typed output shapes
4//! ([`TrafficSourcesOutput`], [`GeoOutput`], [`DevicesOutput`], [`BotsOutput`])
5//! rendered by the `analytics traffic` commands.
6//!
7//! Copyright (c) systemprompt.io — Business Source License 1.1.
8//! See <https://systemprompt.io> for licensing details.
9
10mod bots;
11mod devices;
12mod geo;
13mod sources;
14
15use anyhow::Result;
16use clap::Subcommand;
17use schemars::JsonSchema;
18use serde::{Deserialize, Serialize};
19
20use crate::context::CommandContext;
21use crate::shared::render_result;
22
23#[derive(Debug, Subcommand)]
24pub enum TrafficCommands {
25    #[command(about = "Traffic source breakdown", alias = "list")]
26    Sources(sources::SourcesArgs),
27
28    #[command(about = "Geographic distribution")]
29    Geo(geo::GeoArgs),
30
31    #[command(about = "Device and browser breakdown")]
32    Devices(devices::DevicesArgs),
33
34    #[command(about = "Bot traffic analysis")]
35    Bots(bots::BotsArgs),
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
39pub struct TrafficSourceRow {
40    pub source: String,
41    pub session_count: i64,
42    pub percentage: f64,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
46pub struct TrafficSourcesOutput {
47    pub period: String,
48    pub sources: Vec<TrafficSourceRow>,
49    pub total_sessions: i64,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
53pub struct GeoRow {
54    pub country: String,
55    pub session_count: i64,
56    pub percentage: f64,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60pub struct GeoOutput {
61    pub period: String,
62    pub countries: Vec<GeoRow>,
63    pub total_sessions: i64,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
67pub struct DeviceRow {
68    pub device_type: String,
69    pub browser: String,
70    pub session_count: i64,
71    pub percentage: f64,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
75pub struct DevicesOutput {
76    pub period: String,
77    pub devices: Vec<DeviceRow>,
78    pub total_sessions: i64,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
82pub struct BotRow {
83    pub bot_type: String,
84    pub request_count: i64,
85    pub percentage: f64,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
89pub struct BotsOutput {
90    pub period: String,
91    pub human_sessions: i64,
92    pub bot_sessions: i64,
93    pub bot_percentage: f64,
94    pub bot_breakdown: Vec<BotRow>,
95}
96
97pub async fn execute(command: TrafficCommands, ctx: &CommandContext) -> Result<()> {
98    let db_ctx = ctx.database().await?;
99    match command {
100        TrafficCommands::Sources(args) => {
101            let result = sources::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
102            render_result(&result, &ctx.cli);
103            Ok(())
104        },
105        TrafficCommands::Geo(args) => {
106            let result = geo::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
107            render_result(&result, &ctx.cli);
108            Ok(())
109        },
110        TrafficCommands::Devices(args) => {
111            let result = devices::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
112            render_result(&result, &ctx.cli);
113            Ok(())
114        },
115        TrafficCommands::Bots(args) => {
116            let result = bots::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
117            render_result(&result, &ctx.cli);
118            Ok(())
119        },
120    }
121}