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 navigation;
14mod pages;
15mod sources;
16
17use anyhow::Result;
18use clap::Subcommand;
19use schemars::JsonSchema;
20use serde::{Deserialize, Serialize};
21
22use crate::context::CommandContext;
23use crate::shared::render_result;
24
25#[derive(Debug, Subcommand)]
26pub enum TrafficCommands {
27    #[command(about = "Traffic source breakdown", alias = "list")]
28    Sources(sources::SourcesArgs),
29
30    #[command(about = "Sessions by landing page")]
31    Pages(pages::PagesArgs),
32
33    #[command(about = "Internal link-click transitions")]
34    Navigation(navigation::NavigationArgs),
35
36    #[command(about = "Geographic distribution")]
37    Geo(geo::GeoArgs),
38
39    #[command(about = "Device and browser breakdown")]
40    Devices(devices::DevicesArgs),
41
42    #[command(about = "Bot traffic analysis")]
43    Bots(bots::BotsArgs),
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
47pub struct TrafficSourceRow {
48    pub source: String,
49    pub session_count: i64,
50    pub percentage: f64,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
54pub struct TrafficSourcesOutput {
55    pub period: String,
56    pub sources: Vec<TrafficSourceRow>,
57    pub total_sessions: i64,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
61pub struct TrafficPageRow {
62    pub page: String,
63    pub source: String,
64    pub session_count: i64,
65    pub percentage: f64,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
69pub struct TrafficPagesOutput {
70    pub period: String,
71    pub pages: Vec<TrafficPageRow>,
72    pub total_sessions: i64,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
76pub struct NavigationRow {
77    pub from_path: String,
78    pub to_path: String,
79    pub click_count: i64,
80    pub percentage: f64,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
84pub struct NavigationOutput {
85    pub period: String,
86    pub transitions: Vec<NavigationRow>,
87    pub total_clicks: i64,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
91pub struct GeoRow {
92    pub country: String,
93    pub session_count: i64,
94    pub percentage: f64,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
98pub struct GeoOutput {
99    pub period: String,
100    pub countries: Vec<GeoRow>,
101    pub total_sessions: i64,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
105pub struct DeviceRow {
106    pub device_type: String,
107    pub browser: String,
108    pub session_count: i64,
109    pub percentage: f64,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
113pub struct DevicesOutput {
114    pub period: String,
115    pub devices: Vec<DeviceRow>,
116    pub total_sessions: i64,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
120pub struct BotRow {
121    pub bot_type: String,
122    pub session_count: i64,
123    pub percentage: f64,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
127pub struct BotsOutput {
128    pub period: String,
129    pub human_sessions: i64,
130    pub ghost_sessions: i64,
131    pub bot_sessions: i64,
132    pub bot_percentage: f64,
133    pub bot_breakdown: Vec<BotRow>,
134}
135
136pub async fn execute(command: TrafficCommands, ctx: &CommandContext) -> Result<()> {
137    let db_ctx = ctx.database().await?;
138    match command {
139        TrafficCommands::Sources(args) => {
140            let result = sources::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
141            render_result(&result, &ctx.cli);
142            Ok(())
143        },
144        TrafficCommands::Pages(args) => {
145            let result = pages::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
146            render_result(&result, &ctx.cli);
147            Ok(())
148        },
149        TrafficCommands::Navigation(args) => {
150            let result = navigation::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
151            render_result(&result, &ctx.cli);
152            Ok(())
153        },
154        TrafficCommands::Geo(args) => {
155            let result = geo::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
156            render_result(&result, &ctx.cli);
157            Ok(())
158        },
159        TrafficCommands::Devices(args) => {
160            let result = devices::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
161            render_result(&result, &ctx.cli);
162            Ok(())
163        },
164        TrafficCommands::Bots(args) => {
165            let result = bots::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
166            render_result(&result, &ctx.cli);
167            Ok(())
168        },
169    }
170}