feat: revamp analytics dashboards and harden database migrations

Add dashboard and overview analytics, health monitoring, provider expense tracking, and announcement updates across the gateway and frontend.

Keep schema migrations free of historical backfills while preserving automatic backfill execution. Bound migration deadlines, run schema preparation before Compose replacement, and anonymize deleted dashboard users.

Include the current documentation cleanup and regression coverage.
This commit is contained in:
elky
2026-10-01 11:48:17 +08:00
parent 60b89cc840
commit 066ea87d72
327 changed files with 31728 additions and 20645 deletions
+1
View File
@@ -17,6 +17,7 @@ aether-provider-transport.workspace = true
axum.workspace = true
base64.workspace = true
chrono.workspace = true
chrono-tz.workspace = true
http.workspace = true
reqwest.workspace = true
regex.workspace = true
@@ -0,0 +1,201 @@
use super::{envelope, page_value, OverviewRequest, OVERVIEW_EXPORT_LIMIT};
use aether_data_contracts::repository::usage::{StoredUsageAnalytics, UsageAnalyticsView};
use serde_json::Value;
pub fn export_csv(
request: &OverviewRequest,
snapshot: &StoredUsageAnalytics,
) -> Result<String, String> {
if snapshot.total > u64::from(OVERVIEW_EXPORT_LIMIT) {
return Err(format!(
"export exceeds {OVERVIEW_EXPORT_LIMIT} rows; narrow the report range or filters"
));
}
let page = page_value(request, snapshot);
let items = page["items"].as_array().ok_or("invalid export data")?;
if items.len() as u64 != snapshot.total {
return Err("the complete export could not be read from one snapshot".into());
}
let columns: &[&str] = match request.query.view {
UsageAnalyticsView::Users => &[
"user_id",
"username",
"email",
"is_active",
"last_used_at",
"active_days",
"request_count",
"successful_request_count",
"failed_request_count",
"total_tokens",
"billable_amount.value",
"billable_amount.status",
"quota_covered_amount.value",
"wallet_consumed_amount.value",
"wallet_debit_amount.value",
"finance.wallet_balance.value",
"finance.wallet_balance.status",
"finance.recharge_balance.value",
"finance.gift_balance.value",
"finance.recharge_amount.value",
"finance.recharge_count",
"finance.plan_purchase_amount.value",
"finance.plan_purchase_count",
"finance.gift_credit_amount.value",
"finance.gift_credit_count",
"finance.balance_time_basis",
"finance.payment_time_basis",
],
UsageAnalyticsView::Consumption => &[
"id",
"request_id",
"started_at",
"user_id",
"credential_owner_id",
"model",
"provider",
"status",
"settlement_status",
"attribution_kind",
"attribution_source",
"rated_amount.value",
"billable_amount.value",
"quota_covered_amount.value",
"wallet_consumed_amount.value",
"wallet_debit_amount.value",
],
UsageAnalyticsView::Breakdown => &[
"id",
"label",
"request_count",
"successful_request_count",
"failed_request_count",
"total_tokens",
"rated_amount.value",
"billable_amount.value",
"billable_amount.status",
"quota_covered_amount.value",
"wallet_consumed_amount.value",
"wallet_debit_amount.value",
],
_ => return Err("this report does not support CSV".into()),
};
let mut columns = columns.to_vec();
columns.extend([
"report.range.from",
"report.range.to",
"report.range.timezone",
"report.scope.kind",
"report.metric_version",
"report.read_revision",
"report.coverage.status",
"report.coverage.unrecoverable_bucket_count",
"billable_amount.currency",
"rated_amount.status",
"quota_covered_amount.status",
"wallet_consumed_amount.status",
"wallet_debit_amount.status",
]);
let metadata = envelope(request, snapshot, Value::Null)["meta"].clone();
let mut output = String::from("\u{feff}");
output.push_str(&columns.join(","));
output.push_str("\r\n");
for row in items {
let mut row = row.clone();
row["report"] = metadata.clone();
for (index, column) in columns.iter().enumerate() {
if index > 0 {
output.push(',');
}
let value = column.split('.').fold(&row, |value, field| &value[field]);
let text = match value {
Value::Null => String::new(),
Value::String(value) => value.clone(),
value => value.to_string(),
};
output.push_str(&escape(&text));
}
output.push_str("\r\n");
}
Ok(output)
}
fn escape(value: &str) -> String {
let formula = value.trim_start().starts_with(['=', '+', '-', '@'])
|| value.starts_with(['\t', '\r', '\n']);
format!(
"\"{}{}\"",
if formula { "'" } else { "" },
value.replace('"', "\"\"")
)
}
#[cfg(test)]
mod tests {
use super::*;
use aether_data_contracts::repository::usage::{
UsageAnalyticsQuery, UsageAnalyticsUser, UsageAnalyticsUserFinance,
};
#[test]
fn quotes_csv_and_prevents_user_fields_from_becoming_formulas() {
assert_eq!(escape("=SUM(1,2)"), "\"'=SUM(1,2)\"");
assert_eq!(escape("a\"b\nc"), "\"a\"\"b\nc\"");
assert_eq!(escape("12.34567890"), "\"12.34567890\"");
}
#[test]
fn user_export_keeps_consumption_credits_and_current_balances_separate() {
let request = OverviewRequest {
query: UsageAnalyticsQuery {
view: UsageAnalyticsView::Users,
..Default::default()
},
csv: true,
amount_basis: "billable".into(),
};
let snapshot = StoredUsageAnalytics {
total: 1,
users: vec![UsageAnalyticsUser {
user_id: "member".into(),
username: "Alice".into(),
email: None,
is_active: true,
last_used_at: None,
active_days: 1,
metrics: aether_data_contracts::repository::usage::UsageAnalyticsMetrics {
billable_amount: Some("3.00000000".into()),
..Default::default()
},
finance: Some(UsageAnalyticsUserFinance {
wallet_balance: Some("12.00000000".into()),
recharge_amount: Some("100.00000000".into()),
recharge_count: 1,
plan_purchase_amount: Some("25.00000000".into()),
plan_purchase_count: 1,
..Default::default()
}),
}],
..Default::default()
};
let csv = export_csv(&request, &snapshot).unwrap();
let mut lines = csv.trim_start_matches('\u{feff}').lines();
let headers = lines.next().unwrap().split(',').collect::<Vec<_>>();
let cells = lines.next().unwrap().split(',').collect::<Vec<_>>();
let value = |column| {
cells[headers
.iter()
.position(|candidate| *candidate == column)
.unwrap()]
};
assert_eq!(value("billable_amount.value"), "\"3.00000000\"");
assert_eq!(value("finance.wallet_balance.value"), "\"12.00000000\"");
assert_eq!(value("finance.recharge_amount.value"), "\"100.00000000\"");
assert_eq!(
value("finance.plan_purchase_amount.value"),
"\"25.00000000\""
);
assert_eq!(value("finance.balance_time_basis"), "\"current\"");
assert_eq!(value("finance.payment_time_basis"), "\"credited_at\"");
}
}
@@ -0,0 +1,318 @@
use super::{envelope, metrics_value, parse_overview_query, OverviewRequest};
use aether_data_contracts::repository::usage::{
StoredUsageAnalytics, StoredUsageDashboardAnalytics, UsageAnalyticsQuery, UsageAnalyticsRow,
UsageAnalyticsView, UsageDashboardAnalyticsQuery,
};
use chrono::DateTime;
use serde_json::{json, Value};
pub fn parse_dashboard_query(raw: Option<&str>) -> Result<UsageDashboardAnalyticsQuery, String> {
let mut timezone = None;
for (key, value) in url::form_urlencoded::parse(raw.unwrap_or_default().as_bytes()) {
if key != "timezone" {
return Err(format!("unsupported dashboard query parameter: {key}"));
}
if timezone.replace(value.into_owned()).is_some() {
return Err("duplicate query parameters are not supported".into());
}
}
let query = UsageDashboardAnalyticsQuery {
timezone: timezone.unwrap_or_else(|| "UTC".into()),
};
query.validate().map_err(|err| err.to_string())?;
Ok(query)
}
pub fn parse_dashboard_charts_query(raw: Option<&str>) -> Result<OverviewRequest, String> {
for (key, _) in url::form_urlencoded::parse(raw.unwrap_or_default().as_bytes()) {
if !matches!(key.as_ref(), "from" | "to" | "timezone" | "granularity") {
return Err(format!(
"unsupported dashboard charts query parameter: {key}"
));
}
}
let mut request = parse_overview_query(raw, UsageAnalyticsView::DashboardCharts)?;
request.query.limit = OVERVIEW_CHART_LIMIT;
Ok(request)
}
const OVERVIEW_CHART_LIMIT: u32 = 10_000;
pub fn dashboard_charts_value(snapshot: &StoredUsageAnalytics) -> Value {
let rows = |items: &[UsageAnalyticsRow]| {
items
.iter()
.map(|row| {
let mut value = metrics_value(&row.metrics);
if snapshot.unrecoverable_bucket_count > 0 {
mark_incomplete_amounts(&mut value);
}
value["id"] = json!(row.id);
value["label"] = json!(row.label);
value["bucket_start"] = json!(row.bucket_start);
value
})
.collect::<Vec<_>>()
};
let mut summary = metrics_value(&snapshot.summary);
if snapshot.unrecoverable_bucket_count > 0 {
mark_incomplete_amounts(&mut summary);
}
json!({
"summary": summary,
"series": rows(&snapshot.rows),
"models": rows(&snapshot.model_rows),
"providers": rows(&snapshot.provider_rows),
})
}
pub fn dashboard_value(
query: &UsageDashboardAnalyticsQuery,
snapshot: &StoredUsageDashboardAnalytics,
) -> Result<Value, String> {
let today = summary_value(query, &snapshot.today, &snapshot.today_from, &snapshot.to)?;
let mut total = summary_value(
query,
&snapshot.total,
snapshot.total_from.as_deref().unwrap_or(&snapshot.to),
&snapshot.to,
)?;
total["meta"]["range"]["period"] = json!("all_time");
total["meta"]["range"]["available_from"] = json!(snapshot.total_from);
// Lifetime cards intentionally omit expensive historical diagnostics. Do not expose
// their uncomputed defaults as measured zeroes.
let total_fields = [
"request_count",
"total_tokens",
"billable_amount",
"enabled_users",
];
total["data"]
.as_object_mut()
.expect("metrics are an object")
.retain(|key, _| total_fields.contains(&key.as_str()));
total["meta"]["available_metrics"] = json!(total_fields);
let total_coverage = total["meta"]["coverage"]
.as_object_mut()
.expect("coverage is an object");
total_coverage.remove("attribution_available_count");
total_coverage.remove("classified_failure_count");
if snapshot.history_complete == Some(false) {
total["meta"]["coverage"]["status"] = json!("partial");
mark_incomplete_amounts(&mut total["data"]);
}
Ok(json!({"today": today, "total": total, "history_complete": snapshot.history_complete}))
}
fn summary_value(
query: &UsageDashboardAnalyticsQuery,
snapshot: &StoredUsageAnalytics,
from: &str,
to: &str,
) -> Result<Value, String> {
let timestamp = |value: &str| {
let parsed = DateTime::parse_from_rfc3339(value)
.map_err(|_| "invalid dashboard snapshot timestamp".to_string())?;
u64::try_from(parsed.timestamp_millis())
.map_err(|_| "invalid dashboard snapshot timestamp".to_string())
};
let request = OverviewRequest {
query: UsageAnalyticsQuery {
from_unix_ms: timestamp(from)?,
to_unix_ms: timestamp(to)?,
timezone: query.timezone.clone(),
..Default::default()
},
csv: false,
amount_basis: "billable".into(),
};
let mut data = metrics_value(&snapshot.summary);
if snapshot.unrecoverable_bucket_count > 0 {
mark_incomplete_amounts(&mut data);
}
Ok(envelope(&request, snapshot, data))
}
fn mark_incomplete_amounts(data: &mut Value) {
for key in [
"rated_amount",
"billable_amount",
"quota_covered_amount",
"wallet_consumed_amount",
"wallet_debit_amount",
"wallet_recharge_debit_amount",
"wallet_gift_debit_amount",
"wallet_overdraft_amount",
] {
if data[key]["status"] == "known" {
data[key]["status"] = json!("known_subtotal");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dashboard_accepts_only_one_valid_timezone() {
assert_eq!(parse_dashboard_query(None).unwrap().timezone, "UTC");
assert_eq!(
parse_dashboard_query(Some("timezone=Asia%2FShanghai"))
.unwrap()
.timezone,
"Asia/Shanghai"
);
for raw in [
"timezone=invalid",
"timezone=UTC&timezone=UTC",
"timezone=UTC&from=2026-01-01T00:00:00Z",
"user_id=employee-1",
"model=test-model",
] {
assert!(parse_dashboard_query(Some(raw)).is_err(), "{raw}");
}
}
#[test]
fn dashboard_charts_keeps_a_bounded_installation_range() {
let range =
"from=2026-09-01T00:00:00Z&to=2026-09-08T00:00:00Z&timezone=UTC&granularity=day";
let request = parse_dashboard_charts_query(Some(range)).unwrap();
assert_eq!(request.query.view, UsageAnalyticsView::DashboardCharts);
assert_eq!(request.query.limit, 10_000);
for suffix in [
"&user_id=alice",
"&model=test",
"&limit=1",
"&format=csv",
"&timezone=UTC",
] {
assert!(parse_dashboard_charts_query(Some(&format!("{range}{suffix}"))).is_err());
}
}
#[test]
fn dashboard_marks_known_missing_history_as_partial() {
let mut snapshot = StoredUsageDashboardAnalytics {
today_from: "2026-09-11T00:00:00Z".into(),
total_from: Some("2024-01-01T00:00:00Z".into()),
to: "2026-09-11T12:00:00Z".into(),
history_complete: Some(false),
..Default::default()
};
snapshot.total.summary.billable_amount = Some("3.00000000".into());
let value = dashboard_value(&parse_dashboard_query(None).unwrap(), &snapshot).unwrap();
assert_eq!(value["total"]["meta"]["range"]["period"], "all_time");
assert_eq!(value["total"]["meta"]["coverage"]["status"], "partial");
assert_eq!(
value["total"]["data"]["billable_amount"]["status"],
"known_subtotal"
);
assert_eq!(value["today"]["meta"]["coverage"]["status"], "complete");
assert_eq!(value["history_complete"], false);
}
#[test]
fn dashboard_today_preserves_known_deleted_usage_coverage() {
let mut snapshot = StoredUsageDashboardAnalytics {
today_from: "2026-09-11T00:00:00Z".into(),
to: "2026-09-11T12:00:00Z".into(),
..Default::default()
};
snapshot.today.unrecoverable_bucket_count = 1;
snapshot.today.summary.billable_amount = Some("2.00000000".into());
let value = dashboard_value(&parse_dashboard_query(None).unwrap(), &snapshot).unwrap();
assert_eq!(value["today"]["meta"]["coverage"]["status"], "partial");
assert_eq!(
value["today"]["data"]["billable_amount"]["value"],
"2.00000000"
);
assert_eq!(
value["today"]["data"]["billable_amount"]["status"],
"known_subtotal"
);
}
#[test]
fn dashboard_total_exposes_only_computed_cards_and_preserves_coverage() {
let mut snapshot = StoredUsageDashboardAnalytics {
today_from: "2026-09-11T00:00:00Z".into(),
total_from: Some("2024-01-01T00:00:00Z".into()),
to: "2026-09-11T12:00:00Z".into(),
..Default::default()
};
snapshot.today.summary.successful_request_count = 2;
snapshot.today.summary.latency_p95_ms = Some(1200.0);
snapshot.total.summary = aether_data_contracts::repository::usage::UsageAnalyticsMetrics {
request_count: 3,
total_tokens: 120,
usage_available_count: 2,
pricing_available_count: 2,
settled_count: 2,
allocation_available_count: 1,
enabled_users: 4,
billable_amount: Some("1.25000000".into()),
..Default::default()
};
let value = dashboard_value(&parse_dashboard_query(None).unwrap(), &snapshot).unwrap();
assert_eq!(
value["total"]["data"],
json!({
"request_count": 3,
"total_tokens": 120,
"enabled_users": 4,
"billable_amount": {
"value": "1.25000000", "currency": "USD", "basis": "billable", "status": "known_subtotal"
}
})
);
assert_eq!(value["total"]["meta"]["coverage"]["status"], "partial");
assert_eq!(
value["total"]["meta"]["coverage"]["allocation_available_count"],
1
);
assert_eq!(
value["total"]["meta"]["coverage"]["usage_available_count"],
2
);
assert!(value["total"]["meta"]["coverage"]
.get("attribution_available_count")
.is_none());
assert!(value["total"]["meta"]["coverage"]
.get("classified_failure_count")
.is_none());
assert_eq!(value["today"]["data"]["successful_request_count"], 2);
assert_eq!(value["today"]["data"]["latency_ms"]["p95"], 1200.0);
}
#[test]
fn dashboard_charts_marks_all_amounts_in_a_window_with_lost_history() {
let mut snapshot = StoredUsageAnalytics {
unrecoverable_bucket_count: 1,
..Default::default()
};
snapshot.summary.billable_amount = Some("2.00000000".into());
let row = UsageAnalyticsRow {
id: Some("model-1".into()),
label: None,
bucket_start: Some("2026-09-11T00:00:00Z".into()),
metrics: snapshot.summary.clone(),
};
snapshot.rows.push(row.clone());
snapshot.model_rows.push(row.clone());
snapshot.provider_rows.push(row);
let data = dashboard_charts_value(&snapshot);
assert_eq!(
data["summary"]["billable_amount"]["status"],
"known_subtotal"
);
for group in ["series", "models", "providers"] {
assert_eq!(data[group][0]["billable_amount"]["value"], "2.00000000");
assert_eq!(
data[group][0]["billable_amount"]["status"],
"known_subtotal"
);
}
}
}
@@ -0,0 +1,123 @@
use super::amount;
use aether_data_contracts::repository::usage::{DashboardSummaryMetrics, StoredDashboardSummary};
use serde_json::{json, Value};
/// The homepage reads a durable aggregate, not the full historical report.
pub fn dashboard_summary_value(snapshot: &StoredDashboardSummary) -> Value {
let total = metrics_value(&snapshot.total);
json!({
"stats_since": snapshot.stats_since,
"generated_at": snapshot.generated_at,
"timezone": snapshot.timezone,
"today_from": snapshot.today_from,
"window_seconds": snapshot.window_seconds,
"today": metrics_value(&snapshot.today),
"total": {
"request_count": total["request_count"],
"total_tokens": total["total_tokens"],
"cache_read_tokens": total["cache_read_tokens"],
"cache_input_tokens": total["cache_input_tokens"],
"billable_amount": total["billable_amount"],
},
"users": snapshot.users,
"active_days": snapshot.active_days,
"consecutive_active_days": snapshot.consecutive_active_days,
"activity_days": snapshot.activity_days,
})
}
fn metrics_value(metrics: &DashboardSummaryMetrics) -> Value {
let empty = metrics.request_count == 0;
let tokens_available = empty || metrics.usage_available_count > 0;
let billable = if empty {
Some("0.00000000".to_string())
} else {
metrics.billable_amount.clone()
};
let average = |sum: f64, samples: u64| {
(samples > 0 && sum.is_finite() && sum >= 0.0).then(|| sum / samples as f64)
};
json!({
"request_count": metrics.request_count,
"input_tokens": tokens_available.then_some(metrics.input_tokens),
"output_tokens": tokens_available.then_some(metrics.output_tokens),
"total_tokens": tokens_available.then_some(metrics.total_tokens),
"billable_amount": amount(&billable, "billable", metrics.pricing_available_count == metrics.request_count),
"active_users": metrics.active_users,
"cache_read_tokens": tokens_available.then_some(metrics.cache_read_tokens),
"cache_creation_tokens": tokens_available.then_some(metrics.cache_creation_tokens),
"cache_input_tokens": tokens_available.then_some(metrics.cache_input_tokens),
"avg_first_byte_ms": average(metrics.first_byte_sum_ms, metrics.first_byte_sample_count),
"avg_response_ms": average(metrics.response_sum_ms, metrics.response_sample_count),
"stream_requests": metrics.stream_requests,
"standard_requests": metrics.standard_requests,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compact_summary_preserves_unknown_cost_and_weighted_response_samples() {
let mut snapshot = StoredDashboardSummary {
stats_since: "2026-09-19T00:00:00Z".into(),
active_days: 90,
consecutive_active_days: 12,
today: DashboardSummaryMetrics {
request_count: 3,
response_sum_ms: 600.0,
response_sample_count: 2,
first_byte_sum_ms: 80.0,
first_byte_sample_count: 1,
..Default::default()
},
total: DashboardSummaryMetrics {
request_count: 3,
usage_available_count: 3,
cache_read_tokens: 120,
cache_input_tokens: 400,
pricing_available_count: 1,
billable_amount: Some("1.23456789".into()),
..Default::default()
},
..Default::default()
};
let value = dashboard_summary_value(&snapshot);
assert_eq!(value["active_days"], 90);
assert_eq!(value["consecutive_active_days"], 12);
assert_eq!(value["today"]["avg_response_ms"], 300.0);
assert_eq!(value["today"]["avg_first_byte_ms"], 80.0);
assert_eq!(value["today"]["billable_amount"]["status"], "unknown");
assert!(value["today"]["total_tokens"].is_null());
assert_eq!(
value["total"]["billable_amount"]["status"],
"known_subtotal"
);
assert_eq!(value["total"]["billable_amount"]["value"], "1.23456789");
assert_eq!(value["total"]["cache_read_tokens"], 120);
assert_eq!(value["total"]["cache_input_tokens"], 400);
assert!(value["today"].get("latency_p95_ms").is_none());
snapshot.total.usage_available_count = 0;
let unknown = dashboard_summary_value(&snapshot);
assert!(unknown["total"].get("cache_read_tokens").unwrap().is_null());
assert!(unknown["total"]
.get("cache_input_tokens")
.unwrap()
.is_null());
}
#[test]
fn empty_collection_has_zero_cost_but_no_measured_latency() {
let value = dashboard_summary_value(&StoredDashboardSummary::default());
assert_eq!(value["consecutive_active_days"], 0);
assert_eq!(value["today"]["billable_amount"]["value"], "0.00000000");
assert_eq!(value["today"]["billable_amount"]["status"], "known");
assert!(value["today"]["avg_response_ms"].is_null());
assert!(value["today"]["avg_first_byte_ms"].is_null());
assert_eq!(value["total"]["request_count"], 0);
assert_eq!(value["total"]["cache_read_tokens"], 0);
assert_eq!(value["total"]["cache_input_tokens"], 0);
}
}
@@ -0,0 +1,16 @@
mod csv;
mod dashboard;
mod dashboard_summary;
mod query;
mod response;
pub use csv::export_csv;
pub use dashboard::{
dashboard_charts_value, dashboard_value, parse_dashboard_charts_query, parse_dashboard_query,
};
pub use dashboard_summary::dashboard_summary_value;
pub use query::{parse_overview_query, OverviewRequest, OVERVIEW_EXPORT_LIMIT};
pub use response::{
amount, consumption_value, costs_value, envelope, metrics_value, page_value, performance_value,
user_finance_value, user_payments_value,
};
@@ -0,0 +1,318 @@
use aether_data_contracts::repository::usage::{
UsageAnalyticsGranularity, UsageAnalyticsGroupBy, UsageAnalyticsQuery, UsageAnalyticsSort,
UsageAnalyticsView,
};
use chrono::{DateTime, Utc};
use std::collections::BTreeMap;
pub const OVERVIEW_EXPORT_LIMIT: u32 = 10_000;
#[derive(Debug)]
pub struct OverviewRequest {
pub query: UsageAnalyticsQuery,
pub csv: bool,
pub amount_basis: String,
}
pub fn parse_overview_query(
raw: Option<&str>,
view: UsageAnalyticsView,
) -> Result<OverviewRequest, String> {
let mut params = BTreeMap::new();
for (key, value) in url::form_urlencoded::parse(raw.unwrap_or_default().as_bytes()) {
if params
.insert(key.into_owned(), value.into_owned())
.is_some()
{
return Err("duplicate query parameters are not supported".into());
}
}
let from = timestamp(&mut params, "from")?;
let to = timestamp(&mut params, "to")?;
let timezone = params.remove("timezone").unwrap_or_else(|| "UTC".into());
let csv = match params.remove("format").as_deref() {
None | Some("json") => false,
Some("csv")
if matches!(
view,
UsageAnalyticsView::Users
| UsageAnalyticsView::Consumption
| UsageAnalyticsView::Breakdown
) =>
{
true
}
_ => return Err("format is not supported for this report".into()),
};
let limit = number(&mut params, "limit", 25_u32)?;
let offset = number(&mut params, "offset", 0_u64)?;
let payment_limit = params
.remove("payment_limit")
.map(|value| {
value
.parse::<u32>()
.map_err(|_| "invalid payment_limit".to_string())
})
.transpose()?;
let payment_offset = params
.remove("payment_offset")
.map(|value| {
value
.parse::<u64>()
.map_err(|_| "invalid payment_offset".to_string())
})
.transpose()?;
if limit == 0 || limit > 100 {
return Err("limit must be between 1 and 100".into());
}
if csv && offset != 0 {
return Err("CSV exports apply to the complete filter; offset must be zero".into());
}
let granularity = match params.remove("granularity").as_deref() {
None | Some("day") => UsageAnalyticsGranularity::Day,
Some("hour") => UsageAnalyticsGranularity::Hour,
_ => return Err("granularity must be hour or day".into()),
};
if granularity == UsageAnalyticsGranularity::Hour && to.saturating_sub(from) > 31 * 86_400_000 {
return Err("hourly reports are limited to 31 days".into());
}
let group_by = match params.remove("group_by").as_deref() {
None | Some("model") => UsageAnalyticsGroupBy::Model,
Some("provider") => UsageAnalyticsGroupBy::Provider,
Some("api_key") => UsageAnalyticsGroupBy::ApiKey,
Some("attribution") => UsageAnalyticsGroupBy::Attribution,
Some("api_format") => UsageAnalyticsGroupBy::ApiFormat,
Some("request_type") => UsageAnalyticsGroupBy::RequestType,
_ => return Err("unsupported group_by dimension".into()),
};
let sort = match params.remove("sort").as_deref() {
None | Some("requests") | Some("request_count") => UsageAnalyticsSort::Requests,
Some("billable_amount") => UsageAnalyticsSort::BillableAmount,
Some("total_tokens") => UsageAnalyticsSort::Tokens,
Some("active_days") => UsageAnalyticsSort::ActiveDays,
Some("started_at") => UsageAnalyticsSort::StartedAt,
Some("last_used") | Some("last_used_at") => UsageAnalyticsSort::LastUsed,
Some("username") => UsageAnalyticsSort::Username,
_ => return Err("unsupported sort field".into()),
};
let descending = match params.remove("order").as_deref() {
None | Some("desc") => true,
Some("asc") => false,
_ => return Err("order must be asc or desc".into()),
};
let user_is_active = match params.remove("account_status").as_deref() {
None | Some("all") => None,
Some("active") | Some("enabled") => Some(true),
Some("inactive") | Some("disabled") => Some(false),
_ => return Err("unsupported account_status".into()),
};
let has_usage = match params.remove("usage_status").as_deref() {
None | Some("all") => None,
Some("used") | Some("active") => Some(true),
Some("unused") | Some("inactive") => Some(false),
_ => return Err("unsupported usage_status".into()),
};
if view != UsageAnalyticsView::Users && (user_is_active.is_some() || has_usage.is_some()) {
return Err(
"account_status and usage_status are only supported by employee reports".into(),
);
}
let amount_basis = params
.remove("amount_basis")
.unwrap_or_else(|| "billable".into());
if !matches!(
amount_basis.as_str(),
"rated" | "billable" | "quota_covered" | "wallet_consumed" | "wallet_debit"
) {
return Err("unsupported amount_basis".into());
}
let user_id = text(&mut params, "user_id")?;
let attribution_kind = text(&mut params, "attribution_kind")?;
let explicit_owner = text(&mut params, "credential_owner_id")?;
if user_id.is_some() && explicit_owner.is_some() {
return Err("user_id and credential_owner_id cannot be combined".into());
}
let member_account = attribution_kind.as_deref() == Some("employee");
let status = match text(&mut params, "status")?.as_deref() {
None => None,
Some("success" | "completed") => Some("completed".into()),
Some(value @ ("failed" | "cancelled" | "pending" | "streaming")) => Some(value.to_string()),
_ => return Err("unsupported request status".into()),
};
let search = text(&mut params, "search")?;
if search.is_some() && view != UsageAnalyticsView::Users {
return Err("search is only supported by employee reports".into());
}
let slow_threshold_ms = params
.remove("slow_threshold_ms")
.map(|value| {
value
.parse::<u64>()
.map_err(|_| "invalid slow_threshold_ms".to_string())
})
.transpose()?;
if slow_threshold_ms.is_some_and(|value| value == 0 || value > 86_400_000) {
return Err("slow_threshold_ms must be between 1 and 86400000".into());
}
let query = UsageAnalyticsQuery {
from_unix_ms: from,
to_unix_ms: to,
timezone,
view,
group_by,
granularity,
actor_user_id: member_account.then(|| user_id.clone()).flatten(),
credential_owner_id: if member_account {
explicit_owner
} else {
user_id.or(explicit_owner)
},
attribution_kind,
api_key_id: text(&mut params, "api_key_id")?,
model: text(&mut params, "model")?,
provider_id: text(&mut params, "provider_id")?,
api_format: text(&mut params, "api_format")?,
endpoint_kind: text(&mut params, "endpoint_kind")?,
request_type: text(&mut params, "request_type")?,
status,
is_stream: boolean(&mut params, "is_stream")?,
has_format_conversion: boolean(&mut params, "has_format_conversion")?,
slow_threshold_ms,
search,
user_is_active,
has_usage,
sort,
descending,
limit: if csv {
OVERVIEW_EXPORT_LIMIT + 1
} else {
limit
},
offset,
payment_limit,
payment_offset,
};
if let Some(key) = params.keys().next() {
return Err(format!("unsupported query parameter: {key}"));
}
query.validate().map_err(|err| err.to_string())?;
Ok(OverviewRequest {
query,
csv,
amount_basis,
})
}
fn timestamp(params: &mut BTreeMap<String, String>, key: &str) -> Result<u64, String> {
let value = params
.remove(key)
.ok_or_else(|| format!("{key} is required"))?;
let value = DateTime::parse_from_rfc3339(&value)
.map_err(|_| format!("{key} must be an RFC 3339 timestamp"))?
.with_timezone(&Utc);
u64::try_from(value.timestamp_millis())
.map_err(|_| format!("{key} must not precede the Unix epoch"))
}
fn number<T: std::str::FromStr>(
params: &mut BTreeMap<String, String>,
key: &str,
default: T,
) -> Result<T, String> {
params
.remove(key)
.map(|value| value.parse().map_err(|_| format!("invalid {key}")))
.unwrap_or(Ok(default))
}
fn boolean(params: &mut BTreeMap<String, String>, key: &str) -> Result<Option<bool>, String> {
match params.remove(key).as_deref() {
None => Ok(None),
Some("true") => Ok(Some(true)),
Some("false") => Ok(Some(false)),
_ => Err(format!("{key} must be true or false")),
}
}
fn text(params: &mut BTreeMap<String, String>, key: &str) -> Result<Option<String>, String> {
params
.remove(key)
.map(|value| {
let value = value.trim();
if value.is_empty() || value.len() > 512 || value.chars().any(char::is_control) {
Err(format!("invalid {key}"))
} else {
Ok(value.to_string())
}
})
.transpose()
}
#[cfg(test)]
mod tests {
use super::*;
const RANGE: &str =
"from=2026-09-01T23:45:00Z&to=2026-09-02T00:15:00Z&timezone=Asia%2FShanghai";
#[test]
fn preserves_precise_cross_midnight_bounds() {
let parsed = parse_overview_query(Some(RANGE), UsageAnalyticsView::Summary).unwrap();
assert_eq!(
parsed.query.to_unix_ms - parsed.query.from_unix_ms,
30 * 60 * 1000
);
assert_eq!(parsed.query.timezone, "Asia/Shanghai");
}
#[test]
fn rejects_unknown_duplicate_and_invalid_ranges() {
for suffix in [
"&model=x&model=y",
"&invented=1",
"&limit=101",
"&timezone=Europe/Paris",
"&is_stream=perhaps",
] {
assert!(parse_overview_query(
Some(&format!("{RANGE}{suffix}")),
UsageAnalyticsView::Summary
)
.is_err());
}
assert!(parse_overview_query(
Some("from=2026-09-01T00:00:00Z&to=2026-09-01T00:00:00Z"),
UsageAnalyticsView::Summary
)
.is_err());
}
#[test]
fn supports_iana_zone_across_dst_and_full_filter_exports() {
let parsed = parse_overview_query(Some("from=2026-03-08T05:00:00Z&to=2026-03-09T04:00:00Z&timezone=America%2FNew_York&format=csv&search=alice"), UsageAnalyticsView::Users).unwrap();
assert_eq!(
parsed.query.to_unix_ms - parsed.query.from_unix_ms,
23 * 3_600_000
);
assert_eq!(parsed.query.limit, OVERVIEW_EXPORT_LIMIT + 1);
assert_eq!(parsed.query.search.as_deref(), Some("alice"));
}
#[test]
fn user_payments_have_independent_bounded_pagination() {
let raw = format!("{RANGE}&payment_limit=10&payment_offset=20&limit=1&offset=0");
let parsed = parse_overview_query(Some(&raw), UsageAnalyticsView::Users).unwrap();
assert_eq!(parsed.query.payment_limit, Some(10));
assert_eq!(parsed.query.payment_offset, Some(20));
assert_eq!(parsed.query.limit, 1);
assert_eq!(parsed.query.offset, 0);
assert!(parse_overview_query(Some(&raw), UsageAnalyticsView::Summary).is_err());
for value in ["0", "101", "-1", "garbage"] {
assert!(parse_overview_query(
Some(&format!("{RANGE}&payment_limit={value}")),
UsageAnalyticsView::Users
)
.is_err());
}
}
}
@@ -0,0 +1,677 @@
use super::OverviewRequest;
use aether_data_contracts::repository::usage::{
StoredUsageAnalytics, UsageAnalyticsConsumption, UsageAnalyticsMetrics,
UsageAnalyticsUserFinance, UsageAnalyticsUserPayments, UsageAnalyticsView,
USAGE_ANALYTICS_VERSION,
};
use chrono::{DateTime, Datelike, TimeZone, Utc};
use serde_json::{json, Value};
pub fn amount(value: &Option<String>, basis: &str, complete: bool) -> Value {
json!({
"value": value, "currency": "USD", "basis": basis,
"status": if value.is_none() { "unknown" } else if complete { "known" } else { "known_subtotal" },
})
}
pub fn metrics_value(metrics: &UsageAnalyticsMetrics) -> Value {
let terminal = metrics
.successful_request_count
.saturating_add(metrics.failed_request_count)
.saturating_add(metrics.cancelled_request_count);
let priced = metrics.pricing_available_count == metrics.request_count;
let allocated = metrics.allocation_available_count == metrics.request_count;
let tokens_available = metrics.request_count == 0 || metrics.usage_available_count > 0;
let usage_source =
if metrics.request_count == 0 || metrics.unknown_usage_count == metrics.request_count {
"unknown"
} else if metrics.reported_usage_count == metrics.request_count {
"reported"
} else if metrics.estimated_usage_count == metrics.request_count {
"estimated"
} else {
"mixed"
};
json!({
"request_count": metrics.request_count,
"successful_request_count": metrics.successful_request_count,
"failed_request_count": metrics.failed_request_count,
"cancelled_request_count": metrics.cancelled_request_count,
"in_flight_request_count": metrics.in_flight_request_count,
"slow_request_count": metrics.slow_request_count,
"unclassified_failure_count": metrics.failed_request_count.saturating_sub(metrics.classified_failure_count),
"input_tokens": tokens_available.then_some(metrics.input_tokens),
"output_tokens": tokens_available.then_some(metrics.output_tokens),
"total_tokens": tokens_available.then_some(metrics.total_tokens),
"usage_source": usage_source,
"usage_source_counts": {
"reported": metrics.reported_usage_count,
"estimated": metrics.estimated_usage_count,
"mixed": metrics.mixed_usage_count,
"unknown": metrics.unknown_usage_count,
},
"usage_active_users": metrics.usage_active_users,
"enabled_users": metrics.enabled_users,
"success_rate": {
"value": (terminal > 0).then(|| metrics.successful_request_count as f64 / terminal as f64),
"numerator": metrics.successful_request_count, "denominator": terminal,
},
"latency_ms": {
"avg": (metrics.latency_sample_count > 0).then(|| metrics.latency_sum_ms / metrics.latency_sample_count as f64),
"p50": metrics.latency_p50_ms, "p95": metrics.latency_p95_ms, "p99": metrics.latency_p99_ms,
"sample_count": metrics.latency_sample_count,
},
"rated_amount": amount(&metrics.rated_amount, "rated", priced),
"billable_amount": amount(&metrics.billable_amount, "billable", priced),
"quota_covered_amount": amount(&metrics.quota_covered_amount, "quota_covered", allocated),
"wallet_consumed_amount": amount(&metrics.wallet_consumed_amount, "wallet_consumed", allocated),
"wallet_debit_amount": amount(&metrics.wallet_debit_amount, "wallet_debit", allocated),
"wallet_recharge_debit_amount": amount(&metrics.wallet_recharge_debit_amount, "wallet_recharge_debit", allocated),
"wallet_gift_debit_amount": amount(&metrics.wallet_gift_debit_amount, "wallet_gift_debit", allocated),
"wallet_overdraft_amount": amount(&metrics.wallet_overdraft_amount, "wallet_overdraft", allocated),
})
}
pub fn envelope(request: &OverviewRequest, snapshot: &StoredUsageAnalytics, data: Value) -> Value {
let query = &request.query;
let metrics = &snapshot.summary;
let complete = snapshot.unrecoverable_bucket_count == 0
&& metrics.usage_available_count == metrics.request_count
&& metrics.pricing_available_count == metrics.request_count
&& metrics.settled_count == metrics.request_count
&& metrics.allocation_available_count == metrics.request_count;
json!({
"meta": {
"schema_version": 1, "metric_version": USAGE_ANALYTICS_VERSION,
"scope": if let Some(id) = &query.actor_user_id {
json!({"kind": "employee", "user_id": id})
} else if let Some(id) = &query.credential_owner_id {
json!({"kind": "credential_owner", "user_id": id})
} else { json!({"kind": "installation"}) },
"range": {
"from": rfc3339(query.from_unix_ms), "to": rfc3339(query.to_unix_ms),
"timezone": query.timezone, "time_basis": "request_started_at",
},
"generated_at": snapshot.generated_at, "data_through": snapshot.data_through,
"read_revision": snapshot.read_revision,
"projection": snapshot.coverage,
"amount_basis": request.amount_basis,
"coverage": {
"status": if complete { "complete" } else { "partial" },
"request_count": metrics.request_count,
"usage_available_count": metrics.usage_available_count,
"pricing_available_count": metrics.pricing_available_count,
"settled_count": metrics.settled_count,
"allocation_available_count": metrics.allocation_available_count,
"attribution_available_count": metrics.trusted_attribution_count,
"classified_failure_count": metrics.classified_failure_count,
"unrecoverable_bucket_count": snapshot.unrecoverable_bucket_count,
},
},
"data": data,
})
}
pub fn page_value(request: &OverviewRequest, snapshot: &StoredUsageAnalytics) -> Value {
let items: Vec<Value> = match request.query.view {
UsageAnalyticsView::Users => snapshot
.users
.iter()
.map(|row| {
let mut value = metrics_value(&row.metrics);
value["user_id"] = json!(row.user_id);
value["username"] = json!(row.username);
value["email"] = json!(row.email);
value["is_active"] = json!(row.is_active);
value["last_used_at"] = json!(row.last_used_at);
value["active_days"] = json!(row.active_days);
value["finance"] = user_finance_value(row.finance.as_ref());
value
})
.collect(),
UsageAnalyticsView::Consumption => {
snapshot.consumption.iter().map(consumption_value).collect()
}
_ => snapshot
.rows
.iter()
.map(|row| {
let mut value = metrics_value(&row.metrics);
value["id"] = json!(row.id);
value["label"] = json!(row.label);
value["bucket_start"] = json!(row.bucket_start);
value
})
.collect(),
};
let mut page = json!({ "items": items, "total": snapshot.total, "limit": request.query.limit, "offset": request.query.offset });
if request.query.view == UsageAnalyticsView::Users {
page["summary"] = snapshot
.user_summary
.as_ref()
.map(|summary| {
let mut value = metrics_value(&summary.metrics);
value["user_count"] = json!(summary.user_count);
value["active_user_count"] = json!(summary.active_user_count);
value
})
.unwrap_or(Value::Null);
page["finance_summary"] = user_finance_value(snapshot.user_finance_summary.as_ref());
}
page
}
pub fn user_finance_value(finance: Option<&UsageAnalyticsUserFinance>) -> Value {
let Some(finance) = finance else {
return Value::Null;
};
json!({
"wallet_balance": amount(&finance.wallet_balance, "wallet_balance", true),
"recharge_balance": amount(&finance.recharge_balance, "recharge_balance", true),
"gift_balance": amount(&finance.gift_balance, "gift_balance", true),
"recharge_amount": amount(&finance.recharge_amount, "credited_wallet_recharge", true),
"recharge_count": finance.recharge_count,
"plan_purchase_amount": amount(&finance.plan_purchase_amount, "credited_plan_purchase", true),
"plan_purchase_count": finance.plan_purchase_count,
"gift_credit_amount": amount(&finance.gift_credit_amount, "credited_gift_order", true),
"gift_credit_count": finance.gift_credit_count,
"balance_time_basis": "current",
"payment_time_basis": "credited_at",
})
}
pub fn user_payments_value(payments: Option<&UsageAnalyticsUserPayments>) -> Value {
let Some(payments) = payments else {
return Value::Null;
};
let items: Vec<Value> = payments
.items
.iter()
.map(|payment| {
json!({
"id": payment.id, "order_no": payment.order_no, "kind": payment.kind,
"amount": amount(&Some(payment.amount.clone()), "credited_order", true),
"payment_method": payment.payment_method, "credited_at": payment.credited_at,
})
})
.collect();
json!({ "items": items, "total": payments.total, "limit": payments.limit, "offset": payments.offset })
}
pub fn consumption_value(row: &UsageAnalyticsConsumption) -> Value {
json!({
"id": row.id, "request_id": row.request_id, "started_at": row.started_at,
"user_id": row.user_id, "credential_owner_id": row.credential_owner_id,
"model": row.model, "provider": row.provider, "provider_id": row.provider_id,
"api_key_id": row.api_key_id,
"status": row.status, "settlement_status": row.settlement_status,
"attribution_kind": row.attribution_kind, "attribution_source": row.attribution_source,
"rated_amount": amount(&row.rated_amount, "rated", true),
"billable_amount": amount(&row.billable_amount, "billable", true),
"quota_covered_amount": amount(&row.quota_covered_amount, "quota_covered", true),
"wallet_consumed_amount": amount(&row.wallet_consumed_amount, "wallet_consumed", true),
"wallet_debit_amount": amount(&row.wallet_debit_amount, "wallet_debit", true),
})
}
pub fn costs_value(request: &OverviewRequest, snapshot: &StoredUsageAnalytics) -> Value {
let metrics = &snapshot.summary;
let cache_complete = metrics.cache_pricing_available_count == metrics.request_count;
let savings = metrics
.cache_estimated_full_cost_amount
.as_deref()
.and_then(decimal_units)
.zip(
metrics
.cache_read_cost_amount
.as_deref()
.and_then(decimal_units),
)
.and_then(|(full, read)| full.checked_sub(read))
.map(format_units);
json!({
"summary": metrics_value(metrics),
"timeseries": snapshot.rows.iter().map(|row| {
let mut value = metrics_value(&row.metrics);
value["bucket_start"] = json!(row.bucket_start);
value
}).collect::<Vec<_>>(),
"supplier_estimated_cost": amount(&None, "supplier_estimated", false),
"supplier_verified_cost": amount(&None, "supplier_verified", false),
"cache": {
"read_tokens": (metrics.usage_available_count > 0 || metrics.request_count == 0).then_some(metrics.cache_read_input_tokens),
"creation_tokens": (metrics.usage_available_count > 0 || metrics.request_count == 0).then_some(metrics.cache_creation_input_tokens),
"read_cost": amount(&metrics.cache_read_cost_amount, "cache_read", cache_complete),
"creation_cost": amount(&metrics.cache_creation_cost_amount, "cache_creation", cache_complete),
"estimated_full_cost": estimated_amount(&metrics.cache_estimated_full_cost_amount, "cache_full_price_estimate", cache_complete),
"estimated_savings": estimated_amount(&savings, "cache_read_savings_estimate", cache_complete),
"pricing_available_count": metrics.cache_pricing_available_count,
"request_count": metrics.request_count,
},
"forecast": forecast(request, snapshot),
})
}
fn estimated_amount(value: &Option<String>, basis: &str, complete: bool) -> Value {
let mut result = amount(value, basis, complete);
if value.is_some() {
result["status"] = json!(if complete {
"estimated"
} else {
"estimated_subtotal"
});
}
result
}
fn performance_metrics(metrics: &UsageAnalyticsMetrics) -> Value {
let mut value = json!({
"request_count": metrics.request_count,
"success_count": metrics.successful_request_count,
"error_count": metrics.failed_request_count,
"success_rate": metrics_value(metrics)["success_rate"]["value"],
"output_tokens": metrics.output_tokens,
"avg_output_tps": (metrics.output_tps_sample_count > 0).then(|| metrics.output_tps_sum / metrics.output_tps_sample_count as f64),
"avg_first_byte_time_ms": (metrics.first_byte_sample_count > 0).then(|| metrics.first_byte_sum_ms / metrics.first_byte_sample_count as f64),
"avg_response_time_ms": (metrics.latency_sample_count > 0).then(|| metrics.latency_sum_ms / metrics.latency_sample_count as f64),
"p90_response_time_ms": metrics.latency_p90_ms,
"p99_response_time_ms": metrics.latency_p99_ms,
"p90_first_byte_time_ms": metrics.first_byte_p90_ms,
"p99_first_byte_time_ms": metrics.first_byte_p99_ms,
"tps_sample_count": metrics.output_tps_sample_count,
"response_time_sample_count": metrics.latency_sample_count,
"first_byte_sample_count": metrics.first_byte_sample_count,
"slow_request_count": metrics.slow_request_count,
});
// The legacy provider chart contract expresses rates as percentages.
value["success_rate"] = value["success_rate"]
.as_f64()
.map(|rate| json!(rate * 100.0))
.unwrap_or(Value::Null);
value
}
pub fn performance_value(request: &OverviewRequest, snapshot: &StoredUsageAnalytics) -> Value {
let models = snapshot
.model_rows
.iter()
.map(|row| {
let mut value = performance_metrics(&row.metrics);
value["model"] = json!(row.id);
value
})
.collect::<Vec<_>>();
let providers = snapshot
.provider_rows
.iter()
.map(|row| {
let mut value = performance_metrics(&row.metrics);
value["provider_id"] = json!(row.id);
value["provider"] = json!(row.label);
value
})
.collect::<Vec<_>>();
let timeline = snapshot
.provider_timeline_rows
.iter()
.map(|row| {
let mut value = performance_metrics(&row.metrics);
value["provider_id"] = json!(row.id);
value["provider"] = json!(row.label);
value["date"] = json!(row.bucket_start);
value
})
.collect::<Vec<_>>();
json!({
"summary": metrics_value(&snapshot.summary),
"timeseries": page_value(request, snapshot)["items"],
"providers": { "summary": performance_metrics(&snapshot.summary), "providers": providers, "timeline": timeline },
"models": models,
"errors": snapshot.errors,
})
}
fn forecast(request: &OverviewRequest, snapshot: &StoredUsageAnalytics) -> Value {
let unavailable = |days| {
json!({
"amount": amount(&None, "billable_forecast", false), "method": "calendar_month_daily_average",
"status": "insufficient_data", "sample_days": days, "period_end": null,
})
};
if snapshot.unrecoverable_bucket_count > 0 {
return unavailable(0);
}
let Ok(zone) = request.query.timezone.parse::<chrono_tz::Tz>() else {
return unavailable(0);
};
let Some(end) = DateTime::<Utc>::from_timestamp_millis(request.query.to_unix_ms as i64) else {
return unavailable(0);
};
// Select the forecast month from the last included instant in this half-open range.
let local_end = (end - chrono::Duration::milliseconds(1)).with_timezone(&zone);
let month = local_end
.date_naive()
.with_day(1)
.expect("first day exists");
let Some(next_month) = month.checked_add_months(chrono::Months::new(1)) else {
return unavailable(0);
};
let mut sum = 0_i128;
let mut days = std::collections::BTreeSet::new();
for row in &snapshot.rows {
let Some(start) = row
.bucket_start
.as_deref()
.and_then(|value| DateTime::parse_from_rfc3339(value).ok())
else {
continue;
};
let date = start.with_timezone(&zone).date_naive();
let Some(next) = date.succ_opt().and_then(|day| {
zone.from_local_datetime(&day.and_hms_opt(0, 0, 0)?)
.earliest()
}) else {
continue;
};
if date < month
|| date >= next_month
|| start.timestamp_millis() < request.query.from_unix_ms as i64
|| next.with_timezone(&Utc) > end
|| next.with_timezone(&Utc) > Utc::now()
{
continue;
}
if row.metrics.pricing_available_count != row.metrics.request_count
|| row.metrics.settled_count != row.metrics.request_count
{
return unavailable(days.len());
}
let Some(value) = row
.metrics
.billable_amount
.as_deref()
.and_then(decimal_units)
else {
return unavailable(days.len());
};
let Some(total) = sum.checked_add(value) else {
return unavailable(days.len());
};
sum = total;
days.insert(date);
}
// Missing calendar days are not assumed to be zero-use days.
if days.len() < 7
|| days
.last()
.zip(days.first())
.is_none_or(|(last, first)| (*last - *first).num_days() + 1 != days.len() as i64)
{
return unavailable(days.len());
}
let Some(projected) = sum
.checked_mul((next_month - month).num_days() as i128)
.map(|value| value / days.len() as i128)
else {
return unavailable(days.len());
};
json!({
"amount": {"value": format_units(projected), "currency": "USD", "basis": "billable_forecast", "status": "estimated"},
"method": "calendar_month_daily_average", "status": "estimated", "sample_days": days.len(),
"period_end": zone.from_local_datetime(&next_month.and_hms_opt(0, 0, 0).expect("midnight exists")).earliest().map(|value| value.to_rfc3339()),
})
}
fn decimal_units(value: &str) -> Option<i128> {
let (whole, fraction) = value.split_once('.').unwrap_or((value, ""));
if fraction.len() > 8 || !fraction.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
let negative = whole.starts_with('-');
let whole = whole.parse::<i128>().ok()?;
let fractional = if fraction.is_empty() {
0
} else {
fraction.parse::<i128>().ok()? * 10_i128.pow(8 - fraction.len() as u32)
};
whole
.checked_mul(100_000_000)?
.checked_add(if negative { -fractional } else { fractional })
}
fn format_units(value: i128) -> String {
format!(
"{}{}.{:08}",
if value < 0 { "-" } else { "" },
value.unsigned_abs() / 100_000_000,
value.unsigned_abs() % 100_000_000
)
}
fn rfc3339(millis: u64) -> Option<String> {
DateTime::<Utc>::from_timestamp_millis(millis as i64).map(|value| value.to_rfc3339())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn user_finance_preserves_unknown_sources_and_distinct_payment_bases() {
assert!(user_finance_value(None).is_null());
assert!(user_payments_value(None).is_null());
let finance = UsageAnalyticsUserFinance {
wallet_balance: Some("15.00000000".into()),
recharge_amount: Some("100.00000000".into()),
recharge_count: 2,
plan_purchase_amount: Some("25.00000000".into()),
plan_purchase_count: 1,
gift_credit_amount: Some("7.00000000".into()),
gift_credit_count: 1,
..Default::default()
};
let value = user_finance_value(Some(&finance));
assert_eq!(value["wallet_balance"]["value"], "15.00000000");
assert_eq!(value["recharge_amount"]["value"], "100.00000000");
assert_eq!(
value["plan_purchase_amount"]["basis"],
"credited_plan_purchase"
);
assert_eq!(value["gift_credit_amount"]["basis"], "credited_gift_order");
assert_eq!(value["gift_balance"]["status"], "unknown");
assert_eq!(value["balance_time_basis"], "current");
assert_eq!(value["payment_time_basis"], "credited_at");
}
#[test]
fn unknown_money_and_zero_denominators_stay_unknown() {
let metrics = UsageAnalyticsMetrics {
request_count: 3,
in_flight_request_count: 3,
..Default::default()
};
let value = metrics_value(&metrics);
assert!(value["success_rate"]["value"].is_null());
assert!(value["latency_ms"]["avg"].is_null());
assert!(value["total_tokens"].is_null());
assert!(value["billable_amount"]["value"].is_null());
assert_eq!(value["billable_amount"]["status"], "unknown");
}
#[test]
fn known_subtotal_and_cancelled_consumption_remain_visible() {
let metrics = UsageAnalyticsMetrics {
request_count: 4,
successful_request_count: 2,
cancelled_request_count: 1,
in_flight_request_count: 1,
pricing_available_count: 2,
billable_amount: Some("1.25000000".into()),
..Default::default()
};
let value = metrics_value(&metrics);
assert_eq!(value["success_rate"]["denominator"], 3);
assert_eq!(value["billable_amount"]["status"], "known_subtotal");
assert_eq!(value["billable_amount"]["value"], "1.25000000");
}
#[test]
fn overview_model_performance_preserves_unknowns_and_uses_sample_weighted_averages() {
use aether_data_contracts::repository::usage::UsageAnalyticsRow;
let request = super::super::parse_overview_query(
Some("from=2026-09-01T00:00:00Z&to=2026-09-02T00:00:00Z"),
UsageAnalyticsView::Performance,
)
.unwrap();
let snapshot = StoredUsageAnalytics {
model_rows: vec![
UsageAnalyticsRow {
id: Some("requested-model".into()),
label: Some("requested-model".into()),
bucket_start: None,
metrics: UsageAnalyticsMetrics {
request_count: 6,
successful_request_count: 3,
failed_request_count: 1,
cancelled_request_count: 1,
in_flight_request_count: 1,
first_byte_sample_count: 3,
first_byte_sum_ms: 900.0,
output_tps_sample_count: 3,
output_tps_sum: 500.0,
latency_sample_count: 3,
latency_sum_ms: 4900.0,
..Default::default()
},
},
UsageAnalyticsRow {
id: None,
label: None,
bucket_start: None,
metrics: UsageAnalyticsMetrics {
request_count: 1,
in_flight_request_count: 1,
..Default::default()
},
},
],
..Default::default()
};
let value = performance_value(&request, &snapshot);
let rows = value["models"].as_array().unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0]["model"], "requested-model");
assert_eq!(rows[0]["success_rate"], 60.0);
assert_eq!(rows[0]["avg_first_byte_time_ms"], 300.0);
assert_eq!(rows[0]["avg_output_tps"], 500.0 / 3.0);
assert_eq!(rows[0]["avg_response_time_ms"], 4900.0 / 3.0);
for field in [
"model",
"success_rate",
"avg_first_byte_time_ms",
"avg_output_tps",
"avg_response_time_ms",
] {
assert!(rows[1][field].is_null(), "{field}");
}
assert!(value["providers"]["providers"].is_array());
assert!(value["timeseries"].is_array());
}
#[test]
fn forecast_arithmetic_keeps_eight_decimal_places_without_floats() {
for value in [
"12.34567890",
"0.00000001",
"-0.10000000",
"999999999999.99999999",
] {
assert_eq!(format_units(decimal_units(value).unwrap()), value);
}
}
#[test]
fn forecast_includes_complete_calendar_month_at_exclusive_local_boundary() {
use aether_data_contracts::repository::usage::UsageAnalyticsRow;
for timezone in ["UTC", "Asia/Shanghai", "America/New_York"] {
let zone = timezone.parse::<chrono_tz::Tz>().unwrap();
let from = zone.with_ymd_and_hms(2020, 3, 1, 0, 0, 0).unwrap();
let to = zone.with_ymd_and_hms(2020, 4, 1, 0, 0, 0).unwrap();
let request = super::super::parse_overview_query(
Some(&format!(
"from={}&to={}&timezone={timezone}",
from.with_timezone(&Utc)
.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
to.with_timezone(&Utc)
.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
)),
UsageAnalyticsView::Timeseries,
)
.unwrap();
let snapshot = StoredUsageAnalytics {
rows: (1..=31)
.map(|day| UsageAnalyticsRow {
id: None,
label: None,
bucket_start: Some(
zone.with_ymd_and_hms(2020, 3, day, 0, 0, 0)
.unwrap()
.to_rfc3339(),
),
metrics: UsageAnalyticsMetrics {
request_count: 1,
pricing_available_count: 1,
settled_count: 1,
billable_amount: Some("1.25000000".into()),
..Default::default()
},
})
.collect(),
..Default::default()
};
let value = costs_value(&request, &snapshot);
assert_eq!(value["forecast"]["sample_days"], 31, "{timezone}");
assert_eq!(value["forecast"]["status"], "estimated", "{timezone}");
assert_eq!(
value["forecast"]["amount"]["value"], "38.75000000",
"{timezone}"
);
assert_eq!(
value["forecast"]["period_end"],
to.to_rfc3339(),
"{timezone}"
);
}
}
#[test]
fn cache_savings_use_known_prices_and_preserve_estimate_coverage() {
let request = super::super::parse_overview_query(
Some("from=2026-09-01T00:00:00Z&to=2026-09-02T00:00:00Z"),
UsageAnalyticsView::Timeseries,
)
.unwrap();
let mut snapshot = StoredUsageAnalytics::default();
snapshot.summary.request_count = 2;
snapshot.summary.cache_pricing_available_count = 1;
snapshot.summary.cache_estimated_full_cost_amount = Some("0.10000001".into());
snapshot.summary.cache_read_cost_amount = Some("0.02000000".into());
let value = costs_value(&request, &snapshot);
assert_eq!(value["cache"]["estimated_savings"]["value"], "0.08000001");
assert_eq!(
value["cache"]["estimated_savings"]["status"],
"estimated_subtotal"
);
snapshot.summary.cache_estimated_full_cost_amount = None;
let value = costs_value(&request, &snapshot);
assert!(value["cache"]["estimated_savings"]["value"].is_null());
snapshot.unrecoverable_bucket_count = 1;
assert_eq!(
envelope(&request, &snapshot, json!({}))["meta"]["coverage"]["status"],
"partial"
);
assert_eq!(value["supplier_verified_cost"]["status"], "unknown");
}
}
@@ -1,3 +1,4 @@
pub mod analytics;
pub mod monitoring;
pub mod stats;
pub mod usage;