feat(billing): 引入 billing v3 settlement snapshot 及 usage_billing_facts 视图

- 新增迁移 20260424000000:为 usage_settlement_snapshots 添加 settlement_snapshot、
  billing_dimensions、billing_input/output/cache tokens、billing_total_cost_usd 等列,
  并创建 usage_billing_facts 视图(从 settlement snapshot 覆盖原始 usage token/cost 字段)
- event_enrichment:构建结构化 settlement_snapshot(含 pricing_snapshot、billing_plan_snapshot、
  resolved_dimensions、cost_breakdown 等),写入 request_metadata
- pricing:新增 pricing_source() 方法区分 provider_override / global_default / unpriced
- sql.rs:settlement snapshot 写入新列;用量汇总查询改用 usage_billing_facts 视图
- maintenance/runtime:所有统计查询的 FROM usage 替换为 FROM usage_billing_facts
- admin observability:在 settlement 响应中暴露 settlement_snapshot / billing_dimensions,
  并从 metadata 中剥离对应字段
- request_metadata:允许 settlement_snapshot / billing_dimensions 字段传播
- stream execution:在首个数据帧到达时记录 TTFB,补全流式请求的 streaming 事件
This commit is contained in:
fawney19
2026-04-24 17:52:33 +08:00
parent fb46fcd80f
commit f695238e8a
11 changed files with 1970 additions and 197 deletions

View File

@@ -1,5 +1,6 @@
use std::collections::{BTreeMap, VecDeque};
use std::io::Error as IoError;
use std::time::Instant;
use aether_contracts::{
ExecutionPlan, ExecutionStreamTerminalSummary, ExecutionTelemetry, StreamFrame,
@@ -304,6 +305,7 @@ pub(crate) async fn execute_execution_runtime_stream(
report_kind: Option<String>,
mut report_context: Option<serde_json::Value>,
) -> Result<Option<Response<Body>>, GatewayError> {
let stream_started_at = Instant::now();
ensure_execution_request_candidate_slot(state, &mut plan, &mut report_context).await;
let lifecycle_seed = build_lifecycle_usage_seed(&plan, report_context.as_ref());
let request_candidate_status_snapshot =
@@ -391,6 +393,7 @@ pub(crate) async fn execute_execution_runtime_stream(
report_kind,
report_context,
candidate_started_unix_secs,
stream_started_at,
frame_stream,
)
.await;
@@ -450,6 +453,7 @@ pub(crate) async fn execute_execution_runtime_stream(
report_kind,
report_context,
candidate_started_unix_secs,
stream_started_at,
frame_stream,
)
.await;
@@ -534,6 +538,7 @@ pub(crate) async fn execute_execution_runtime_stream(
report_kind,
report_context,
candidate_started_unix_secs,
stream_started_at,
frame_stream,
)
.await;
@@ -675,6 +680,7 @@ async fn execute_stream_from_frame_stream(
report_kind: Option<String>,
report_context: Option<serde_json::Value>,
candidate_started_unix_secs: u64,
stream_started_at: Instant,
frame_stream: BoxStream<'static, Result<Bytes, IoError>>,
) -> Result<Option<Response<Body>>, GatewayError> {
let request_id = plan.request_id.as_str();
@@ -1639,6 +1645,32 @@ async fn execute_stream_from_frame_stream(
continue;
}
if usage_stream_telemetry
.as_ref()
.and_then(|telemetry| telemetry.ttfb_ms)
.is_none()
{
let first_data_elapsed_ms = stream_started_at
.elapsed()
.as_millis()
.min(u128::from(u64::MAX))
as u64;
let first_data_telemetry = ExecutionTelemetry {
ttfb_ms: Some(first_data_elapsed_ms),
elapsed_ms: Some(first_data_elapsed_ms),
upstream_bytes: telemetry
.as_ref()
.and_then(|telemetry| telemetry.upstream_bytes),
};
state_for_report.usage_runtime.record_stream_started(
state_for_report.data.as_ref(),
&lifecycle_seed_for_report,
status_code,
Some(&first_data_telemetry),
);
usage_stream_telemetry = Some(first_data_telemetry);
}
append_stream_capture_bytes(
&mut buffered_body,
&rewritten_chunk,
@@ -2082,16 +2114,22 @@ fn apply_stream_summary_report_context(
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use std::convert::Infallible;
use std::sync::Arc;
use std::time::Duration;
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, RequestBody};
use axum::body::{to_bytes, Body};
use aether_data::repository::usage::InMemoryUsageReadRepository;
use aether_data_contracts::repository::usage::UsageReadRepository;
use aether_usage_runtime::UsageRuntimeConfig;
use async_stream::stream;
use axum::body::{to_bytes, Body, Bytes};
use axum::extract::ws::Message;
use axum::extract::Request;
use axum::routing::any;
use axum::{http::header, http::HeaderValue, Router};
use serde_json::{json, Value};
use tokio::sync::watch;
use tokio::sync::{watch, Notify};
use super::{execute_execution_runtime_stream, should_skip_direct_finalize_prefetch};
use crate::control::GatewayControlDecision;
@@ -2168,6 +2206,153 @@ mod tests {
));
}
#[tokio::test]
async fn execute_execution_runtime_stream_records_first_data_as_streaming_before_terminal_telemetry(
) {
let listener = crate::test_support::bind_loopback_listener()
.await
.expect("listener should bind");
let addr = listener.local_addr().expect("local addr should resolve");
let first_data_seen = Arc::new(Notify::new());
let release_terminal = Arc::new(Notify::new());
let first_data_seen_for_route = Arc::clone(&first_data_seen);
let release_terminal_for_route = Arc::clone(&release_terminal);
let server = tokio::spawn(async move {
let app = Router::new().route(
"/v1/execute/stream",
any(move |_request: Request| {
let first_data_seen = Arc::clone(&first_data_seen_for_route);
let release_terminal = Arc::clone(&release_terminal_for_route);
async move {
let frames = stream! {
yield Ok::<Bytes, Infallible>(Bytes::from_static(
b"{\"type\":\"headers\",\"payload\":{\"kind\":\"headers\",\"status_code\":200,\"headers\":{\"content-type\":\"text/event-stream\"}}}\n",
));
tokio::time::sleep(Duration::from_millis(10)).await;
yield Ok::<Bytes, Infallible>(Bytes::from_static(
b"{\"type\":\"data\",\"payload\":{\"kind\":\"data\",\"text\":\"event: response.output_text.delta\\ndata: {\\\"type\\\":\\\"response.output_text.delta\\\",\\\"delta\\\":\\\"hi\\\"}\\n\\n\"}}\n",
));
first_data_seen.notify_one();
release_terminal.notified().await;
yield Ok::<Bytes, Infallible>(Bytes::from_static(
b"{\"type\":\"telemetry\",\"payload\":{\"kind\":\"telemetry\",\"telemetry\":{\"ttfb_ms\":123,\"elapsed_ms\":456}}}\n",
));
yield Ok::<Bytes, Infallible>(Bytes::from_static(
b"{\"type\":\"eof\",\"payload\":{\"kind\":\"eof\"}}\n",
));
};
let mut response = axum::http::Response::new(Body::from_stream(frames));
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/x-ndjson"),
);
response
}
}),
);
axum::serve(listener, app)
.await
.expect("server should start");
});
let usage_repository = Arc::new(InMemoryUsageReadRepository::default());
let state = AppState::new()
.expect("app state should build")
.with_usage_data_repository_for_tests(Arc::clone(&usage_repository))
.with_usage_runtime_for_tests(UsageRuntimeConfig {
enabled: true,
..UsageRuntimeConfig::default()
})
.with_execution_runtime_override_base_url(format!("http://{addr}"));
let plan = ExecutionPlan {
request_id: "req-live-stream-first-data".into(),
candidate_id: Some("cand-live-stream-first-data".into()),
provider_name: Some("openai".into()),
provider_id: "prov-1".into(),
endpoint_id: "ep-1".into(),
key_id: "key-1".into(),
method: "POST".into(),
url: "https://chatgpt.com/backend-api/codex/responses".into(),
headers: BTreeMap::from([
("content-type".into(), "application/json".into()),
("accept".into(), "text/event-stream".into()),
]),
content_type: Some("application/json".into()),
content_encoding: None,
body: RequestBody::from_json(json!({
"model": "gpt-5.4",
"input": "hello",
"stream": true
})),
stream: true,
client_api_format: "openai:cli".into(),
provider_api_format: "openai:cli".into(),
model_name: Some("gpt-5.4".into()),
proxy: None,
tls_profile: None,
timeouts: Some(ExecutionTimeouts {
connect_ms: Some(5_000),
total_ms: Some(5_000),
..ExecutionTimeouts::default()
}),
};
let decision = GatewayControlDecision::synthetic(
"/v1/responses",
Some("ai_public".to_string()),
Some("openai".to_string()),
Some("cli".to_string()),
Some("openai:cli".to_string()),
)
.with_execution_runtime_candidate(true);
let response = execute_execution_runtime_stream(
&state,
plan,
"trace-live-stream-first-data",
&decision,
"openai_cli_stream",
None,
Some(json!({
"provider_api_format": "openai:cli",
"client_api_format": "openai:cli",
})),
)
.await
.expect("execution should succeed")
.expect("execution should return a client response");
first_data_seen.notified().await;
let deadline = tokio::time::Instant::now() + Duration::from_secs(2);
let live_usage = loop {
let usage = usage_repository
.find_by_request_id("req-live-stream-first-data")
.await
.expect("usage should read");
if usage.as_ref().is_some_and(|usage| {
usage.status == "streaming" && usage.first_byte_time_ms.is_some()
}) {
break usage.expect("live usage should exist");
}
assert!(
tokio::time::Instant::now() < deadline,
"usage should record streaming status with first byte before terminal telemetry"
);
tokio::time::sleep(Duration::from_millis(10)).await;
};
assert_eq!(live_usage.status, "streaming");
assert!(live_usage.first_byte_time_ms.is_some());
release_terminal.notify_one();
let body = to_bytes(response.into_body(), usize::MAX)
.await
.expect("response body should read");
let text = String::from_utf8(body.to_vec()).expect("response body should be utf8");
assert!(text.contains("response.output_text.delta"));
server.abort();
}
#[tokio::test]
async fn execute_execution_runtime_stream_bridges_sync_json_body_from_remote_runtime_to_sse() {
let listener = crate::test_support::bind_loopback_listener()

View File

@@ -121,7 +121,7 @@ WITH aggregated AS (
COALESCE(SUM(usage.cache_read_input_tokens), 0) AS cache_read_tokens,
MIN(COALESCE(usage_settlement_snapshots.finalized_at, usage.finalized_at)) AS first_finalized_at,
MAX(COALESCE(usage_settlement_snapshots.finalized_at, usage.finalized_at)) AS last_finalized_at
FROM usage
FROM usage_billing_facts AS usage
JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage_settlement_snapshots.wallet_id IS NOT NULL
@@ -184,7 +184,7 @@ WHERE ledgers.billing_date = $1
AND ledgers.billing_timezone = $2
AND NOT EXISTS (
SELECT 1
FROM usage
FROM usage_billing_facts AS usage
JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage_settlement_snapshots.wallet_id = ledgers.wallet_id
@@ -600,7 +600,7 @@ const SELECT_STATS_DAILY_AGGREGATE_SQL: &str = r#"
SELECT
(
SELECT CAST(COUNT(cache_hit_usage.id) AS BIGINT)
FROM usage AS cache_hit_usage
FROM usage_billing_facts AS cache_hit_usage
WHERE cache_hit_usage.created_at >= $1
AND cache_hit_usage.created_at < $2
) AS cache_hit_total_requests,
@@ -610,13 +610,13 @@ SELECT
WHERE GREATEST(COALESCE(cache_hit_usage.cache_read_input_tokens, 0), 0) > 0
) AS BIGINT
)
FROM usage AS cache_hit_usage
FROM usage_billing_facts AS cache_hit_usage
WHERE cache_hit_usage.created_at >= $1
AND cache_hit_usage.created_at < $2
) AS cache_hit_requests,
(
SELECT CAST(COUNT(completed_usage.id) AS BIGINT)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -627,7 +627,7 @@ SELECT
WHERE GREATEST(COALESCE(completed_usage.cache_read_input_tokens, 0), 0) > 0
) AS BIGINT
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -636,7 +636,7 @@ SELECT
SELECT CAST(
COALESCE(SUM(GREATEST(COALESCE(completed_usage.input_tokens, 0), 0)), 0) AS BIGINT
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -659,7 +659,7 @@ SELECT
0
) AS BIGINT
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -671,7 +671,7 @@ SELECT
0
) AS BIGINT
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -809,7 +809,7 @@ SELECT
0
) AS BIGINT
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -826,7 +826,7 @@ SELECT
0
) AS DOUBLE PRECISION
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -840,7 +840,7 @@ SELECT
0
) AS DOUBLE PRECISION
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -850,7 +850,7 @@ SELECT
COALESCE(SUM(COALESCE(CAST(settled_usage.total_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS DOUBLE PRECISION
)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -858,7 +858,7 @@ SELECT
) AS settled_total_cost,
(
SELECT CAST(COUNT(settled_usage.id) AS BIGINT)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -868,7 +868,7 @@ SELECT
SELECT CAST(
COALESCE(SUM(GREATEST(COALESCE(settled_usage.input_tokens, 0), 0)), 0) AS BIGINT
)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -878,7 +878,7 @@ SELECT
SELECT CAST(
COALESCE(SUM(GREATEST(COALESCE(settled_usage.output_tokens, 0), 0)), 0) AS BIGINT
)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -891,7 +891,7 @@ SELECT
0
) AS BIGINT
)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -904,7 +904,7 @@ SELECT
0
) AS BIGINT
)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -912,7 +912,7 @@ SELECT
) AS settled_cache_read_tokens,
(
SELECT MIN(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -920,7 +920,7 @@ SELECT
) AS settled_first_finalized_at_unix_secs,
(
SELECT MAX(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -1081,7 +1081,7 @@ SELECT
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms,
CAST(COUNT(DISTINCT model) AS BIGINT) AS unique_models,
CAST(COUNT(DISTINCT provider_name) AS BIGINT) AS unique_providers
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND status NOT IN ('pending', 'streaming')
@@ -1105,7 +1105,7 @@ SELECT
CAST(percentile_cont(0.5) WITHIN GROUP (ORDER BY response_time_ms) AS DOUBLE PRECISION) AS p50,
CAST(percentile_cont(0.9) WITHIN GROUP (ORDER BY response_time_ms) AS DOUBLE PRECISION) AS p90,
CAST(percentile_cont(0.99) WITHIN GROUP (ORDER BY response_time_ms) AS DOUBLE PRECISION) AS p99
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND status = 'completed'
@@ -1118,7 +1118,7 @@ SELECT
CAST(percentile_cont(0.5) WITHIN GROUP (ORDER BY first_byte_time_ms) AS DOUBLE PRECISION) AS p50,
CAST(percentile_cont(0.9) WITHIN GROUP (ORDER BY first_byte_time_ms) AS DOUBLE PRECISION) AS p90,
CAST(percentile_cont(0.99) WITHIN GROUP (ORDER BY first_byte_time_ms) AS DOUBLE PRECISION) AS p99
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND status = 'completed'
@@ -1295,7 +1295,7 @@ WITH aggregated AS (
) AS BIGINT
) AS response_time_samples,
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND model IS NOT NULL
@@ -1365,7 +1365,7 @@ WITH aggregated AS (
CAST(COALESCE(SUM(cache_creation_input_tokens), 0) AS BIGINT) AS cache_creation_tokens,
CAST(COALESCE(SUM(cache_read_input_tokens), 0) AS BIGINT) AS cache_read_tokens,
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND status NOT IN ('pending', 'streaming')
@@ -1437,7 +1437,7 @@ WITH aggregated AS (
0
) AS BIGINT
) AS response_time_samples
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND model IS NOT NULL
@@ -1523,7 +1523,7 @@ WITH aggregated AS (
0
) AS DOUBLE PRECISION
) AS estimated_full_cost
FROM usage
FROM usage_billing_facts AS usage
LEFT JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage.created_at >= $1
@@ -1589,7 +1589,7 @@ WITH aggregated AS (
0
) AS DOUBLE PRECISION
) AS estimated_full_cost
FROM usage
FROM usage_billing_facts AS usage
LEFT JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage.created_at >= $1
@@ -1665,7 +1665,7 @@ WITH aggregated AS (
0
) AS DOUBLE PRECISION
) AS estimated_full_cost
FROM usage
FROM usage_billing_facts AS usage
LEFT JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage.created_at >= $1
@@ -1742,7 +1742,7 @@ WITH aggregated AS (
0
) AS DOUBLE PRECISION
) AS estimated_full_cost
FROM usage
FROM usage_billing_facts AS usage
LEFT JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage.created_at >= $1
@@ -1832,7 +1832,7 @@ WITH aggregated AS (
CAST(COALESCE(SUM(cache_creation_input_tokens), 0) AS BIGINT) AS cache_creation_tokens,
CAST(COALESCE(SUM(cache_read_input_tokens), 0) AS BIGINT) AS cache_read_tokens,
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND api_key_id IS NOT NULL
@@ -1894,7 +1894,7 @@ WITH aggregated AS (
provider_name,
model,
CAST(COUNT(id) AS BIGINT) AS total_count
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND error_category IS NOT NULL
@@ -2183,7 +2183,7 @@ WITH aggregated AS (
0
) AS BIGINT
) AS response_time_samples
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND user_id IS NOT NULL
@@ -2472,7 +2472,7 @@ WITH aggregated AS (
0
) AS BIGINT
) AS successful_response_time_samples
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND user_id IS NOT NULL
@@ -2740,7 +2740,7 @@ WITH aggregated AS (
0
) AS BIGINT
) AS successful_response_time_samples
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND user_id IS NOT NULL
@@ -3008,7 +3008,7 @@ WITH aggregated AS (
0
) AS BIGINT
) AS successful_response_time_samples
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND user_id IS NOT NULL
@@ -3122,7 +3122,7 @@ WITH aggregated AS (
0
) AS BIGINT
) AS response_time_samples
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND user_id IS NOT NULL
@@ -3218,7 +3218,7 @@ WITH aggregated AS (
0
) AS DOUBLE PRECISION
) AS estimated_full_cost
FROM usage
FROM usage_billing_facts AS usage
LEFT JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage.created_at >= $1
@@ -3300,7 +3300,7 @@ WITH aggregated AS (
0
) AS DOUBLE PRECISION
) AS estimated_full_cost
FROM usage
FROM usage_billing_facts AS usage
LEFT JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage.created_at >= $1
@@ -3386,7 +3386,7 @@ WITH aggregated AS (
0
) AS DOUBLE PRECISION
) AS estimated_full_cost
FROM usage
FROM usage_billing_facts AS usage
LEFT JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage.created_at >= $1
@@ -3473,7 +3473,7 @@ WITH aggregated AS (
0
) AS DOUBLE PRECISION
) AS estimated_full_cost
FROM usage
FROM usage_billing_facts AS usage
LEFT JOIN usage_settlement_snapshots
ON usage_settlement_snapshots.request_id = usage.request_id
WHERE usage.created_at >= $1
@@ -3700,7 +3700,7 @@ WHERE is_complete IS TRUE
"#;
const SELECT_NEXT_STATS_DAILY_BUCKET_SQL: &str = r#"
SELECT date_trunc('day', MIN(created_at)) AS next_bucket
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND status NOT IN ('pending', 'streaming')
@@ -3713,7 +3713,7 @@ WHERE is_complete IS TRUE
"#;
const SELECT_NEXT_STATS_HOURLY_BUCKET_SQL: &str = r#"
SELECT date_trunc('hour', MIN(created_at)) AS next_bucket
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND status NOT IN ('pending', 'streaming')
@@ -3723,7 +3723,7 @@ const SELECT_STATS_HOURLY_AGGREGATE_SQL: &str = r#"
SELECT
(
SELECT CAST(COUNT(cache_hit_usage.id) AS BIGINT)
FROM usage AS cache_hit_usage
FROM usage_billing_facts AS cache_hit_usage
WHERE cache_hit_usage.created_at >= $1
AND cache_hit_usage.created_at < $2
) AS cache_hit_total_requests,
@@ -3733,13 +3733,13 @@ SELECT
WHERE GREATEST(COALESCE(cache_hit_usage.cache_read_input_tokens, 0), 0) > 0
) AS BIGINT
)
FROM usage AS cache_hit_usage
FROM usage_billing_facts AS cache_hit_usage
WHERE cache_hit_usage.created_at >= $1
AND cache_hit_usage.created_at < $2
) AS cache_hit_requests,
(
SELECT CAST(COUNT(completed_usage.id) AS BIGINT)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -3750,7 +3750,7 @@ SELECT
WHERE GREATEST(COALESCE(completed_usage.cache_read_input_tokens, 0), 0) > 0
) AS BIGINT
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -3759,7 +3759,7 @@ SELECT
SELECT CAST(
COALESCE(SUM(GREATEST(COALESCE(completed_usage.input_tokens, 0), 0)), 0) AS BIGINT
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -3782,7 +3782,7 @@ SELECT
0
) AS BIGINT
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -3794,7 +3794,7 @@ SELECT
0
) AS BIGINT
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -3932,7 +3932,7 @@ SELECT
0
) AS BIGINT
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -3949,7 +3949,7 @@ SELECT
0
) AS DOUBLE PRECISION
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -3963,7 +3963,7 @@ SELECT
0
) AS DOUBLE PRECISION
)
FROM usage AS completed_usage
FROM usage_billing_facts AS completed_usage
WHERE completed_usage.created_at >= $1
AND completed_usage.created_at < $2
AND completed_usage.status = 'completed'
@@ -3973,7 +3973,7 @@ SELECT
COALESCE(SUM(COALESCE(CAST(settled_usage.total_cost_usd AS DOUBLE PRECISION), 0)), 0)
AS DOUBLE PRECISION
)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -3981,7 +3981,7 @@ SELECT
) AS settled_total_cost,
(
SELECT CAST(COUNT(settled_usage.id) AS BIGINT)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -3991,7 +3991,7 @@ SELECT
SELECT CAST(
COALESCE(SUM(GREATEST(COALESCE(settled_usage.input_tokens, 0), 0)), 0) AS BIGINT
)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -4001,7 +4001,7 @@ SELECT
SELECT CAST(
COALESCE(SUM(GREATEST(COALESCE(settled_usage.output_tokens, 0), 0)), 0) AS BIGINT
)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -4014,7 +4014,7 @@ SELECT
0
) AS BIGINT
)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -4027,7 +4027,7 @@ SELECT
0
) AS BIGINT
)
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -4035,7 +4035,7 @@ SELECT
) AS settled_cache_read_tokens,
(
SELECT MIN(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -4043,7 +4043,7 @@ SELECT
) AS settled_first_finalized_at_unix_secs,
(
SELECT MAX(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
FROM usage AS settled_usage
FROM usage_billing_facts AS settled_usage
WHERE settled_usage.created_at >= $1
AND settled_usage.created_at < $2
AND settled_usage.billing_status = 'settled'
@@ -4101,7 +4101,7 @@ SELECT
0
) AS response_time_samples,
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND status NOT IN ('pending', 'streaming')
@@ -4330,7 +4330,7 @@ WITH aggregated AS (
),
0
) AS response_time_samples
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND user_id IS NOT NULL
@@ -4441,7 +4441,7 @@ WITH aggregated AS (
0
) AS response_time_samples,
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND model IS NOT NULL
@@ -4517,7 +4517,7 @@ WITH aggregated AS (
),
0
) AS response_time_samples
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND user_id IS NOT NULL
@@ -4573,7 +4573,7 @@ WITH aggregated AS (
COALESCE(SUM(input_tokens), 0) AS input_tokens,
COALESCE(SUM(output_tokens), 0) AS output_tokens,
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost
FROM usage
FROM usage_billing_facts AS usage
WHERE created_at >= $1
AND created_at < $2
AND provider_name IS NOT NULL

View File

@@ -110,7 +110,7 @@ SELECT
"usage".user_id,
COUNT(*)::BIGINT AS request_count,
COALESCE(SUM(GREATEST(COALESCE("usage".total_tokens, 0), 0)), 0)::BIGINT AS total_tokens
FROM "usage"
FROM usage_billing_facts AS "usage"
WHERE "usage".user_id = ANY($1::TEXT[])
AND "usage".created_at >= $2
AND "usage".status NOT IN ('pending', 'streaming')