use bitrouter_sdk::{BitrouterError, Result};
use sqlx::{Row, SqlitePool};
pub const MIGRATION_SQL: &str = r#"
CREATE TABLE IF NOT EXISTS requests (
request_id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
api_key_id TEXT NOT NULL,
model_id TEXT NOT NULL,
provider_id TEXT NOT NULL,
prompt_tokens INTEGER NOT NULL,
completion_tokens INTEGER NOT NULL,
reasoning_tokens INTEGER NOT NULL,
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
estimated_charge_micro_usd INTEGER NOT NULL DEFAULT 0,
streamed INTEGER NOT NULL DEFAULT 0,
latency_ms INTEGER NOT NULL DEFAULT 0,
generation_time_ms INTEGER NOT NULL DEFAULT 0,
error TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_requests_api_key_created
ON requests(api_key_id, created_at);
CREATE INDEX IF NOT EXISTS idx_requests_user_created
ON requests(user_id, created_at);
"#;
pub async fn migrate(pool: &SqlitePool) -> Result<()> {
for stmt in MIGRATION_SQL.split(';') {
let stmt = stmt.trim();
if stmt.is_empty() {
continue;
}
sqlx::query(stmt)
.execute(pool)
.await
.map_err(|e| BitrouterError::internal(format!("metering migration: {e}")))?;
}
rename_legacy_charge_column(pool).await?;
Ok(())
}
async fn rename_legacy_charge_column(pool: &SqlitePool) -> Result<()> {
let columns: Vec<String> = sqlx::query("PRAGMA table_info(requests)")
.fetch_all(pool)
.await
.map_err(|e| BitrouterError::internal(format!("metering migration probe: {e}")))?
.into_iter()
.map(|row| row.get::<String, _>("name"))
.collect();
let has_legacy = columns.iter().any(|c| c == "final_charge_micro_usd");
let has_current = columns.iter().any(|c| c == "estimated_charge_micro_usd");
if has_legacy && !has_current {
sqlx::query(
"ALTER TABLE requests RENAME COLUMN final_charge_micro_usd \
TO estimated_charge_micro_usd",
)
.execute(pool)
.await
.map_err(|e| BitrouterError::internal(format!("metering migration rename: {e}")))?;
tracing::info!(
"metering: renamed legacy `requests.final_charge_micro_usd` → \
`estimated_charge_micro_usd`"
);
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct RequestMetric {
pub request_id: String,
pub user_id: String,
pub api_key_id: String,
pub model_id: String,
pub provider_id: String,
pub prompt_tokens: u64,
pub completion_tokens: u64,
pub reasoning_tokens: u64,
pub cache_read_tokens: u64,
pub cache_write_tokens: u64,
pub estimated_charge_micro_usd: i64,
pub latency_ms: u64,
pub generation_time_ms: u64,
pub streamed: bool,
pub error: Option<String>,
}