mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
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:
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::{BTreeMap, VecDeque};
|
use std::collections::{BTreeMap, VecDeque};
|
||||||
use std::io::Error as IoError;
|
use std::io::Error as IoError;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
use aether_contracts::{
|
use aether_contracts::{
|
||||||
ExecutionPlan, ExecutionStreamTerminalSummary, ExecutionTelemetry, StreamFrame,
|
ExecutionPlan, ExecutionStreamTerminalSummary, ExecutionTelemetry, StreamFrame,
|
||||||
@@ -304,6 +305,7 @@ pub(crate) async fn execute_execution_runtime_stream(
|
|||||||
report_kind: Option<String>,
|
report_kind: Option<String>,
|
||||||
mut report_context: Option<serde_json::Value>,
|
mut report_context: Option<serde_json::Value>,
|
||||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||||
|
let stream_started_at = Instant::now();
|
||||||
ensure_execution_request_candidate_slot(state, &mut plan, &mut report_context).await;
|
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 lifecycle_seed = build_lifecycle_usage_seed(&plan, report_context.as_ref());
|
||||||
let request_candidate_status_snapshot =
|
let request_candidate_status_snapshot =
|
||||||
@@ -391,6 +393,7 @@ pub(crate) async fn execute_execution_runtime_stream(
|
|||||||
report_kind,
|
report_kind,
|
||||||
report_context,
|
report_context,
|
||||||
candidate_started_unix_secs,
|
candidate_started_unix_secs,
|
||||||
|
stream_started_at,
|
||||||
frame_stream,
|
frame_stream,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@@ -450,6 +453,7 @@ pub(crate) async fn execute_execution_runtime_stream(
|
|||||||
report_kind,
|
report_kind,
|
||||||
report_context,
|
report_context,
|
||||||
candidate_started_unix_secs,
|
candidate_started_unix_secs,
|
||||||
|
stream_started_at,
|
||||||
frame_stream,
|
frame_stream,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@@ -534,6 +538,7 @@ pub(crate) async fn execute_execution_runtime_stream(
|
|||||||
report_kind,
|
report_kind,
|
||||||
report_context,
|
report_context,
|
||||||
candidate_started_unix_secs,
|
candidate_started_unix_secs,
|
||||||
|
stream_started_at,
|
||||||
frame_stream,
|
frame_stream,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@@ -675,6 +680,7 @@ async fn execute_stream_from_frame_stream(
|
|||||||
report_kind: Option<String>,
|
report_kind: Option<String>,
|
||||||
report_context: Option<serde_json::Value>,
|
report_context: Option<serde_json::Value>,
|
||||||
candidate_started_unix_secs: u64,
|
candidate_started_unix_secs: u64,
|
||||||
|
stream_started_at: Instant,
|
||||||
frame_stream: BoxStream<'static, Result<Bytes, IoError>>,
|
frame_stream: BoxStream<'static, Result<Bytes, IoError>>,
|
||||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||||
let request_id = plan.request_id.as_str();
|
let request_id = plan.request_id.as_str();
|
||||||
@@ -1639,6 +1645,32 @@ async fn execute_stream_from_frame_stream(
|
|||||||
continue;
|
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(
|
append_stream_capture_bytes(
|
||||||
&mut buffered_body,
|
&mut buffered_body,
|
||||||
&rewritten_chunk,
|
&rewritten_chunk,
|
||||||
@@ -2082,16 +2114,22 @@ fn apply_stream_summary_report_context(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::convert::Infallible;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, RequestBody};
|
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::ws::Message;
|
||||||
use axum::extract::Request;
|
use axum::extract::Request;
|
||||||
use axum::routing::any;
|
use axum::routing::any;
|
||||||
use axum::{http::header, http::HeaderValue, Router};
|
use axum::{http::header, http::HeaderValue, Router};
|
||||||
use serde_json::{json, Value};
|
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 super::{execute_execution_runtime_stream, should_skip_direct_finalize_prefetch};
|
||||||
use crate::control::GatewayControlDecision;
|
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]
|
#[tokio::test]
|
||||||
async fn execute_execution_runtime_stream_bridges_sync_json_body_from_remote_runtime_to_sse() {
|
async fn execute_execution_runtime_stream_bridges_sync_json_body_from_remote_runtime_to_sse() {
|
||||||
let listener = crate::test_support::bind_loopback_listener()
|
let listener = crate::test_support::bind_loopback_listener()
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ WITH aggregated AS (
|
|||||||
COALESCE(SUM(usage.cache_read_input_tokens), 0) AS cache_read_tokens,
|
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,
|
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
|
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
|
JOIN usage_settlement_snapshots
|
||||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||||
WHERE usage_settlement_snapshots.wallet_id IS NOT NULL
|
WHERE usage_settlement_snapshots.wallet_id IS NOT NULL
|
||||||
@@ -184,7 +184,7 @@ WHERE ledgers.billing_date = $1
|
|||||||
AND ledgers.billing_timezone = $2
|
AND ledgers.billing_timezone = $2
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
JOIN usage_settlement_snapshots
|
JOIN usage_settlement_snapshots
|
||||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||||
WHERE usage_settlement_snapshots.wallet_id = ledgers.wallet_id
|
WHERE usage_settlement_snapshots.wallet_id = ledgers.wallet_id
|
||||||
@@ -600,7 +600,7 @@ const SELECT_STATS_DAILY_AGGREGATE_SQL: &str = r#"
|
|||||||
SELECT
|
SELECT
|
||||||
(
|
(
|
||||||
SELECT CAST(COUNT(cache_hit_usage.id) AS BIGINT)
|
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
|
WHERE cache_hit_usage.created_at >= $1
|
||||||
AND cache_hit_usage.created_at < $2
|
AND cache_hit_usage.created_at < $2
|
||||||
) AS cache_hit_total_requests,
|
) AS cache_hit_total_requests,
|
||||||
@@ -610,13 +610,13 @@ SELECT
|
|||||||
WHERE GREATEST(COALESCE(cache_hit_usage.cache_read_input_tokens, 0), 0) > 0
|
WHERE GREATEST(COALESCE(cache_hit_usage.cache_read_input_tokens, 0), 0) > 0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS cache_hit_usage
|
FROM usage_billing_facts AS cache_hit_usage
|
||||||
WHERE cache_hit_usage.created_at >= $1
|
WHERE cache_hit_usage.created_at >= $1
|
||||||
AND cache_hit_usage.created_at < $2
|
AND cache_hit_usage.created_at < $2
|
||||||
) AS cache_hit_requests,
|
) AS cache_hit_requests,
|
||||||
(
|
(
|
||||||
SELECT CAST(COUNT(completed_usage.id) AS BIGINT)
|
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
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -627,7 +627,7 @@ SELECT
|
|||||||
WHERE GREATEST(COALESCE(completed_usage.cache_read_input_tokens, 0), 0) > 0
|
WHERE GREATEST(COALESCE(completed_usage.cache_read_input_tokens, 0), 0) > 0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -636,7 +636,7 @@ SELECT
|
|||||||
SELECT CAST(
|
SELECT CAST(
|
||||||
COALESCE(SUM(GREATEST(COALESCE(completed_usage.input_tokens, 0), 0)), 0) AS BIGINT
|
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
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -659,7 +659,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -671,7 +671,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -809,7 +809,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -826,7 +826,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -840,7 +840,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -850,7 +850,7 @@ SELECT
|
|||||||
COALESCE(SUM(COALESCE(CAST(settled_usage.total_cost_usd AS DOUBLE PRECISION), 0)), 0)
|
COALESCE(SUM(COALESCE(CAST(settled_usage.total_cost_usd AS DOUBLE PRECISION), 0)), 0)
|
||||||
AS DOUBLE PRECISION
|
AS DOUBLE PRECISION
|
||||||
)
|
)
|
||||||
FROM usage AS settled_usage
|
FROM usage_billing_facts AS settled_usage
|
||||||
WHERE settled_usage.created_at >= $1
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -858,7 +858,7 @@ SELECT
|
|||||||
) AS settled_total_cost,
|
) AS settled_total_cost,
|
||||||
(
|
(
|
||||||
SELECT CAST(COUNT(settled_usage.id) AS BIGINT)
|
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
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -868,7 +868,7 @@ SELECT
|
|||||||
SELECT CAST(
|
SELECT CAST(
|
||||||
COALESCE(SUM(GREATEST(COALESCE(settled_usage.input_tokens, 0), 0)), 0) AS BIGINT
|
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
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -878,7 +878,7 @@ SELECT
|
|||||||
SELECT CAST(
|
SELECT CAST(
|
||||||
COALESCE(SUM(GREATEST(COALESCE(settled_usage.output_tokens, 0), 0)), 0) AS BIGINT
|
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
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -891,7 +891,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS settled_usage
|
FROM usage_billing_facts AS settled_usage
|
||||||
WHERE settled_usage.created_at >= $1
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -904,7 +904,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS settled_usage
|
FROM usage_billing_facts AS settled_usage
|
||||||
WHERE settled_usage.created_at >= $1
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -912,7 +912,7 @@ SELECT
|
|||||||
) AS settled_cache_read_tokens,
|
) AS settled_cache_read_tokens,
|
||||||
(
|
(
|
||||||
SELECT MIN(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
|
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
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -920,7 +920,7 @@ SELECT
|
|||||||
) AS settled_first_finalized_at_unix_secs,
|
) AS settled_first_finalized_at_unix_secs,
|
||||||
(
|
(
|
||||||
SELECT MAX(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
|
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
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
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(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 model) AS BIGINT) AS unique_models,
|
||||||
CAST(COUNT(DISTINCT provider_name) AS BIGINT) AS unique_providers
|
CAST(COUNT(DISTINCT provider_name) AS BIGINT) AS unique_providers
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND status NOT IN ('pending', 'streaming')
|
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.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.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
|
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
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND status = 'completed'
|
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.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.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
|
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
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND status = 'completed'
|
AND status = 'completed'
|
||||||
@@ -1295,7 +1295,7 @@ WITH aggregated AS (
|
|||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
) AS response_time_samples,
|
) AS response_time_samples,
|
||||||
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms
|
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
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND model IS NOT NULL
|
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_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(cache_read_input_tokens), 0) AS BIGINT) AS cache_read_tokens,
|
||||||
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost
|
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
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND status NOT IN ('pending', 'streaming')
|
AND status NOT IN ('pending', 'streaming')
|
||||||
@@ -1437,7 +1437,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
) AS response_time_samples
|
) AS response_time_samples
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND model IS NOT NULL
|
AND model IS NOT NULL
|
||||||
@@ -1523,7 +1523,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
) AS estimated_full_cost
|
) AS estimated_full_cost
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
LEFT JOIN usage_settlement_snapshots
|
LEFT JOIN usage_settlement_snapshots
|
||||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||||
WHERE usage.created_at >= $1
|
WHERE usage.created_at >= $1
|
||||||
@@ -1589,7 +1589,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
) AS estimated_full_cost
|
) AS estimated_full_cost
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
LEFT JOIN usage_settlement_snapshots
|
LEFT JOIN usage_settlement_snapshots
|
||||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||||
WHERE usage.created_at >= $1
|
WHERE usage.created_at >= $1
|
||||||
@@ -1665,7 +1665,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
) AS estimated_full_cost
|
) AS estimated_full_cost
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
LEFT JOIN usage_settlement_snapshots
|
LEFT JOIN usage_settlement_snapshots
|
||||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||||
WHERE usage.created_at >= $1
|
WHERE usage.created_at >= $1
|
||||||
@@ -1742,7 +1742,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
) AS estimated_full_cost
|
) AS estimated_full_cost
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
LEFT JOIN usage_settlement_snapshots
|
LEFT JOIN usage_settlement_snapshots
|
||||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||||
WHERE usage.created_at >= $1
|
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_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(cache_read_input_tokens), 0) AS BIGINT) AS cache_read_tokens,
|
||||||
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost
|
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
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND api_key_id IS NOT NULL
|
AND api_key_id IS NOT NULL
|
||||||
@@ -1894,7 +1894,7 @@ WITH aggregated AS (
|
|||||||
provider_name,
|
provider_name,
|
||||||
model,
|
model,
|
||||||
CAST(COUNT(id) AS BIGINT) AS total_count
|
CAST(COUNT(id) AS BIGINT) AS total_count
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND error_category IS NOT NULL
|
AND error_category IS NOT NULL
|
||||||
@@ -2183,7 +2183,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
) AS response_time_samples
|
) AS response_time_samples
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND user_id IS NOT NULL
|
AND user_id IS NOT NULL
|
||||||
@@ -2472,7 +2472,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
) AS successful_response_time_samples
|
) AS successful_response_time_samples
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND user_id IS NOT NULL
|
AND user_id IS NOT NULL
|
||||||
@@ -2740,7 +2740,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
) AS successful_response_time_samples
|
) AS successful_response_time_samples
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND user_id IS NOT NULL
|
AND user_id IS NOT NULL
|
||||||
@@ -3008,7 +3008,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
) AS successful_response_time_samples
|
) AS successful_response_time_samples
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND user_id IS NOT NULL
|
AND user_id IS NOT NULL
|
||||||
@@ -3122,7 +3122,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
) AS response_time_samples
|
) AS response_time_samples
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND user_id IS NOT NULL
|
AND user_id IS NOT NULL
|
||||||
@@ -3218,7 +3218,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
) AS estimated_full_cost
|
) AS estimated_full_cost
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
LEFT JOIN usage_settlement_snapshots
|
LEFT JOIN usage_settlement_snapshots
|
||||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||||
WHERE usage.created_at >= $1
|
WHERE usage.created_at >= $1
|
||||||
@@ -3300,7 +3300,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
) AS estimated_full_cost
|
) AS estimated_full_cost
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
LEFT JOIN usage_settlement_snapshots
|
LEFT JOIN usage_settlement_snapshots
|
||||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||||
WHERE usage.created_at >= $1
|
WHERE usage.created_at >= $1
|
||||||
@@ -3386,7 +3386,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
) AS estimated_full_cost
|
) AS estimated_full_cost
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
LEFT JOIN usage_settlement_snapshots
|
LEFT JOIN usage_settlement_snapshots
|
||||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||||
WHERE usage.created_at >= $1
|
WHERE usage.created_at >= $1
|
||||||
@@ -3473,7 +3473,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
) AS estimated_full_cost
|
) AS estimated_full_cost
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
LEFT JOIN usage_settlement_snapshots
|
LEFT JOIN usage_settlement_snapshots
|
||||||
ON usage_settlement_snapshots.request_id = usage.request_id
|
ON usage_settlement_snapshots.request_id = usage.request_id
|
||||||
WHERE usage.created_at >= $1
|
WHERE usage.created_at >= $1
|
||||||
@@ -3700,7 +3700,7 @@ WHERE is_complete IS TRUE
|
|||||||
"#;
|
"#;
|
||||||
const SELECT_NEXT_STATS_DAILY_BUCKET_SQL: &str = r#"
|
const SELECT_NEXT_STATS_DAILY_BUCKET_SQL: &str = r#"
|
||||||
SELECT date_trunc('day', MIN(created_at)) AS next_bucket
|
SELECT date_trunc('day', MIN(created_at)) AS next_bucket
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND status NOT IN ('pending', 'streaming')
|
AND status NOT IN ('pending', 'streaming')
|
||||||
@@ -3713,7 +3713,7 @@ WHERE is_complete IS TRUE
|
|||||||
"#;
|
"#;
|
||||||
const SELECT_NEXT_STATS_HOURLY_BUCKET_SQL: &str = r#"
|
const SELECT_NEXT_STATS_HOURLY_BUCKET_SQL: &str = r#"
|
||||||
SELECT date_trunc('hour', MIN(created_at)) AS next_bucket
|
SELECT date_trunc('hour', MIN(created_at)) AS next_bucket
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND status NOT IN ('pending', 'streaming')
|
AND status NOT IN ('pending', 'streaming')
|
||||||
@@ -3723,7 +3723,7 @@ const SELECT_STATS_HOURLY_AGGREGATE_SQL: &str = r#"
|
|||||||
SELECT
|
SELECT
|
||||||
(
|
(
|
||||||
SELECT CAST(COUNT(cache_hit_usage.id) AS BIGINT)
|
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
|
WHERE cache_hit_usage.created_at >= $1
|
||||||
AND cache_hit_usage.created_at < $2
|
AND cache_hit_usage.created_at < $2
|
||||||
) AS cache_hit_total_requests,
|
) AS cache_hit_total_requests,
|
||||||
@@ -3733,13 +3733,13 @@ SELECT
|
|||||||
WHERE GREATEST(COALESCE(cache_hit_usage.cache_read_input_tokens, 0), 0) > 0
|
WHERE GREATEST(COALESCE(cache_hit_usage.cache_read_input_tokens, 0), 0) > 0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS cache_hit_usage
|
FROM usage_billing_facts AS cache_hit_usage
|
||||||
WHERE cache_hit_usage.created_at >= $1
|
WHERE cache_hit_usage.created_at >= $1
|
||||||
AND cache_hit_usage.created_at < $2
|
AND cache_hit_usage.created_at < $2
|
||||||
) AS cache_hit_requests,
|
) AS cache_hit_requests,
|
||||||
(
|
(
|
||||||
SELECT CAST(COUNT(completed_usage.id) AS BIGINT)
|
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
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -3750,7 +3750,7 @@ SELECT
|
|||||||
WHERE GREATEST(COALESCE(completed_usage.cache_read_input_tokens, 0), 0) > 0
|
WHERE GREATEST(COALESCE(completed_usage.cache_read_input_tokens, 0), 0) > 0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -3759,7 +3759,7 @@ SELECT
|
|||||||
SELECT CAST(
|
SELECT CAST(
|
||||||
COALESCE(SUM(GREATEST(COALESCE(completed_usage.input_tokens, 0), 0)), 0) AS BIGINT
|
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
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -3782,7 +3782,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -3794,7 +3794,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -3932,7 +3932,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -3949,7 +3949,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -3963,7 +3963,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS DOUBLE PRECISION
|
) AS DOUBLE PRECISION
|
||||||
)
|
)
|
||||||
FROM usage AS completed_usage
|
FROM usage_billing_facts AS completed_usage
|
||||||
WHERE completed_usage.created_at >= $1
|
WHERE completed_usage.created_at >= $1
|
||||||
AND completed_usage.created_at < $2
|
AND completed_usage.created_at < $2
|
||||||
AND completed_usage.status = 'completed'
|
AND completed_usage.status = 'completed'
|
||||||
@@ -3973,7 +3973,7 @@ SELECT
|
|||||||
COALESCE(SUM(COALESCE(CAST(settled_usage.total_cost_usd AS DOUBLE PRECISION), 0)), 0)
|
COALESCE(SUM(COALESCE(CAST(settled_usage.total_cost_usd AS DOUBLE PRECISION), 0)), 0)
|
||||||
AS DOUBLE PRECISION
|
AS DOUBLE PRECISION
|
||||||
)
|
)
|
||||||
FROM usage AS settled_usage
|
FROM usage_billing_facts AS settled_usage
|
||||||
WHERE settled_usage.created_at >= $1
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -3981,7 +3981,7 @@ SELECT
|
|||||||
) AS settled_total_cost,
|
) AS settled_total_cost,
|
||||||
(
|
(
|
||||||
SELECT CAST(COUNT(settled_usage.id) AS BIGINT)
|
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
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -3991,7 +3991,7 @@ SELECT
|
|||||||
SELECT CAST(
|
SELECT CAST(
|
||||||
COALESCE(SUM(GREATEST(COALESCE(settled_usage.input_tokens, 0), 0)), 0) AS BIGINT
|
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
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -4001,7 +4001,7 @@ SELECT
|
|||||||
SELECT CAST(
|
SELECT CAST(
|
||||||
COALESCE(SUM(GREATEST(COALESCE(settled_usage.output_tokens, 0), 0)), 0) AS BIGINT
|
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
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -4014,7 +4014,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS settled_usage
|
FROM usage_billing_facts AS settled_usage
|
||||||
WHERE settled_usage.created_at >= $1
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -4027,7 +4027,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS BIGINT
|
) AS BIGINT
|
||||||
)
|
)
|
||||||
FROM usage AS settled_usage
|
FROM usage_billing_facts AS settled_usage
|
||||||
WHERE settled_usage.created_at >= $1
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -4035,7 +4035,7 @@ SELECT
|
|||||||
) AS settled_cache_read_tokens,
|
) AS settled_cache_read_tokens,
|
||||||
(
|
(
|
||||||
SELECT MIN(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
|
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
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -4043,7 +4043,7 @@ SELECT
|
|||||||
) AS settled_first_finalized_at_unix_secs,
|
) AS settled_first_finalized_at_unix_secs,
|
||||||
(
|
(
|
||||||
SELECT MAX(CAST(EXTRACT(EPOCH FROM settled_usage.finalized_at) AS BIGINT))
|
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
|
WHERE settled_usage.created_at >= $1
|
||||||
AND settled_usage.created_at < $2
|
AND settled_usage.created_at < $2
|
||||||
AND settled_usage.billing_status = 'settled'
|
AND settled_usage.billing_status = 'settled'
|
||||||
@@ -4101,7 +4101,7 @@ SELECT
|
|||||||
0
|
0
|
||||||
) AS response_time_samples,
|
) AS response_time_samples,
|
||||||
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms
|
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
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND status NOT IN ('pending', 'streaming')
|
AND status NOT IN ('pending', 'streaming')
|
||||||
@@ -4330,7 +4330,7 @@ WITH aggregated AS (
|
|||||||
),
|
),
|
||||||
0
|
0
|
||||||
) AS response_time_samples
|
) AS response_time_samples
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND user_id IS NOT NULL
|
AND user_id IS NOT NULL
|
||||||
@@ -4441,7 +4441,7 @@ WITH aggregated AS (
|
|||||||
0
|
0
|
||||||
) AS response_time_samples,
|
) AS response_time_samples,
|
||||||
CAST(COALESCE(AVG(response_time_ms), 0) AS DOUBLE PRECISION) AS avg_response_time_ms
|
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
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND model IS NOT NULL
|
AND model IS NOT NULL
|
||||||
@@ -4517,7 +4517,7 @@ WITH aggregated AS (
|
|||||||
),
|
),
|
||||||
0
|
0
|
||||||
) AS response_time_samples
|
) AS response_time_samples
|
||||||
FROM usage
|
FROM usage_billing_facts AS usage
|
||||||
WHERE created_at >= $1
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND user_id IS NOT NULL
|
AND user_id IS NOT NULL
|
||||||
@@ -4573,7 +4573,7 @@ WITH aggregated AS (
|
|||||||
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
||||||
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
||||||
CAST(COALESCE(SUM(total_cost_usd), 0) AS DOUBLE PRECISION) AS total_cost
|
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
|
WHERE created_at >= $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
AND provider_name IS NOT NULL
|
AND provider_name IS NOT NULL
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ SELECT
|
|||||||
"usage".user_id,
|
"usage".user_id,
|
||||||
COUNT(*)::BIGINT AS request_count,
|
COUNT(*)::BIGINT AS request_count,
|
||||||
COALESCE(SUM(GREATEST(COALESCE("usage".total_tokens, 0), 0)), 0)::BIGINT AS total_tokens
|
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[])
|
WHERE "usage".user_id = ANY($1::TEXT[])
|
||||||
AND "usage".created_at >= $2
|
AND "usage".created_at >= $2
|
||||||
AND "usage".status NOT IN ('pending', 'streaming')
|
AND "usage".status NOT IN ('pending', 'streaming')
|
||||||
|
|||||||
@@ -299,6 +299,9 @@ fn admin_usage_strip_settlement_metadata(metadata: &mut serde_json::Map<String,
|
|||||||
metadata.remove("billing_snapshot");
|
metadata.remove("billing_snapshot");
|
||||||
metadata.remove("billing_snapshot_schema_version");
|
metadata.remove("billing_snapshot_schema_version");
|
||||||
metadata.remove("billing_snapshot_status");
|
metadata.remove("billing_snapshot_status");
|
||||||
|
metadata.remove("settlement_snapshot");
|
||||||
|
metadata.remove("settlement_snapshot_schema_version");
|
||||||
|
metadata.remove("billing_dimensions");
|
||||||
metadata.remove("rate_multiplier");
|
metadata.remove("rate_multiplier");
|
||||||
metadata.remove("is_free_tier");
|
metadata.remove("is_free_tier");
|
||||||
metadata.remove("input_price_per_1m");
|
metadata.remove("input_price_per_1m");
|
||||||
@@ -353,6 +356,36 @@ fn admin_usage_body_capture_json(item: &StoredRequestUsageAudit) -> Value {
|
|||||||
|
|
||||||
fn admin_usage_settlement_json(item: &StoredRequestUsageAudit) -> Value {
|
fn admin_usage_settlement_json(item: &StoredRequestUsageAudit) -> Value {
|
||||||
let mut settlement = serde_json::Map::new();
|
let mut settlement = serde_json::Map::new();
|
||||||
|
if let Some(snapshot) = item
|
||||||
|
.request_metadata
|
||||||
|
.as_ref()
|
||||||
|
.and_then(Value::as_object)
|
||||||
|
.and_then(|metadata| metadata.get("settlement_snapshot"))
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
settlement.insert("settlement_snapshot".to_string(), snapshot);
|
||||||
|
}
|
||||||
|
if let Some(schema_version) = item
|
||||||
|
.request_metadata
|
||||||
|
.as_ref()
|
||||||
|
.and_then(Value::as_object)
|
||||||
|
.and_then(|metadata| metadata.get("settlement_snapshot_schema_version"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
{
|
||||||
|
settlement.insert(
|
||||||
|
"settlement_snapshot_schema_version".to_string(),
|
||||||
|
json!(schema_version),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(dimensions) = item
|
||||||
|
.request_metadata
|
||||||
|
.as_ref()
|
||||||
|
.and_then(Value::as_object)
|
||||||
|
.and_then(|metadata| metadata.get("billing_dimensions"))
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
settlement.insert("billing_dimensions".to_string(), dimensions);
|
||||||
|
}
|
||||||
if let Some(snapshot) = item
|
if let Some(snapshot) = item
|
||||||
.request_metadata
|
.request_metadata
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -2339,6 +2372,17 @@ mod tests {
|
|||||||
"cache_creation_price_per_1m": 3.75,
|
"cache_creation_price_per_1m": 3.75,
|
||||||
"cache_read_price_per_1m": 0.30,
|
"cache_read_price_per_1m": 0.30,
|
||||||
"price_per_request": 0.02,
|
"price_per_request": 0.02,
|
||||||
|
"settlement_snapshot_schema_version": "3.0",
|
||||||
|
"billing_dimensions": {
|
||||||
|
"input_tokens": 35,
|
||||||
|
"total_input_context": 42
|
||||||
|
},
|
||||||
|
"settlement_snapshot": {
|
||||||
|
"schema_version": "3.0",
|
||||||
|
"pricing_snapshot": {
|
||||||
|
"pricing_source": "provider_override"
|
||||||
|
}
|
||||||
|
},
|
||||||
"billing_snapshot": {
|
"billing_snapshot": {
|
||||||
"resolved_variables": {
|
"resolved_variables": {
|
||||||
"output_price_per_1m": 11.0
|
"output_price_per_1m": 11.0
|
||||||
@@ -2369,6 +2413,18 @@ mod tests {
|
|||||||
payload["settlement"]["billing_snapshot"]["resolved_variables"]["output_price_per_1m"],
|
payload["settlement"]["billing_snapshot"]["resolved_variables"]["output_price_per_1m"],
|
||||||
11.0
|
11.0
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
payload["settlement"]["settlement_snapshot_schema_version"],
|
||||||
|
"3.0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
payload["settlement"]["settlement_snapshot"]["pricing_snapshot"]["pricing_source"],
|
||||||
|
"provider_override"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
payload["settlement"]["billing_dimensions"]["input_tokens"],
|
||||||
|
35
|
||||||
|
);
|
||||||
assert_eq!(payload["settlement"]["billing_snapshot_status"], "resolved");
|
assert_eq!(payload["settlement"]["billing_snapshot_status"], "resolved");
|
||||||
assert_eq!(payload["settlement"]["rate_multiplier"], 0.5);
|
assert_eq!(payload["settlement"]["rate_multiplier"], 0.5);
|
||||||
assert_eq!(payload["settlement"]["is_free_tier"], false);
|
assert_eq!(payload["settlement"]["is_free_tier"], false);
|
||||||
@@ -2381,6 +2437,9 @@ mod tests {
|
|||||||
assert!(payload["metadata"]["billing_snapshot"].is_null());
|
assert!(payload["metadata"]["billing_snapshot"].is_null());
|
||||||
assert!(payload["metadata"]["billing_snapshot_schema_version"].is_null());
|
assert!(payload["metadata"]["billing_snapshot_schema_version"].is_null());
|
||||||
assert!(payload["metadata"]["billing_snapshot_status"].is_null());
|
assert!(payload["metadata"]["billing_snapshot_status"].is_null());
|
||||||
|
assert!(payload["metadata"]["settlement_snapshot"].is_null());
|
||||||
|
assert!(payload["metadata"]["settlement_snapshot_schema_version"].is_null());
|
||||||
|
assert!(payload["metadata"]["billing_dimensions"].is_null());
|
||||||
assert!(payload["metadata"]["rate_multiplier"].is_null());
|
assert!(payload["metadata"]["rate_multiplier"].is_null());
|
||||||
assert!(payload["metadata"]["is_free_tier"].is_null());
|
assert!(payload["metadata"]["is_free_tier"].is_null());
|
||||||
assert!(payload["metadata"]["input_price_per_1m"].is_null());
|
assert!(payload["metadata"]["input_price_per_1m"].is_null());
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ use aether_data_contracts::repository::billing::StoredBillingModelContext;
|
|||||||
use aether_data_contracts::DataLayerError;
|
use aether_data_contracts::DataLayerError;
|
||||||
use aether_usage_runtime::{UsageEvent, UsageEventType};
|
use aether_usage_runtime::{UsageEvent, UsageEventType};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{json, Map, Value};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BillingComputation, BillingModelPricingSnapshot, BillingService, BillingSnapshotStatus,
|
BillingComputation, BillingModelPricingSnapshot, BillingService, BillingSnapshotStatus,
|
||||||
BillingUsageInput,
|
BillingUsageInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SETTLEMENT_SNAPSHOT_SCHEMA_VERSION: &str = "3.0";
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait BillingModelContextLookup: Send + Sync {
|
pub trait BillingModelContextLookup: Send + Sync {
|
||||||
async fn find_billing_model_context_by_model_id(
|
async fn find_billing_model_context_by_model_id(
|
||||||
@@ -65,7 +67,7 @@ pub async fn enrich_usage_event_with_billing(
|
|||||||
{
|
{
|
||||||
let pricing = map_pricing_context(context);
|
let pricing = map_pricing_context(context);
|
||||||
let computation = calculate_billing_computation(&pricing, event)?;
|
let computation = calculate_billing_computation(&pricing, event)?;
|
||||||
apply_billing_computation(event, computation)?;
|
apply_billing_computation(event, &pricing, computation)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,15 +91,15 @@ pub async fn enrich_usage_event_with_billing(
|
|||||||
computation.cost_result.status,
|
computation.cost_result.status,
|
||||||
BillingSnapshotStatus::NoRule
|
BillingSnapshotStatus::NoRule
|
||||||
) {
|
) {
|
||||||
first_no_rule.get_or_insert(computation);
|
first_no_rule.get_or_insert((pricing, computation));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
apply_billing_computation(event, computation)?;
|
apply_billing_computation(event, &pricing, computation)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(computation) = first_no_rule {
|
if let Some((pricing, computation)) = first_no_rule {
|
||||||
apply_billing_computation(event, computation)?;
|
apply_billing_computation(event, &pricing, computation)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -162,13 +164,16 @@ fn calculate_billing_computation(
|
|||||||
|
|
||||||
fn apply_billing_computation(
|
fn apply_billing_computation(
|
||||||
event: &mut UsageEvent,
|
event: &mut UsageEvent,
|
||||||
|
pricing: &BillingModelPricingSnapshot,
|
||||||
computation: BillingComputation,
|
computation: BillingComputation,
|
||||||
) -> Result<(), DataLayerError> {
|
) -> Result<(), DataLayerError> {
|
||||||
event.data.total_cost_usd = Some(computation.cost_result.cost);
|
event.data.total_cost_usd = Some(computation.cost_result.cost);
|
||||||
event.data.actual_total_cost_usd = Some(computation.actual_total_cost);
|
event.data.actual_total_cost_usd = Some(computation.actual_total_cost);
|
||||||
merge_billing_snapshot_metadata(
|
merge_billing_snapshot_metadata(
|
||||||
&mut event.data.request_metadata,
|
&mut event.data.request_metadata,
|
||||||
|
pricing,
|
||||||
&computation.cost_result.snapshot,
|
&computation.cost_result.snapshot,
|
||||||
|
computation.actual_total_cost,
|
||||||
computation.rate_multiplier,
|
computation.rate_multiplier,
|
||||||
computation.is_free_tier,
|
computation.is_free_tier,
|
||||||
)
|
)
|
||||||
@@ -196,25 +201,83 @@ fn map_pricing_context(context: StoredBillingModelContext) -> BillingModelPricin
|
|||||||
|
|
||||||
fn merge_billing_snapshot_metadata(
|
fn merge_billing_snapshot_metadata(
|
||||||
request_metadata: &mut Option<Value>,
|
request_metadata: &mut Option<Value>,
|
||||||
|
pricing: &BillingModelPricingSnapshot,
|
||||||
snapshot: &crate::BillingSnapshot,
|
snapshot: &crate::BillingSnapshot,
|
||||||
|
actual_total_cost: f64,
|
||||||
rate_multiplier: f64,
|
rate_multiplier: f64,
|
||||||
is_free_tier: bool,
|
is_free_tier: bool,
|
||||||
) -> Result<(), DataLayerError> {
|
) -> Result<(), DataLayerError> {
|
||||||
let snapshot = serde_json::to_value(snapshot).map_err(|err| {
|
let billing_snapshot = serde_json::to_value(snapshot).map_err(|err| {
|
||||||
DataLayerError::UnexpectedValue(format!("failed to serialize billing snapshot: {err}"))
|
DataLayerError::UnexpectedValue(format!("failed to serialize billing snapshot: {err}"))
|
||||||
})?;
|
})?;
|
||||||
|
let settlement_snapshot = build_settlement_snapshot(
|
||||||
|
pricing,
|
||||||
|
snapshot,
|
||||||
|
actual_total_cost,
|
||||||
|
rate_multiplier,
|
||||||
|
is_free_tier,
|
||||||
|
);
|
||||||
|
|
||||||
let mut metadata = match request_metadata.take() {
|
let mut metadata = match request_metadata.take() {
|
||||||
Some(Value::Object(object)) => object,
|
Some(Value::Object(object)) => object,
|
||||||
_ => Map::new(),
|
_ => Map::new(),
|
||||||
};
|
};
|
||||||
metadata.insert("billing_snapshot".to_string(), snapshot);
|
metadata.insert("billing_snapshot".to_string(), billing_snapshot);
|
||||||
|
metadata.insert(
|
||||||
|
"settlement_snapshot_schema_version".to_string(),
|
||||||
|
Value::from(SETTLEMENT_SNAPSHOT_SCHEMA_VERSION),
|
||||||
|
);
|
||||||
|
metadata.insert("settlement_snapshot".to_string(), settlement_snapshot);
|
||||||
|
metadata.insert(
|
||||||
|
"billing_dimensions".to_string(),
|
||||||
|
Value::Object(snapshot.resolved_dimensions.clone().into_iter().collect()),
|
||||||
|
);
|
||||||
metadata.insert("rate_multiplier".to_string(), Value::from(rate_multiplier));
|
metadata.insert("rate_multiplier".to_string(), Value::from(rate_multiplier));
|
||||||
metadata.insert("is_free_tier".to_string(), Value::from(is_free_tier));
|
metadata.insert("is_free_tier".to_string(), Value::from(is_free_tier));
|
||||||
*request_metadata = Some(Value::Object(metadata));
|
*request_metadata = Some(Value::Object(metadata));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_settlement_snapshot(
|
||||||
|
pricing: &BillingModelPricingSnapshot,
|
||||||
|
snapshot: &crate::BillingSnapshot,
|
||||||
|
actual_total_cost: f64,
|
||||||
|
rate_multiplier: f64,
|
||||||
|
is_free_tier: bool,
|
||||||
|
) -> Value {
|
||||||
|
json!({
|
||||||
|
"schema_version": SETTLEMENT_SNAPSHOT_SCHEMA_VERSION,
|
||||||
|
"pricing_snapshot": {
|
||||||
|
"provider_id": pricing.provider_id.clone(),
|
||||||
|
"provider_billing_type": pricing.provider_billing_type.clone(),
|
||||||
|
"provider_api_key_id": pricing.provider_api_key_id.clone(),
|
||||||
|
"global_model_id": pricing.global_model_id.clone(),
|
||||||
|
"global_model_name": pricing.global_model_name.clone(),
|
||||||
|
"model_id": pricing.model_id.clone(),
|
||||||
|
"provider_model_name": pricing.model_provider_model_name.clone(),
|
||||||
|
"pricing_source": pricing.pricing_source(),
|
||||||
|
"tiered_pricing": pricing.effective_tiered_pricing().cloned(),
|
||||||
|
"price_per_request": pricing.effective_price_per_request(),
|
||||||
|
"rate_multiplier": rate_multiplier,
|
||||||
|
"is_free_tier": is_free_tier,
|
||||||
|
},
|
||||||
|
"billing_plan_snapshot": {
|
||||||
|
"rule_id": snapshot.rule_id.clone(),
|
||||||
|
"rule_name": snapshot.rule_name.clone(),
|
||||||
|
"scope": snapshot.scope.clone(),
|
||||||
|
"expression": snapshot.expression.clone(),
|
||||||
|
"engine_version": snapshot.engine_version.clone(),
|
||||||
|
},
|
||||||
|
"resolved_dimensions": snapshot.resolved_dimensions.clone(),
|
||||||
|
"resolved_variables": snapshot.resolved_variables.clone(),
|
||||||
|
"cost_breakdown": snapshot.cost_breakdown.clone(),
|
||||||
|
"total_cost": snapshot.total_cost,
|
||||||
|
"actual_total_cost": actual_total_cost,
|
||||||
|
"status": snapshot.status,
|
||||||
|
"calculated_at": snapshot.calculated_at.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use aether_data_contracts::repository::billing::StoredBillingModelContext;
|
use aether_data_contracts::repository::billing::StoredBillingModelContext;
|
||||||
|
|||||||
@@ -33,6 +33,22 @@ impl BillingModelPricingSnapshot {
|
|||||||
.or(self.default_price_per_request)
|
.or(self.default_price_per_request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pricing_source(&self) -> &'static str {
|
||||||
|
if self
|
||||||
|
.model_tiered_pricing
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(has_tiered_pricing_tiers)
|
||||||
|
|| self.model_price_per_request.is_some()
|
||||||
|
{
|
||||||
|
"provider_override"
|
||||||
|
} else if self.default_tiered_pricing.is_some() || self.default_price_per_request.is_some()
|
||||||
|
{
|
||||||
|
"global_default"
|
||||||
|
} else {
|
||||||
|
"unpriced"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_free_tier(&self) -> bool {
|
pub fn is_free_tier(&self) -> bool {
|
||||||
self.provider_billing_type
|
self.provider_billing_type
|
||||||
.as_deref()
|
.as_deref()
|
||||||
|
|||||||
@@ -4711,7 +4711,268 @@ ALTER TABLE IF EXISTS public.usage_settlement_snapshots
|
|||||||
ADD COLUMN IF NOT EXISTS output_price_per_1m numeric(20,8),
|
ADD COLUMN IF NOT EXISTS output_price_per_1m numeric(20,8),
|
||||||
ADD COLUMN IF NOT EXISTS cache_creation_price_per_1m numeric(20,8),
|
ADD COLUMN IF NOT EXISTS cache_creation_price_per_1m numeric(20,8),
|
||||||
ADD COLUMN IF NOT EXISTS cache_read_price_per_1m numeric(20,8),
|
ADD COLUMN IF NOT EXISTS cache_read_price_per_1m numeric(20,8),
|
||||||
ADD COLUMN IF NOT EXISTS price_per_request numeric(20,8);
|
ADD COLUMN IF NOT EXISTS price_per_request numeric(20,8),
|
||||||
|
ADD COLUMN IF NOT EXISTS settlement_snapshot_schema_version character varying(20),
|
||||||
|
ADD COLUMN IF NOT EXISTS settlement_snapshot jsonb,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_dimensions jsonb,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_input_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_effective_input_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_output_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_creation_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_creation_5m_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_creation_1h_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_read_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_total_input_context bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_creation_cost_usd numeric(20,8),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_read_cost_usd numeric(20,8),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_total_cost_usd numeric(20,8),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_actual_total_cost_usd numeric(20,8),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_pricing_source character varying(50),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_rule_id character varying(100),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_rule_version character varying(50);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_usage_settlement_snapshots_schema_version
|
||||||
|
ON public.usage_settlement_snapshots USING btree (settlement_snapshot_schema_version);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_usage_settlement_snapshots_pricing_source
|
||||||
|
ON public.usage_settlement_snapshots USING btree (billing_pricing_source);
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW public.usage_billing_facts AS
|
||||||
|
SELECT
|
||||||
|
usage_rows.id,
|
||||||
|
usage_rows.request_id,
|
||||||
|
usage_rows.user_id,
|
||||||
|
usage_rows.api_key_id,
|
||||||
|
usage_rows.username,
|
||||||
|
usage_rows.api_key_name,
|
||||||
|
usage_rows.provider_name,
|
||||||
|
usage_rows.model,
|
||||||
|
usage_rows.target_model,
|
||||||
|
usage_rows.provider_id,
|
||||||
|
usage_rows.provider_endpoint_id,
|
||||||
|
usage_rows.provider_api_key_id,
|
||||||
|
usage_rows.request_type,
|
||||||
|
usage_rows.api_format,
|
||||||
|
usage_rows.api_family,
|
||||||
|
usage_rows.endpoint_kind,
|
||||||
|
usage_rows.endpoint_api_format,
|
||||||
|
usage_rows.provider_api_family,
|
||||||
|
usage_rows.provider_endpoint_kind,
|
||||||
|
COALESCE(usage_rows.has_format_conversion, FALSE) AS has_format_conversion,
|
||||||
|
COALESCE(usage_rows.is_stream, FALSE) AS is_stream,
|
||||||
|
usage_rows.status_code,
|
||||||
|
usage_rows.error_message,
|
||||||
|
usage_rows.error_category,
|
||||||
|
usage_rows.response_time_ms,
|
||||||
|
usage_rows.first_byte_time_ms,
|
||||||
|
usage_rows.status,
|
||||||
|
COALESCE(settlement.billing_status, usage_rows.billing_status) AS billing_status,
|
||||||
|
usage_rows.created_at,
|
||||||
|
COALESCE(settlement.finalized_at, usage_rows.finalized_at) AS finalized_at,
|
||||||
|
GREATEST(COALESCE(settlement.billing_input_tokens, usage_rows.input_tokens, 0), 0)::bigint
|
||||||
|
AS input_tokens,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
settlement.billing_effective_input_tokens,
|
||||||
|
CASE
|
||||||
|
WHEN GREATEST(COALESCE(usage_rows.input_tokens, 0), 0) <= 0 THEN 0
|
||||||
|
WHEN GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0) <= 0
|
||||||
|
THEN GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
WHEN split_part(lower(COALESCE(COALESCE(usage_rows.endpoint_api_format, usage_rows.api_format), '')), ':', 1)
|
||||||
|
IN ('openai', 'gemini', 'google')
|
||||||
|
THEN GREATEST(
|
||||||
|
GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
- GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
ELSE GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
END
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS effective_input_tokens,
|
||||||
|
GREATEST(COALESCE(settlement.billing_output_tokens, usage_rows.output_tokens, 0), 0)::bigint
|
||||||
|
AS output_tokens,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
settlement.billing_cache_creation_tokens,
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(usage_rows.cache_creation_input_tokens, 0) = 0
|
||||||
|
AND (
|
||||||
|
COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
) > 0
|
||||||
|
THEN COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
ELSE COALESCE(usage_rows.cache_creation_input_tokens, 0)
|
||||||
|
END,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS cache_creation_input_tokens,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
settlement.billing_cache_creation_5m_tokens,
|
||||||
|
usage_rows.cache_creation_input_tokens_5m,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS cache_creation_input_tokens_5m,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
settlement.billing_cache_creation_1h_tokens,
|
||||||
|
usage_rows.cache_creation_input_tokens_1h,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS cache_creation_input_tokens_1h,
|
||||||
|
GREATEST(COALESCE(settlement.billing_cache_read_tokens, usage_rows.cache_read_input_tokens, 0), 0)::bigint
|
||||||
|
AS cache_read_input_tokens,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
CASE
|
||||||
|
WHEN settlement.billing_input_tokens IS NOT NULL
|
||||||
|
OR settlement.billing_output_tokens IS NOT NULL
|
||||||
|
OR settlement.billing_cache_creation_tokens IS NOT NULL
|
||||||
|
OR settlement.billing_cache_creation_5m_tokens IS NOT NULL
|
||||||
|
OR settlement.billing_cache_creation_1h_tokens IS NOT NULL
|
||||||
|
OR settlement.billing_cache_read_tokens IS NOT NULL
|
||||||
|
THEN COALESCE(settlement.billing_input_tokens, 0)
|
||||||
|
+ COALESCE(settlement.billing_output_tokens, 0)
|
||||||
|
+ COALESCE(
|
||||||
|
settlement.billing_cache_creation_tokens,
|
||||||
|
COALESCE(settlement.billing_cache_creation_5m_tokens, 0)
|
||||||
|
+ COALESCE(settlement.billing_cache_creation_1h_tokens, 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
+ COALESCE(settlement.billing_cache_read_tokens, 0)
|
||||||
|
END,
|
||||||
|
usage_rows.total_tokens,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS total_tokens,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
settlement.billing_total_input_context,
|
||||||
|
CASE
|
||||||
|
WHEN split_part(lower(COALESCE(COALESCE(usage_rows.endpoint_api_format, usage_rows.api_format), '')), ':', 1)
|
||||||
|
IN ('claude', 'anthropic')
|
||||||
|
THEN GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
+ CASE
|
||||||
|
WHEN COALESCE(usage_rows.cache_creation_input_tokens, 0) = 0
|
||||||
|
AND (
|
||||||
|
COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
) > 0
|
||||||
|
THEN COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
ELSE COALESCE(usage_rows.cache_creation_input_tokens, 0)
|
||||||
|
END
|
||||||
|
+ GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0)
|
||||||
|
WHEN split_part(lower(COALESCE(COALESCE(usage_rows.endpoint_api_format, usage_rows.api_format), '')), ':', 1)
|
||||||
|
IN ('openai', 'gemini', 'google')
|
||||||
|
THEN CASE
|
||||||
|
WHEN GREATEST(COALESCE(usage_rows.input_tokens, 0), 0) <= 0 THEN 0
|
||||||
|
WHEN GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0) <= 0
|
||||||
|
THEN GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
ELSE GREATEST(
|
||||||
|
GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
- GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
END
|
||||||
|
+ GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0)
|
||||||
|
ELSE GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
+ CASE
|
||||||
|
WHEN COALESCE(usage_rows.cache_creation_input_tokens, 0) = 0
|
||||||
|
AND (
|
||||||
|
COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
) > 0
|
||||||
|
THEN COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
ELSE COALESCE(usage_rows.cache_creation_input_tokens, 0)
|
||||||
|
END
|
||||||
|
+ GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0)
|
||||||
|
END,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS total_input_context,
|
||||||
|
COALESCE(CAST(usage_rows.input_cost_usd AS DOUBLE PRECISION), 0) AS input_cost_usd,
|
||||||
|
COALESCE(CAST(usage_rows.output_cost_usd AS DOUBLE PRECISION), 0) AS output_cost_usd,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.billing_cache_creation_cost_usd AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.cache_creation_cost_usd AS DOUBLE PRECISION),
|
||||||
|
0
|
||||||
|
) AS cache_creation_cost_usd,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.billing_cache_read_cost_usd AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.cache_read_cost_usd AS DOUBLE PRECISION),
|
||||||
|
0
|
||||||
|
) AS cache_read_cost_usd,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.billing_total_cost_usd AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.total_cost_usd AS DOUBLE PRECISION),
|
||||||
|
0
|
||||||
|
) AS total_cost_usd,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.billing_actual_total_cost_usd AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.actual_total_cost_usd AS DOUBLE PRECISION),
|
||||||
|
0
|
||||||
|
) AS actual_total_cost_usd,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.output_price_per_1m AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.output_price_per_1m AS DOUBLE PRECISION)
|
||||||
|
) AS output_price_per_1m,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.input_price_per_1m AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.input_price_per_1m AS DOUBLE PRECISION)
|
||||||
|
) AS input_price_per_1m,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.cache_creation_price_per_1m AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.cache_creation_price_per_1m AS DOUBLE PRECISION)
|
||||||
|
) AS cache_creation_price_per_1m,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.cache_read_price_per_1m AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.cache_read_price_per_1m AS DOUBLE PRECISION)
|
||||||
|
) AS cache_read_price_per_1m,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.price_per_request AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.price_per_request AS DOUBLE PRECISION)
|
||||||
|
) AS price_per_request,
|
||||||
|
settlement.billing_pricing_source,
|
||||||
|
settlement.billing_rule_id,
|
||||||
|
settlement.billing_rule_version
|
||||||
|
FROM public."usage" AS usage_rows
|
||||||
|
LEFT JOIN public.usage_settlement_snapshots AS settlement
|
||||||
|
ON settlement.request_id = usage_rows.request_id;
|
||||||
|
|
||||||
|
COMMENT ON VIEW public.usage_billing_facts IS
|
||||||
|
'Canonical billing read model. Token/cost fields prefer usage_settlement_snapshots.billing_* and fall back to deprecated usage mirrors for legacy rows.';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN public.usage.input_tokens IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_input_tokens or public.usage_billing_facts.input_tokens.';
|
||||||
|
COMMENT ON COLUMN public.usage.output_tokens IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_output_tokens or public.usage_billing_facts.output_tokens.';
|
||||||
|
COMMENT ON COLUMN public.usage.total_tokens IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_billing_facts.total_tokens.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_creation_input_tokens IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_cache_creation_tokens or public.usage_billing_facts.cache_creation_input_tokens.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_creation_input_tokens_5m IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_cache_creation_5m_tokens or public.usage_billing_facts.cache_creation_input_tokens_5m.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_creation_input_tokens_1h IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_cache_creation_1h_tokens or public.usage_billing_facts.cache_creation_input_tokens_1h.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_read_input_tokens IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_cache_read_tokens or public.usage_billing_facts.cache_read_input_tokens.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_creation_cost_usd IS
|
||||||
|
'DEPRECATED: billing cost mirror. Use public.usage_settlement_snapshots.billing_cache_creation_cost_usd or public.usage_billing_facts.cache_creation_cost_usd.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_read_cost_usd IS
|
||||||
|
'DEPRECATED: billing cost mirror. Use public.usage_settlement_snapshots.billing_cache_read_cost_usd or public.usage_billing_facts.cache_read_cost_usd.';
|
||||||
|
COMMENT ON COLUMN public.usage.total_cost_usd IS
|
||||||
|
'DEPRECATED: billing cost mirror. Use public.usage_settlement_snapshots.billing_total_cost_usd or public.usage_billing_facts.total_cost_usd.';
|
||||||
|
COMMENT ON COLUMN public.usage.actual_total_cost_usd IS
|
||||||
|
'DEPRECATED: billing cost mirror. Use public.usage_settlement_snapshots.billing_actual_total_cost_usd or public.usage_billing_facts.actual_total_cost_usd.';
|
||||||
|
|
||||||
COMMENT ON COLUMN public.usage.wallet_id IS
|
COMMENT ON COLUMN public.usage.wallet_id IS
|
||||||
'DEPRECATED: settlement owner moved to public.usage_settlement_snapshots.wallet_id. Legacy compatibility only; do not write new values.';
|
'DEPRECATED: settlement owner moved to public.usage_settlement_snapshots.wallet_id. Legacy compatibility only; do not write new values.';
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
ALTER TABLE IF EXISTS public.usage_settlement_snapshots
|
||||||
|
ADD COLUMN IF NOT EXISTS settlement_snapshot_schema_version character varying(20),
|
||||||
|
ADD COLUMN IF NOT EXISTS settlement_snapshot jsonb,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_dimensions jsonb,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_input_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_effective_input_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_output_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_creation_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_creation_5m_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_creation_1h_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_read_tokens bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_total_input_context bigint,
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_creation_cost_usd numeric(20,8),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_cache_read_cost_usd numeric(20,8),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_total_cost_usd numeric(20,8),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_actual_total_cost_usd numeric(20,8),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_pricing_source character varying(50),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_rule_id character varying(100),
|
||||||
|
ADD COLUMN IF NOT EXISTS billing_rule_version character varying(50);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_usage_settlement_snapshots_schema_version
|
||||||
|
ON public.usage_settlement_snapshots USING btree (settlement_snapshot_schema_version);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_usage_settlement_snapshots_pricing_source
|
||||||
|
ON public.usage_settlement_snapshots USING btree (billing_pricing_source);
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW public.usage_billing_facts AS
|
||||||
|
SELECT
|
||||||
|
usage_rows.id,
|
||||||
|
usage_rows.request_id,
|
||||||
|
usage_rows.user_id,
|
||||||
|
usage_rows.api_key_id,
|
||||||
|
usage_rows.username,
|
||||||
|
usage_rows.api_key_name,
|
||||||
|
usage_rows.provider_name,
|
||||||
|
usage_rows.model,
|
||||||
|
usage_rows.target_model,
|
||||||
|
usage_rows.provider_id,
|
||||||
|
usage_rows.provider_endpoint_id,
|
||||||
|
usage_rows.provider_api_key_id,
|
||||||
|
usage_rows.request_type,
|
||||||
|
usage_rows.api_format,
|
||||||
|
usage_rows.api_family,
|
||||||
|
usage_rows.endpoint_kind,
|
||||||
|
usage_rows.endpoint_api_format,
|
||||||
|
usage_rows.provider_api_family,
|
||||||
|
usage_rows.provider_endpoint_kind,
|
||||||
|
COALESCE(usage_rows.has_format_conversion, FALSE) AS has_format_conversion,
|
||||||
|
COALESCE(usage_rows.is_stream, FALSE) AS is_stream,
|
||||||
|
usage_rows.status_code,
|
||||||
|
usage_rows.error_message,
|
||||||
|
usage_rows.error_category,
|
||||||
|
usage_rows.response_time_ms,
|
||||||
|
usage_rows.first_byte_time_ms,
|
||||||
|
usage_rows.status,
|
||||||
|
COALESCE(settlement.billing_status, usage_rows.billing_status) AS billing_status,
|
||||||
|
usage_rows.created_at,
|
||||||
|
COALESCE(settlement.finalized_at, usage_rows.finalized_at) AS finalized_at,
|
||||||
|
GREATEST(COALESCE(settlement.billing_input_tokens, usage_rows.input_tokens, 0), 0)::bigint
|
||||||
|
AS input_tokens,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
settlement.billing_effective_input_tokens,
|
||||||
|
CASE
|
||||||
|
WHEN GREATEST(COALESCE(usage_rows.input_tokens, 0), 0) <= 0 THEN 0
|
||||||
|
WHEN GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0) <= 0
|
||||||
|
THEN GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
WHEN split_part(lower(COALESCE(COALESCE(usage_rows.endpoint_api_format, usage_rows.api_format), '')), ':', 1)
|
||||||
|
IN ('openai', 'gemini', 'google')
|
||||||
|
THEN GREATEST(
|
||||||
|
GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
- GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
ELSE GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
END
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS effective_input_tokens,
|
||||||
|
GREATEST(COALESCE(settlement.billing_output_tokens, usage_rows.output_tokens, 0), 0)::bigint
|
||||||
|
AS output_tokens,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
settlement.billing_cache_creation_tokens,
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(usage_rows.cache_creation_input_tokens, 0) = 0
|
||||||
|
AND (
|
||||||
|
COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
) > 0
|
||||||
|
THEN COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
ELSE COALESCE(usage_rows.cache_creation_input_tokens, 0)
|
||||||
|
END,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS cache_creation_input_tokens,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
settlement.billing_cache_creation_5m_tokens,
|
||||||
|
usage_rows.cache_creation_input_tokens_5m,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS cache_creation_input_tokens_5m,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
settlement.billing_cache_creation_1h_tokens,
|
||||||
|
usage_rows.cache_creation_input_tokens_1h,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS cache_creation_input_tokens_1h,
|
||||||
|
GREATEST(COALESCE(settlement.billing_cache_read_tokens, usage_rows.cache_read_input_tokens, 0), 0)::bigint
|
||||||
|
AS cache_read_input_tokens,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
CASE
|
||||||
|
WHEN settlement.billing_input_tokens IS NOT NULL
|
||||||
|
OR settlement.billing_output_tokens IS NOT NULL
|
||||||
|
OR settlement.billing_cache_creation_tokens IS NOT NULL
|
||||||
|
OR settlement.billing_cache_creation_5m_tokens IS NOT NULL
|
||||||
|
OR settlement.billing_cache_creation_1h_tokens IS NOT NULL
|
||||||
|
OR settlement.billing_cache_read_tokens IS NOT NULL
|
||||||
|
THEN COALESCE(settlement.billing_input_tokens, 0)
|
||||||
|
+ COALESCE(settlement.billing_output_tokens, 0)
|
||||||
|
+ COALESCE(
|
||||||
|
settlement.billing_cache_creation_tokens,
|
||||||
|
COALESCE(settlement.billing_cache_creation_5m_tokens, 0)
|
||||||
|
+ COALESCE(settlement.billing_cache_creation_1h_tokens, 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
+ COALESCE(settlement.billing_cache_read_tokens, 0)
|
||||||
|
END,
|
||||||
|
usage_rows.total_tokens,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS total_tokens,
|
||||||
|
GREATEST(
|
||||||
|
COALESCE(
|
||||||
|
settlement.billing_total_input_context,
|
||||||
|
CASE
|
||||||
|
WHEN split_part(lower(COALESCE(COALESCE(usage_rows.endpoint_api_format, usage_rows.api_format), '')), ':', 1)
|
||||||
|
IN ('claude', 'anthropic')
|
||||||
|
THEN GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
+ CASE
|
||||||
|
WHEN COALESCE(usage_rows.cache_creation_input_tokens, 0) = 0
|
||||||
|
AND (
|
||||||
|
COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
) > 0
|
||||||
|
THEN COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
ELSE COALESCE(usage_rows.cache_creation_input_tokens, 0)
|
||||||
|
END
|
||||||
|
+ GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0)
|
||||||
|
WHEN split_part(lower(COALESCE(COALESCE(usage_rows.endpoint_api_format, usage_rows.api_format), '')), ':', 1)
|
||||||
|
IN ('openai', 'gemini', 'google')
|
||||||
|
THEN CASE
|
||||||
|
WHEN GREATEST(COALESCE(usage_rows.input_tokens, 0), 0) <= 0 THEN 0
|
||||||
|
WHEN GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0) <= 0
|
||||||
|
THEN GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
ELSE GREATEST(
|
||||||
|
GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
- GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
END
|
||||||
|
+ GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0)
|
||||||
|
ELSE GREATEST(COALESCE(usage_rows.input_tokens, 0), 0)
|
||||||
|
+ CASE
|
||||||
|
WHEN COALESCE(usage_rows.cache_creation_input_tokens, 0) = 0
|
||||||
|
AND (
|
||||||
|
COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
) > 0
|
||||||
|
THEN COALESCE(usage_rows.cache_creation_input_tokens_5m, 0)
|
||||||
|
+ COALESCE(usage_rows.cache_creation_input_tokens_1h, 0)
|
||||||
|
ELSE COALESCE(usage_rows.cache_creation_input_tokens, 0)
|
||||||
|
END
|
||||||
|
+ GREATEST(COALESCE(usage_rows.cache_read_input_tokens, 0), 0)
|
||||||
|
END,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)::bigint AS total_input_context,
|
||||||
|
COALESCE(CAST(usage_rows.input_cost_usd AS DOUBLE PRECISION), 0) AS input_cost_usd,
|
||||||
|
COALESCE(CAST(usage_rows.output_cost_usd AS DOUBLE PRECISION), 0) AS output_cost_usd,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.billing_cache_creation_cost_usd AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.cache_creation_cost_usd AS DOUBLE PRECISION),
|
||||||
|
0
|
||||||
|
) AS cache_creation_cost_usd,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.billing_cache_read_cost_usd AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.cache_read_cost_usd AS DOUBLE PRECISION),
|
||||||
|
0
|
||||||
|
) AS cache_read_cost_usd,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.billing_total_cost_usd AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.total_cost_usd AS DOUBLE PRECISION),
|
||||||
|
0
|
||||||
|
) AS total_cost_usd,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.billing_actual_total_cost_usd AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.actual_total_cost_usd AS DOUBLE PRECISION),
|
||||||
|
0
|
||||||
|
) AS actual_total_cost_usd,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.output_price_per_1m AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.output_price_per_1m AS DOUBLE PRECISION)
|
||||||
|
) AS output_price_per_1m,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.input_price_per_1m AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.input_price_per_1m AS DOUBLE PRECISION)
|
||||||
|
) AS input_price_per_1m,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.cache_creation_price_per_1m AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.cache_creation_price_per_1m AS DOUBLE PRECISION)
|
||||||
|
) AS cache_creation_price_per_1m,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.cache_read_price_per_1m AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.cache_read_price_per_1m AS DOUBLE PRECISION)
|
||||||
|
) AS cache_read_price_per_1m,
|
||||||
|
COALESCE(
|
||||||
|
CAST(settlement.price_per_request AS DOUBLE PRECISION),
|
||||||
|
CAST(usage_rows.price_per_request AS DOUBLE PRECISION)
|
||||||
|
) AS price_per_request,
|
||||||
|
settlement.billing_pricing_source,
|
||||||
|
settlement.billing_rule_id,
|
||||||
|
settlement.billing_rule_version
|
||||||
|
FROM public."usage" AS usage_rows
|
||||||
|
LEFT JOIN public.usage_settlement_snapshots AS settlement
|
||||||
|
ON settlement.request_id = usage_rows.request_id;
|
||||||
|
|
||||||
|
COMMENT ON VIEW public.usage_billing_facts IS
|
||||||
|
'Canonical billing read model. Token/cost fields prefer usage_settlement_snapshots.billing_* and fall back to deprecated usage mirrors for legacy rows.';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN public.usage.input_tokens IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_input_tokens or public.usage_billing_facts.input_tokens.';
|
||||||
|
COMMENT ON COLUMN public.usage.output_tokens IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_output_tokens or public.usage_billing_facts.output_tokens.';
|
||||||
|
COMMENT ON COLUMN public.usage.total_tokens IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_billing_facts.total_tokens.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_creation_input_tokens IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_cache_creation_tokens or public.usage_billing_facts.cache_creation_input_tokens.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_creation_input_tokens_5m IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_cache_creation_5m_tokens or public.usage_billing_facts.cache_creation_input_tokens_5m.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_creation_input_tokens_1h IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_cache_creation_1h_tokens or public.usage_billing_facts.cache_creation_input_tokens_1h.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_read_input_tokens IS
|
||||||
|
'DEPRECATED: billing dimension mirror. Use public.usage_settlement_snapshots.billing_cache_read_tokens or public.usage_billing_facts.cache_read_input_tokens.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_creation_cost_usd IS
|
||||||
|
'DEPRECATED: billing cost mirror. Use public.usage_settlement_snapshots.billing_cache_creation_cost_usd or public.usage_billing_facts.cache_creation_cost_usd.';
|
||||||
|
COMMENT ON COLUMN public.usage.cache_read_cost_usd IS
|
||||||
|
'DEPRECATED: billing cost mirror. Use public.usage_settlement_snapshots.billing_cache_read_cost_usd or public.usage_billing_facts.cache_read_cost_usd.';
|
||||||
|
COMMENT ON COLUMN public.usage.total_cost_usd IS
|
||||||
|
'DEPRECATED: billing cost mirror. Use public.usage_settlement_snapshots.billing_total_cost_usd or public.usage_billing_facts.total_cost_usd.';
|
||||||
|
COMMENT ON COLUMN public.usage.actual_total_cost_usd IS
|
||||||
|
'DEPRECATED: billing cost mirror. Use public.usage_settlement_snapshots.billing_actual_total_cost_usd or public.usage_billing_facts.actual_total_cost_usd.';
|
||||||
@@ -8,7 +8,7 @@ use tracing::{error, info, warn};
|
|||||||
|
|
||||||
static MIGRATOR: Migrator = sqlx::migrate!("./migrations");
|
static MIGRATOR: Migrator = sqlx::migrate!("./migrations");
|
||||||
static BASELINE_V2_SQL: &str = include_str!("../bootstrap/20260413020000_baseline_v2.sql");
|
static BASELINE_V2_SQL: &str = include_str!("../bootstrap/20260413020000_baseline_v2.sql");
|
||||||
const BASELINE_V2_CUTOFF_VERSION: i64 = 20260423000000;
|
const BASELINE_V2_CUTOFF_VERSION: i64 = 20260424000000;
|
||||||
const MIGRATIONS_TABLE_EXISTS_SQL: &str =
|
const MIGRATIONS_TABLE_EXISTS_SQL: &str =
|
||||||
"SELECT to_regclass('public._sqlx_migrations') IS NOT NULL";
|
"SELECT to_regclass('public._sqlx_migrations') IS NOT NULL";
|
||||||
const PUBLIC_BASE_TABLE_COUNT_SQL: &str = r#"
|
const PUBLIC_BASE_TABLE_COUNT_SQL: &str = r#"
|
||||||
@@ -663,6 +663,7 @@ SELECT EXISTS (
|
|||||||
20260422110000,
|
20260422110000,
|
||||||
20260422120000,
|
20260422120000,
|
||||||
20260423000000,
|
20260423000000,
|
||||||
|
20260424000000,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -683,6 +684,10 @@ SELECT EXISTS (
|
|||||||
.contains("CREATE TABLE IF NOT EXISTS public.usage_settlement_snapshots"));
|
.contains("CREATE TABLE IF NOT EXISTS public.usage_settlement_snapshots"));
|
||||||
assert!(BASELINE_V2_SQL.contains("billing_snapshot_schema_version"));
|
assert!(BASELINE_V2_SQL.contains("billing_snapshot_schema_version"));
|
||||||
assert!(BASELINE_V2_SQL.contains("price_per_request"));
|
assert!(BASELINE_V2_SQL.contains("price_per_request"));
|
||||||
|
assert!(BASELINE_V2_SQL.contains("settlement_snapshot_schema_version"));
|
||||||
|
assert!(BASELINE_V2_SQL.contains("billing_effective_input_tokens"));
|
||||||
|
assert!(BASELINE_V2_SQL.contains("CREATE OR REPLACE VIEW public.usage_billing_facts"));
|
||||||
|
assert!(BASELINE_V2_SQL.contains("usage_settlement_snapshots.billing_total_cost_usd"));
|
||||||
assert!(BASELINE_V2_SQL.contains("candidate_index integer"));
|
assert!(BASELINE_V2_SQL.contains("candidate_index integer"));
|
||||||
assert!(BASELINE_V2_SQL.contains("CREATE TABLE IF NOT EXISTS public.stats_user_summary"));
|
assert!(BASELINE_V2_SQL.contains("CREATE TABLE IF NOT EXISTS public.stats_user_summary"));
|
||||||
assert!(
|
assert!(
|
||||||
@@ -810,6 +815,7 @@ SELECT EXISTS (
|
|||||||
20260422110000,
|
20260422110000,
|
||||||
20260422120000,
|
20260422120000,
|
||||||
20260423000000,
|
20260423000000,
|
||||||
|
20260424000000,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -75,6 +75,9 @@ fn copy_allowed_metadata_fields(source: &Map<String, Value>, target: &mut Map<St
|
|||||||
copy_non_null_value(source, target, "billing_snapshot");
|
copy_non_null_value(source, target, "billing_snapshot");
|
||||||
copy_non_empty_string(source, target, "billing_snapshot_schema_version");
|
copy_non_empty_string(source, target, "billing_snapshot_schema_version");
|
||||||
copy_non_empty_string(source, target, "billing_snapshot_status");
|
copy_non_empty_string(source, target, "billing_snapshot_status");
|
||||||
|
copy_non_null_value(source, target, "settlement_snapshot");
|
||||||
|
copy_non_empty_string(source, target, "settlement_snapshot_schema_version");
|
||||||
|
copy_non_null_value(source, target, "billing_dimensions");
|
||||||
copy_non_empty_string(source, target, "model_id");
|
copy_non_empty_string(source, target, "model_id");
|
||||||
copy_non_empty_string(source, target, "global_model_id");
|
copy_non_empty_string(source, target, "global_model_id");
|
||||||
copy_non_empty_string(source, target, "global_model_name");
|
copy_non_empty_string(source, target, "global_model_name");
|
||||||
@@ -100,6 +103,9 @@ fn move_allowed_metadata_fields(mut source: Map<String, Value>, target: &mut Map
|
|||||||
remove_non_null_value(&mut source, target, "billing_snapshot");
|
remove_non_null_value(&mut source, target, "billing_snapshot");
|
||||||
remove_non_empty_string(&mut source, target, "billing_snapshot_schema_version");
|
remove_non_empty_string(&mut source, target, "billing_snapshot_schema_version");
|
||||||
remove_non_empty_string(&mut source, target, "billing_snapshot_status");
|
remove_non_empty_string(&mut source, target, "billing_snapshot_status");
|
||||||
|
remove_non_null_value(&mut source, target, "settlement_snapshot");
|
||||||
|
remove_non_empty_string(&mut source, target, "settlement_snapshot_schema_version");
|
||||||
|
remove_non_null_value(&mut source, target, "billing_dimensions");
|
||||||
remove_non_empty_string(&mut source, target, "model_id");
|
remove_non_empty_string(&mut source, target, "model_id");
|
||||||
remove_non_empty_string(&mut source, target, "global_model_id");
|
remove_non_empty_string(&mut source, target, "global_model_id");
|
||||||
remove_non_empty_string(&mut source, target, "global_model_name");
|
remove_non_empty_string(&mut source, target, "global_model_name");
|
||||||
|
|||||||
Reference in New Issue
Block a user