Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/cortex-cli/src/stats_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ fn infer_provider(model: &str) -> String {
}

/// Collect statistics from session files.
async fn collect_stats(sessions_dir: &PathBuf, cli: &StatsCli) -> Result<UsageStats> {
pub(super) async fn collect_stats(sessions_dir: &PathBuf, cli: &StatsCli) -> Result<UsageStats> {
let mut stats = UsageStats::default();

// Calculate date range
Expand Down Expand Up @@ -363,10 +363,15 @@ async fn collect_stats(sessions_dir: &PathBuf, cli: &StatsCli) -> Result<UsageSt
// Try to parse the session
if let Ok(session_data) = parse_session_file(&path) {
// Check if session is within date range
if let Some(ref timestamp) = session_data.timestamp
&& let Ok(session_date) = chrono::DateTime::parse_from_rfc3339(timestamp)
&& session_date < start_date
{
let Some(session_date) = session_data
.timestamp
.as_deref()
.and_then(|timestamp| chrono::DateTime::parse_from_rfc3339(timestamp).ok())
else {
continue;
};

if session_date < start_date {
continue;
}

Expand Down
57 changes: 57 additions & 0 deletions src/cortex-cli/tests/stats_days_timestamp_filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use serde_json::json;

#[path = "../src/stats_cmd.rs"]
mod stats_cmd;

fn write_session(
sessions_dir: &std::path::Path,
name: &str,
timestamp: Option<&str>,
input_tokens: u64,
) {
let mut session = json!({
"model": "gpt-4o",
"usage": {
"input_tokens": input_tokens,
"output_tokens": 1
}
});

if let Some(timestamp) = timestamp {
session["timestamp"] = json!(timestamp);
}

std::fs::write(
sessions_dir.join(name),
serde_json::to_string_pretty(&session).expect("serialize session"),
)
.expect("write session");
}

#[tokio::test]
async fn stats_days_ignores_sessions_without_parseable_timestamps() {
let temp = tempfile::tempdir().expect("temp dir");
let sessions_dir = temp.path().join("sessions");
std::fs::create_dir_all(&sessions_dir).expect("create sessions dir");

let recent_timestamp = chrono::Utc::now().to_rfc3339();
write_session(&sessions_dir, "recent.json", Some(&recent_timestamp), 10);
write_session(&sessions_dir, "old.json", Some("2000-01-01T00:00:00Z"), 100);
write_session(&sessions_dir, "invalid.json", Some("not-a-date"), 1_000);
write_session(&sessions_dir, "missing.json", None, 10_000);

let cli = stats_cmd::StatsCli {
days: 1,
provider: None,
model: None,
json: true,
verbose: false,
};
let stats = stats_cmd::collect_stats(&sessions_dir, &cli)
.await
.expect("collect stats");

assert_eq!(stats.total_sessions, 1);
assert_eq!(stats.input_tokens, 10);
assert_eq!(stats.output_tokens, 1);
}