chore: resolve pr 377 checks

This commit is contained in:
fawney19
2026-05-06 02:22:32 +08:00
502 changed files with 89553 additions and 21893 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
ALTER TABLE provider_endpoints
ADD COLUMN IF NOT EXISTS health_score DOUBLE PRECISION NOT NULL DEFAULT 1.0;

View File

@@ -0,0 +1,124 @@
-- Align Rust-managed migrations with legacy Alembic revision
-- c3d4e5f6a7b8 (usage_token_semantics_v2).
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'usage'
AND column_name = 'total_tokens'
) AND NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'usage'
AND column_name = 'input_output_total_tokens'
) THEN
ALTER TABLE "usage" RENAME COLUMN total_tokens TO input_output_total_tokens;
END IF;
END $$;
ALTER TABLE "usage"
ADD COLUMN IF NOT EXISTS input_output_total_tokens INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS cache_creation_input_tokens_5m INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS cache_creation_input_tokens_1h INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS input_context_tokens INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS total_tokens INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS cache_creation_cost_usd_5m NUMERIC(20, 8) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS cache_creation_cost_usd_1h NUMERIC(20, 8) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS actual_cache_creation_cost_usd_5m NUMERIC(20, 8) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS actual_cache_creation_cost_usd_1h NUMERIC(20, 8) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS actual_cache_cost_usd NUMERIC(20, 8) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS cache_creation_price_per_1m_5m NUMERIC(20, 8),
ADD COLUMN IF NOT EXISTS cache_creation_price_per_1m_1h NUMERIC(20, 8);
UPDATE "usage"
SET
input_output_total_tokens = src.new_iot,
input_context_tokens = src.new_ict,
total_tokens = src.new_total,
cache_creation_cost_usd_5m = src.new_cc5m,
cache_creation_cost_usd_1h = src.new_cc1h,
actual_cache_creation_cost_usd_5m = src.new_acc5m,
actual_cache_creation_cost_usd_1h = src.new_acc1h,
actual_cache_cost_usd = src.new_accu,
cache_creation_price_per_1m_5m = src.new_cp5m,
cache_creation_price_per_1m_1h = src.new_cp1h,
cache_cost_usd = src.new_ccu
FROM (
SELECT
id,
COALESCE(
input_output_total_tokens,
COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)
) AS new_iot,
COALESCE(input_tokens, 0) + COALESCE(cache_read_input_tokens, 0) AS new_ict,
COALESCE(
input_output_total_tokens,
COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)
) + COALESCE(cache_creation_input_tokens, 0) + COALESCE(cache_read_input_tokens, 0) AS new_total,
CASE
WHEN COALESCE(cache_creation_input_tokens_5m, 0) > 0
AND COALESCE(cache_creation_input_tokens_1h, 0) = 0
THEN COALESCE(cache_creation_cost_usd, 0)
WHEN COALESCE(cache_creation_input_tokens_5m, 0) > 0
AND COALESCE(cache_creation_input_tokens, 0) > 0
THEN COALESCE(cache_creation_cost_usd, 0)
* (COALESCE(cache_creation_input_tokens_5m, 0) * 1.0
/ GREATEST(COALESCE(cache_creation_input_tokens, 0), 1))
ELSE 0
END AS new_cc5m,
CASE
WHEN COALESCE(cache_creation_input_tokens_1h, 0) > 0
AND COALESCE(cache_creation_input_tokens_5m, 0) = 0
THEN COALESCE(cache_creation_cost_usd, 0)
WHEN COALESCE(cache_creation_input_tokens_1h, 0) > 0
AND COALESCE(cache_creation_input_tokens, 0) > 0
THEN COALESCE(cache_creation_cost_usd, 0)
* (COALESCE(cache_creation_input_tokens_1h, 0) * 1.0
/ GREATEST(COALESCE(cache_creation_input_tokens, 0), 1))
ELSE 0
END AS new_cc1h,
CASE
WHEN COALESCE(cache_creation_input_tokens_5m, 0) > 0
AND COALESCE(cache_creation_input_tokens_1h, 0) = 0
THEN COALESCE(actual_cache_creation_cost_usd, 0)
WHEN COALESCE(cache_creation_input_tokens_5m, 0) > 0
AND COALESCE(cache_creation_input_tokens, 0) > 0
THEN COALESCE(actual_cache_creation_cost_usd, 0)
* (COALESCE(cache_creation_input_tokens_5m, 0) * 1.0
/ GREATEST(COALESCE(cache_creation_input_tokens, 0), 1))
ELSE 0
END AS new_acc5m,
CASE
WHEN COALESCE(cache_creation_input_tokens_1h, 0) > 0
AND COALESCE(cache_creation_input_tokens_5m, 0) = 0
THEN COALESCE(actual_cache_creation_cost_usd, 0)
WHEN COALESCE(cache_creation_input_tokens_1h, 0) > 0
AND COALESCE(cache_creation_input_tokens, 0) > 0
THEN COALESCE(actual_cache_creation_cost_usd, 0)
* (COALESCE(cache_creation_input_tokens_1h, 0) * 1.0
/ GREATEST(COALESCE(cache_creation_input_tokens, 0), 1))
ELSE 0
END AS new_acc1h,
COALESCE(actual_cache_creation_cost_usd, 0)
+ COALESCE(actual_cache_read_cost_usd, 0) AS new_accu,
CASE
WHEN COALESCE(cache_creation_input_tokens_5m, 0) > 0
AND COALESCE(cache_creation_input_tokens_1h, 0) = 0
THEN cache_creation_price_per_1m
ELSE NULL
END AS new_cp5m,
CASE
WHEN COALESCE(cache_creation_input_tokens_1h, 0) > 0
AND COALESCE(cache_creation_input_tokens_5m, 0) = 0
THEN cache_creation_price_per_1m
ELSE NULL
END AS new_cp1h,
COALESCE(cache_creation_cost_usd, 0)
+ COALESCE(cache_read_cost_usd, 0) AS new_ccu
FROM "usage"
) AS src
WHERE "usage".id = src.id;

View File

@@ -0,0 +1,151 @@
-- Squashed unreleased usage schema split:
-- 20260412000000_add_usage_body_blobs.sql
-- 20260412010000_add_usage_http_audits.sql
-- 20260412020000_add_usage_routing_snapshots.sql
-- 20260412030000_add_usage_settlement_snapshots.sql
-- 20260413000000_expand_usage_settlement_snapshots_for_pricing.sql
-- 20260413010000_mark_usage_legacy_columns_deprecated.sql
-- 20260413020000_add_candidate_index_to_usage_routing_snapshots.sql
CREATE TABLE IF NOT EXISTS public.usage_body_blobs (
body_ref character varying(160) NOT NULL,
request_id character varying(100) NOT NULL,
body_field character varying(50) NOT NULL,
payload_gzip bytea NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT usage_body_blobs_pkey PRIMARY KEY (body_ref),
CONSTRAINT usage_body_blobs_request_id_field_key UNIQUE (request_id, body_field),
CONSTRAINT usage_body_blobs_request_id_fkey
FOREIGN KEY (request_id)
REFERENCES public.usage(request_id)
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS ix_usage_body_blobs_request_id
ON public.usage_body_blobs USING btree (request_id);
CREATE TABLE IF NOT EXISTS public.usage_http_audits (
request_id character varying(100) NOT NULL,
request_headers json,
provider_request_headers json,
response_headers json,
client_response_headers json,
request_body_ref character varying(160),
provider_request_body_ref character varying(160),
response_body_ref character varying(160),
client_response_body_ref character varying(160),
request_body_state character varying(32),
provider_request_body_state character varying(32),
response_body_state character varying(32),
client_response_body_state character varying(32),
body_capture_mode character varying(32) DEFAULT 'none' NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT usage_http_audits_pkey PRIMARY KEY (request_id),
CONSTRAINT usage_http_audits_request_id_fkey
FOREIGN KEY (request_id)
REFERENCES public.usage(request_id)
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS ix_usage_http_audits_updated_at
ON public.usage_http_audits USING btree (updated_at);
CREATE TABLE IF NOT EXISTS public.usage_routing_snapshots (
request_id character varying(100) NOT NULL,
candidate_id character varying(160),
candidate_index integer,
key_name character varying(255),
planner_kind character varying(120),
route_family character varying(80),
route_kind character varying(80),
execution_path character varying(80),
local_execution_runtime_miss_reason character varying(120),
selected_provider_id character varying(100),
selected_endpoint_id character varying(100),
selected_provider_api_key_id character varying(100),
has_format_conversion boolean,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT usage_routing_snapshots_pkey PRIMARY KEY (request_id),
CONSTRAINT usage_routing_snapshots_request_id_fkey
FOREIGN KEY (request_id)
REFERENCES public.usage(request_id)
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS ix_usage_routing_snapshots_route_family_kind
ON public.usage_routing_snapshots USING btree (route_family, route_kind);
CREATE INDEX IF NOT EXISTS ix_usage_routing_snapshots_candidate_id
ON public.usage_routing_snapshots USING btree (candidate_id);
CREATE TABLE IF NOT EXISTS public.usage_settlement_snapshots (
request_id character varying(100) NOT NULL,
billing_status character varying(20) NOT NULL,
wallet_id character varying(36),
wallet_balance_before numeric(20,8),
wallet_balance_after numeric(20,8),
wallet_recharge_balance_before numeric(20,8),
wallet_recharge_balance_after numeric(20,8),
wallet_gift_balance_before numeric(20,8),
wallet_gift_balance_after numeric(20,8),
provider_monthly_used_usd numeric(20,8),
finalized_at timestamp with time zone,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT usage_settlement_snapshots_pkey PRIMARY KEY (request_id),
CONSTRAINT usage_settlement_snapshots_request_id_fkey
FOREIGN KEY (request_id)
REFERENCES public.usage(request_id)
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS ix_usage_settlement_snapshots_wallet_id
ON public.usage_settlement_snapshots USING btree (wallet_id);
CREATE INDEX IF NOT EXISTS ix_usage_settlement_snapshots_billing_status
ON public.usage_settlement_snapshots USING btree (billing_status);
ALTER TABLE IF EXISTS public.usage_settlement_snapshots
ADD COLUMN IF NOT EXISTS billing_snapshot_schema_version character varying(20),
ADD COLUMN IF NOT EXISTS billing_snapshot_status character varying(20),
ADD COLUMN IF NOT EXISTS rate_multiplier numeric(10,6),
ADD COLUMN IF NOT EXISTS is_free_tier boolean,
ADD COLUMN IF NOT EXISTS input_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_read_price_per_1m numeric(20,8),
ADD COLUMN IF NOT EXISTS price_per_request numeric(20,8);
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.';
COMMENT ON COLUMN public.usage.wallet_balance_before IS
'DEPRECATED: settlement owner moved to public.usage_settlement_snapshots.wallet_balance_before. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.wallet_balance_after IS
'DEPRECATED: settlement owner moved to public.usage_settlement_snapshots.wallet_balance_after. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.wallet_recharge_balance_before IS
'DEPRECATED: settlement owner moved to public.usage_settlement_snapshots.wallet_recharge_balance_before. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.wallet_recharge_balance_after IS
'DEPRECATED: settlement owner moved to public.usage_settlement_snapshots.wallet_recharge_balance_after. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.wallet_gift_balance_before IS
'DEPRECATED: settlement owner moved to public.usage_settlement_snapshots.wallet_gift_balance_before. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.wallet_gift_balance_after IS
'DEPRECATED: settlement owner moved to public.usage_settlement_snapshots.wallet_gift_balance_after. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.rate_multiplier IS
'DEPRECATED: settlement pricing owner moved to public.usage_settlement_snapshots.rate_multiplier. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.input_price_per_1m IS
'DEPRECATED: settlement pricing owner moved to public.usage_settlement_snapshots.input_price_per_1m. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.output_price_per_1m IS
'DEPRECATED: settlement pricing owner moved to public.usage_settlement_snapshots.output_price_per_1m. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.cache_creation_price_per_1m IS
'DEPRECATED: settlement pricing owner moved to public.usage_settlement_snapshots.cache_creation_price_per_1m. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.cache_read_price_per_1m IS
'DEPRECATED: settlement pricing owner moved to public.usage_settlement_snapshots.cache_read_price_per_1m. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.price_per_request IS
'DEPRECATED: settlement pricing owner moved to public.usage_settlement_snapshots.price_per_request. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.username IS
'DEPRECATED: display cache only. Prefer join-time lookup from user/auth records. Legacy compatibility only.';
COMMENT ON COLUMN public.usage.api_key_name IS
'DEPRECATED: display cache only. Prefer join-time lookup from API key records. Legacy compatibility only.';

View File

@@ -0,0 +1,28 @@
COMMENT ON COLUMN public.usage.billing_status IS
'DEPRECATED: authoritative owner moved to public.usage_settlement_snapshots.billing_status. Compatibility/index mirror only; do not write new values.';
COMMENT ON COLUMN public.usage.finalized_at IS
'DEPRECATED: authoritative owner moved to public.usage_settlement_snapshots.finalized_at. Compatibility/index mirror only; do not write new values.';
COMMENT ON COLUMN public.usage.request_headers IS
'DEPRECATED: HTTP audit owner moved to public.usage_http_audits.request_headers. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.provider_request_headers IS
'DEPRECATED: HTTP audit owner moved to public.usage_http_audits.provider_request_headers. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.response_headers IS
'DEPRECATED: HTTP audit owner moved to public.usage_http_audits.response_headers. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.client_response_headers IS
'DEPRECATED: HTTP audit owner moved to public.usage_http_audits.client_response_headers. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.request_body IS
'DEPRECATED: HTTP body owner moved to public.usage_body_blobs plus public.usage_http_audits.request_body_ref. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.request_body_compressed IS
'DEPRECATED: HTTP body owner moved to public.usage_body_blobs plus public.usage_http_audits.request_body_ref. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.provider_request_body IS
'DEPRECATED: HTTP body owner moved to public.usage_body_blobs plus public.usage_http_audits.provider_request_body_ref. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.provider_request_body_compressed IS
'DEPRECATED: HTTP body owner moved to public.usage_body_blobs plus public.usage_http_audits.provider_request_body_ref. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.response_body IS
'DEPRECATED: HTTP body owner moved to public.usage_body_blobs plus public.usage_http_audits.response_body_ref. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.response_body_compressed IS
'DEPRECATED: HTTP body owner moved to public.usage_body_blobs plus public.usage_http_audits.response_body_ref. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.client_response_body IS
'DEPRECATED: HTTP body owner moved to public.usage_body_blobs plus public.usage_http_audits.client_response_body_ref. Legacy compatibility only; do not write new values.';
COMMENT ON COLUMN public.usage.client_response_body_compressed IS
'DEPRECATED: HTTP body owner moved to public.usage_body_blobs plus public.usage_http_audits.client_response_body_ref. Legacy compatibility only; do not write new values.';

View File

@@ -0,0 +1,116 @@
CREATE TABLE IF NOT EXISTS public.redeem_code_batches (
id character varying(36) NOT NULL,
name character varying(120) NOT NULL,
amount_usd numeric(20,8) NOT NULL,
currency character varying(3) DEFAULT 'USD'::character varying NOT NULL,
balance_bucket character varying(20) DEFAULT 'gift'::character varying NOT NULL,
total_count integer NOT NULL,
status character varying(20) DEFAULT 'active'::character varying NOT NULL,
description text,
created_by character varying(36),
expires_at timestamp with time zone,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
CONSTRAINT ck_redeem_code_batches_amount_positive CHECK ((amount_usd > (0)::numeric)),
CONSTRAINT ck_redeem_code_batches_total_count_positive CHECK ((total_count > 0))
);
CREATE TABLE IF NOT EXISTS public.redeem_codes (
id character varying(36) NOT NULL,
batch_id character varying(36) NOT NULL,
code_hash character varying(64) NOT NULL,
code_prefix character varying(8) NOT NULL,
code_suffix character varying(8) NOT NULL,
status character varying(20) DEFAULT 'active'::character varying NOT NULL,
redeemed_by_user_id character varying(36),
redeemed_wallet_id character varying(36),
redeemed_payment_order_id character varying(36),
redeemed_at timestamp with time zone,
disabled_by character varying(36),
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
DO $mig$ BEGIN
ALTER TABLE ONLY public.redeem_code_batches
ADD CONSTRAINT redeem_code_batches_pkey PRIMARY KEY (id);
EXCEPTION
WHEN duplicate_object THEN NULL;
WHEN duplicate_table THEN NULL;
WHEN invalid_table_definition THEN NULL;
END $mig$;
DO $mig$ BEGIN
ALTER TABLE ONLY public.redeem_codes
ADD CONSTRAINT redeem_codes_pkey PRIMARY KEY (id);
EXCEPTION
WHEN duplicate_object THEN NULL;
WHEN duplicate_table THEN NULL;
WHEN invalid_table_definition THEN NULL;
END $mig$;
DO $mig$ BEGIN
ALTER TABLE ONLY public.redeem_codes
ADD CONSTRAINT uq_redeem_codes_code_hash UNIQUE (code_hash);
EXCEPTION
WHEN duplicate_object THEN NULL;
WHEN duplicate_table THEN NULL;
WHEN invalid_table_definition THEN NULL;
END $mig$;
CREATE INDEX IF NOT EXISTS idx_redeem_code_batches_status
ON public.redeem_code_batches USING btree (status, created_at);
CREATE INDEX IF NOT EXISTS idx_redeem_codes_batch_created
ON public.redeem_codes USING btree (batch_id, created_at);
CREATE INDEX IF NOT EXISTS idx_redeem_codes_status
ON public.redeem_codes USING btree (status, updated_at);
CREATE INDEX IF NOT EXISTS idx_redeem_codes_redeemed_user
ON public.redeem_codes USING btree (redeemed_by_user_id, redeemed_at);
CREATE INDEX IF NOT EXISTS idx_redeem_codes_redeemed_order
ON public.redeem_codes USING btree (redeemed_payment_order_id);
DO $mig$ BEGIN
ALTER TABLE ONLY public.redeem_code_batches
ADD CONSTRAINT redeem_code_batches_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE SET NULL;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $mig$;
DO $mig$ BEGIN
ALTER TABLE ONLY public.redeem_codes
ADD CONSTRAINT redeem_codes_batch_id_fkey FOREIGN KEY (batch_id) REFERENCES public.redeem_code_batches(id) ON DELETE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $mig$;
DO $mig$ BEGIN
ALTER TABLE ONLY public.redeem_codes
ADD CONSTRAINT redeem_codes_redeemed_by_user_id_fkey FOREIGN KEY (redeemed_by_user_id) REFERENCES public.users(id) ON DELETE SET NULL;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $mig$;
DO $mig$ BEGIN
ALTER TABLE ONLY public.redeem_codes
ADD CONSTRAINT redeem_codes_redeemed_wallet_id_fkey FOREIGN KEY (redeemed_wallet_id) REFERENCES public.wallets(id) ON DELETE SET NULL;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $mig$;
DO $mig$ BEGIN
ALTER TABLE ONLY public.redeem_codes
ADD CONSTRAINT redeem_codes_redeemed_payment_order_id_fkey FOREIGN KEY (redeemed_payment_order_id) REFERENCES public.payment_orders(id) ON DELETE SET NULL;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $mig$;
DO $mig$ BEGIN
ALTER TABLE ONLY public.redeem_codes
ADD CONSTRAINT redeem_codes_disabled_by_fkey FOREIGN KEY (disabled_by) REFERENCES public.users(id) ON DELETE SET NULL;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $mig$;

View File

@@ -0,0 +1,5 @@
ALTER TABLE public.usage_http_audits
ADD COLUMN IF NOT EXISTS request_body_state character varying(32),
ADD COLUMN IF NOT EXISTS provider_request_body_state character varying(32),
ADD COLUMN IF NOT EXISTS response_body_state character varying(32),
ADD COLUMN IF NOT EXISTS client_response_body_state character varying(32);

View File

@@ -0,0 +1,127 @@
ALTER TABLE public.stats_user_daily
ADD COLUMN IF NOT EXISTS actual_total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS response_time_samples bigint DEFAULT 0 NOT NULL;
ALTER TABLE public.stats_hourly_user
ADD COLUMN IF NOT EXISTS cache_creation_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS actual_total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS response_time_samples bigint DEFAULT 0 NOT NULL;
CREATE TABLE IF NOT EXISTS public.stats_user_summary (
id character varying(36) NOT NULL,
user_id character varying(36) NOT NULL,
username character varying(100),
cutoff_date timestamp with time zone NOT NULL,
all_time_requests integer DEFAULT 0 NOT NULL,
all_time_success_requests integer DEFAULT 0 NOT NULL,
all_time_error_requests integer DEFAULT 0 NOT NULL,
all_time_input_tokens bigint DEFAULT '0'::bigint NOT NULL,
all_time_output_tokens bigint DEFAULT '0'::bigint NOT NULL,
all_time_cache_creation_tokens bigint DEFAULT '0'::bigint NOT NULL,
all_time_cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
all_time_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
all_time_actual_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
active_days integer DEFAULT 0 NOT NULL,
first_active_date timestamp with time zone,
last_active_date timestamp with time zone,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_user_summary_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_user_summary_user_id UNIQUE (user_id),
CONSTRAINT stats_user_summary_user_id_fkey
FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_stats_user_summary_cutoff_date
ON public.stats_user_summary USING btree (cutoff_date);
ALTER TABLE public.stats_daily
ADD COLUMN IF NOT EXISTS effective_input_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS total_input_context bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS response_time_samples bigint DEFAULT 0 NOT NULL;
ALTER TABLE public.stats_user_daily
ADD COLUMN IF NOT EXISTS effective_input_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS total_input_context bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL;
ALTER TABLE public.stats_daily_model
ADD COLUMN IF NOT EXISTS response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS response_time_samples bigint DEFAULT 0 NOT NULL;
CREATE TABLE IF NOT EXISTS public.stats_user_daily_model (
id character varying(36) NOT NULL,
user_id character varying(36) NOT NULL,
username character varying(100),
date timestamp with time zone NOT NULL,
model character varying(100) NOT NULL,
total_requests integer DEFAULT 0 NOT NULL,
input_tokens bigint DEFAULT '0'::bigint NOT NULL,
output_tokens bigint DEFAULT '0'::bigint NOT NULL,
total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
response_time_samples bigint DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_user_daily_model_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_user_daily_model_user_date_model UNIQUE (user_id, date, model),
CONSTRAINT stats_user_daily_model_user_id_fkey
FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_model_date
ON public.stats_user_daily_model USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_model_user_id
ON public.stats_user_daily_model USING btree (user_id);
ALTER TABLE public.stats_hourly
ADD COLUMN IF NOT EXISTS response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS response_time_samples bigint DEFAULT 0 NOT NULL;
ALTER TABLE public.stats_hourly_model
ADD COLUMN IF NOT EXISTS response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS response_time_samples bigint DEFAULT 0 NOT NULL;
CREATE TABLE IF NOT EXISTS public.stats_hourly_user_model (
id character varying(36) NOT NULL,
hour_utc timestamp with time zone NOT NULL,
user_id character varying(36) NOT NULL,
model character varying(100) NOT NULL,
total_requests integer DEFAULT 0 NOT NULL,
input_tokens bigint DEFAULT '0'::bigint NOT NULL,
output_tokens bigint DEFAULT '0'::bigint NOT NULL,
total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
response_time_samples bigint DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_hourly_user_model_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_hourly_user_model UNIQUE (hour_utc, user_id, model),
CONSTRAINT stats_hourly_user_model_user_id_fkey
FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_stats_hourly_user_model_hour
ON public.stats_hourly_user_model USING btree (hour_utc);
CREATE INDEX IF NOT EXISTS idx_stats_hourly_user_model_user_hour
ON public.stats_hourly_user_model USING btree (user_id, hour_utc);
CREATE TABLE IF NOT EXISTS public.schema_backfills (
version bigint NOT NULL,
description text NOT NULL,
success boolean NOT NULL DEFAULT TRUE,
checksum bytea NOT NULL,
execution_time bigint NOT NULL DEFAULT 0,
applied_at timestamp with time zone NOT NULL DEFAULT now(),
CONSTRAINT schema_backfills_pkey PRIMARY KEY (version)
);
CREATE INDEX IF NOT EXISTS idx_schema_backfills_applied_at
ON public.schema_backfills USING btree (applied_at DESC);

View File

@@ -0,0 +1,377 @@
ALTER TABLE public.stats_user_daily_model
ADD COLUMN IF NOT EXISTS success_requests integer DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS effective_input_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS total_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS total_input_context bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS cache_creation_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS cache_creation_ephemeral_5m_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS cache_creation_ephemeral_1h_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS actual_total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS successful_response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS successful_response_time_samples bigint DEFAULT 0 NOT NULL;
CREATE TABLE IF NOT EXISTS public.stats_user_daily_provider (
id character varying(36) NOT NULL,
user_id character varying(36) NOT NULL,
username character varying(100),
date timestamp with time zone NOT NULL,
provider_name character varying(100) NOT NULL,
total_requests integer DEFAULT 0 NOT NULL,
success_requests integer DEFAULT 0 NOT NULL,
input_tokens bigint DEFAULT '0'::bigint NOT NULL,
effective_input_tokens bigint DEFAULT '0'::bigint NOT NULL,
output_tokens bigint DEFAULT '0'::bigint NOT NULL,
total_tokens bigint DEFAULT '0'::bigint NOT NULL,
total_input_context bigint DEFAULT '0'::bigint NOT NULL,
cache_creation_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_creation_ephemeral_5m_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_creation_ephemeral_1h_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
actual_total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
response_time_samples bigint DEFAULT 0 NOT NULL,
successful_response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
successful_response_time_samples bigint DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_user_daily_provider_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_user_daily_provider_user_date_provider UNIQUE (user_id, date, provider_name),
CONSTRAINT stats_user_daily_provider_user_id_fkey
FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_provider_date
ON public.stats_user_daily_provider USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_provider_user_id
ON public.stats_user_daily_provider USING btree (user_id);
CREATE TABLE IF NOT EXISTS public.stats_user_daily_api_format (
id character varying(36) NOT NULL,
user_id character varying(36) NOT NULL,
username character varying(100),
date timestamp with time zone NOT NULL,
api_format character varying(50) NOT NULL,
total_requests integer DEFAULT 0 NOT NULL,
success_requests integer DEFAULT 0 NOT NULL,
input_tokens bigint DEFAULT '0'::bigint NOT NULL,
effective_input_tokens bigint DEFAULT '0'::bigint NOT NULL,
output_tokens bigint DEFAULT '0'::bigint NOT NULL,
total_tokens bigint DEFAULT '0'::bigint NOT NULL,
total_input_context bigint DEFAULT '0'::bigint NOT NULL,
cache_creation_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_creation_ephemeral_5m_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_creation_ephemeral_1h_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
actual_total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
response_time_samples bigint DEFAULT 0 NOT NULL,
successful_response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
successful_response_time_samples bigint DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_user_daily_api_format_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_user_daily_api_format_user_date_api_format UNIQUE (user_id, date, api_format),
CONSTRAINT stats_user_daily_api_format_user_id_fkey
FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_api_format_date
ON public.stats_user_daily_api_format USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_api_format_user_id
ON public.stats_user_daily_api_format USING btree (user_id);
CREATE TABLE IF NOT EXISTS public.stats_daily_model_provider (
id character varying(36) NOT NULL,
date timestamp with time zone NOT NULL,
model character varying(100) NOT NULL,
provider_name character varying(100) NOT NULL,
total_requests integer DEFAULT 0 NOT NULL,
total_tokens bigint DEFAULT '0'::bigint NOT NULL,
total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
response_time_samples bigint DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_daily_model_provider_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_daily_model_provider UNIQUE (date, model, provider_name)
);
CREATE INDEX IF NOT EXISTS idx_stats_daily_model_provider_date
ON public.stats_daily_model_provider USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_daily_model_provider_date_model_provider
ON public.stats_daily_model_provider USING btree (date, model, provider_name);
CREATE TABLE IF NOT EXISTS public.stats_user_daily_model_provider (
id character varying(36) NOT NULL,
user_id character varying(36) NOT NULL,
username character varying(100),
date timestamp with time zone NOT NULL,
model character varying(100) NOT NULL,
provider_name character varying(100) NOT NULL,
total_requests integer DEFAULT 0 NOT NULL,
total_tokens bigint DEFAULT '0'::bigint NOT NULL,
total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
response_time_sum_ms double precision DEFAULT '0'::double precision NOT NULL,
response_time_samples bigint DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_user_daily_model_provider_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_user_daily_model_provider UNIQUE (user_id, date, model, provider_name),
CONSTRAINT stats_user_daily_model_provider_user_id_fkey
FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_model_provider_date
ON public.stats_user_daily_model_provider USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_model_provider_user_date
ON public.stats_user_daily_model_provider USING btree (user_id, date);
ALTER TABLE public.stats_daily
ADD COLUMN IF NOT EXISTS cache_creation_ephemeral_5m_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS cache_creation_ephemeral_1h_tokens bigint DEFAULT '0'::bigint NOT NULL;
ALTER TABLE public.stats_user_daily
ADD COLUMN IF NOT EXISTS cache_creation_ephemeral_5m_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS cache_creation_ephemeral_1h_tokens bigint DEFAULT '0'::bigint NOT NULL;
ALTER TABLE public.stats_daily
ADD COLUMN IF NOT EXISTS cache_hit_total_requests bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS cache_hit_requests bigint DEFAULT 0 NOT NULL;
ALTER TABLE public.stats_hourly
ADD COLUMN IF NOT EXISTS cache_hit_total_requests bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS cache_hit_requests bigint DEFAULT 0 NOT NULL;
ALTER TABLE public.stats_daily
ADD COLUMN IF NOT EXISTS completed_total_requests bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_cache_hit_requests bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_input_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_cache_creation_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_cache_read_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_total_input_context bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS completed_cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL;
ALTER TABLE public.stats_hourly
ADD COLUMN IF NOT EXISTS completed_total_requests bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_cache_hit_requests bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_input_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_cache_creation_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_cache_read_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_total_input_context bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS completed_cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS completed_cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL;
ALTER TABLE public.stats_daily
ADD COLUMN IF NOT EXISTS settled_total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS settled_total_requests bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_input_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_output_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_cache_creation_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_cache_read_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_first_finalized_at_unix_secs bigint,
ADD COLUMN IF NOT EXISTS settled_last_finalized_at_unix_secs bigint;
ALTER TABLE public.stats_hourly
ADD COLUMN IF NOT EXISTS settled_total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS settled_total_requests bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_input_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_output_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_cache_creation_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_cache_read_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_first_finalized_at_unix_secs bigint,
ADD COLUMN IF NOT EXISTS settled_last_finalized_at_unix_secs bigint;
ALTER TABLE public.stats_user_daily
ADD COLUMN IF NOT EXISTS settled_total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS settled_total_requests bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_input_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_output_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_cache_creation_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_cache_read_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_first_finalized_at_unix_secs bigint,
ADD COLUMN IF NOT EXISTS settled_last_finalized_at_unix_secs bigint;
ALTER TABLE public.stats_hourly_user
ADD COLUMN IF NOT EXISTS settled_total_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
ADD COLUMN IF NOT EXISTS settled_total_requests bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_input_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_output_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_cache_creation_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_cache_read_tokens bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS settled_first_finalized_at_unix_secs bigint,
ADD COLUMN IF NOT EXISTS settled_last_finalized_at_unix_secs bigint;
CREATE TABLE IF NOT EXISTS public.stats_daily_cost_savings (
id character varying(36) NOT NULL,
date timestamp with time zone NOT NULL,
cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
estimated_full_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_daily_cost_savings_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_daily_cost_savings_date UNIQUE (date)
);
CREATE INDEX IF NOT EXISTS idx_stats_daily_cost_savings_date
ON public.stats_daily_cost_savings USING btree (date);
CREATE TABLE IF NOT EXISTS public.stats_daily_cost_savings_provider (
id character varying(36) NOT NULL,
date timestamp with time zone NOT NULL,
provider_name character varying(100) NOT NULL,
cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
estimated_full_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_daily_cost_savings_provider_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_daily_cost_savings_provider UNIQUE (date, provider_name)
);
CREATE INDEX IF NOT EXISTS idx_stats_daily_cost_savings_provider_date
ON public.stats_daily_cost_savings_provider USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_daily_cost_savings_provider_date_provider
ON public.stats_daily_cost_savings_provider USING btree (date, provider_name);
CREATE TABLE IF NOT EXISTS public.stats_daily_cost_savings_model (
id character varying(36) NOT NULL,
date timestamp with time zone NOT NULL,
model character varying(100) NOT NULL,
cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
estimated_full_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_daily_cost_savings_model_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_daily_cost_savings_model UNIQUE (date, model)
);
CREATE INDEX IF NOT EXISTS idx_stats_daily_cost_savings_model_date
ON public.stats_daily_cost_savings_model USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_daily_cost_savings_model_date_model
ON public.stats_daily_cost_savings_model USING btree (date, model);
CREATE TABLE IF NOT EXISTS public.stats_daily_cost_savings_model_provider (
id character varying(36) NOT NULL,
date timestamp with time zone NOT NULL,
model character varying(100) NOT NULL,
provider_name character varying(100) NOT NULL,
cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
estimated_full_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_daily_cost_savings_model_provider_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_daily_cost_savings_model_provider UNIQUE (date, model, provider_name)
);
CREATE INDEX IF NOT EXISTS idx_stats_daily_cost_savings_model_provider_date
ON public.stats_daily_cost_savings_model_provider USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_daily_cost_savings_model_provider_date_dims
ON public.stats_daily_cost_savings_model_provider USING btree (date, model, provider_name);
CREATE TABLE IF NOT EXISTS public.stats_user_daily_cost_savings (
id character varying(36) NOT NULL,
user_id character varying(36) NOT NULL,
username character varying(100),
date timestamp with time zone NOT NULL,
cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
estimated_full_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_user_daily_cost_savings_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_user_daily_cost_savings UNIQUE (user_id, date),
CONSTRAINT stats_user_daily_cost_savings_user_id_fkey
FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_cost_savings_date
ON public.stats_user_daily_cost_savings USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_cost_savings_user_date
ON public.stats_user_daily_cost_savings USING btree (user_id, date);
CREATE TABLE IF NOT EXISTS public.stats_user_daily_cost_savings_provider (
id character varying(36) NOT NULL,
user_id character varying(36) NOT NULL,
username character varying(100),
date timestamp with time zone NOT NULL,
provider_name character varying(100) NOT NULL,
cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
estimated_full_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_user_daily_cost_savings_provider_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_user_daily_cost_savings_provider UNIQUE (user_id, date, provider_name),
CONSTRAINT stats_user_daily_cost_savings_provider_user_id_fkey
FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_cost_savings_provider_date
ON public.stats_user_daily_cost_savings_provider USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_cost_savings_provider_user_date
ON public.stats_user_daily_cost_savings_provider USING btree (user_id, date);
CREATE TABLE IF NOT EXISTS public.stats_user_daily_cost_savings_model (
id character varying(36) NOT NULL,
user_id character varying(36) NOT NULL,
username character varying(100),
date timestamp with time zone NOT NULL,
model character varying(100) NOT NULL,
cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
estimated_full_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_user_daily_cost_savings_model_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_user_daily_cost_savings_model UNIQUE (user_id, date, model),
CONSTRAINT stats_user_daily_cost_savings_model_user_id_fkey
FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_cost_savings_model_date
ON public.stats_user_daily_cost_savings_model USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_cost_savings_model_user_date
ON public.stats_user_daily_cost_savings_model USING btree (user_id, date);
CREATE TABLE IF NOT EXISTS public.stats_user_daily_cost_savings_model_provider (
id character varying(36) NOT NULL,
user_id character varying(36) NOT NULL,
username character varying(100),
date timestamp with time zone NOT NULL,
model character varying(100) NOT NULL,
provider_name character varying(100) NOT NULL,
cache_read_tokens bigint DEFAULT '0'::bigint NOT NULL,
cache_read_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
cache_creation_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
estimated_full_cost numeric(20,8) DEFAULT '0'::double precision NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT stats_user_daily_cost_savings_model_provider_pkey PRIMARY KEY (id),
CONSTRAINT uq_stats_user_daily_cost_savings_model_provider
UNIQUE (user_id, date, model, provider_name),
CONSTRAINT stats_user_daily_cost_savings_model_provider_user_id_fkey
FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_cost_savings_model_provider_date
ON public.stats_user_daily_cost_savings_model_provider USING btree (date);
CREATE INDEX IF NOT EXISTS idx_stats_user_daily_cost_savings_model_provider_user_date
ON public.stats_user_daily_cost_savings_model_provider USING btree (user_id, date);

View File

@@ -0,0 +1,8 @@
ALTER TABLE public.api_keys
ADD COLUMN IF NOT EXISTS total_tokens bigint DEFAULT '0'::bigint NOT NULL;
UPDATE public.provider_api_keys
SET
fingerprint = NULL,
updated_at = NOW()
WHERE fingerprint IS NOT NULL;

View File

@@ -0,0 +1,38 @@
ALTER TABLE public.provider_api_keys
ALTER COLUMN api_formats DROP DEFAULT,
ALTER COLUMN api_formats DROP NOT NULL;
UPDATE public.provider_api_keys AS pak
SET
api_formats = NULL,
updated_at = NOW()
FROM public.providers AS p
WHERE p.id = pak.provider_id
AND pak.api_formats IS NOT NULL
AND pak.api_formats::jsonb = '[]'::jsonb
AND (
(
LOWER(BTRIM(p.provider_type)) IN (
'claude_code',
'codex',
'gemini_cli',
'vertex_ai',
'antigravity'
)
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
)
OR (
LOWER(BTRIM(p.provider_type)) = 'kiro'
AND (
LOWER(BTRIM(pak.auth_type)) = 'oauth'
OR (
LOWER(BTRIM(pak.auth_type)) = 'bearer'
AND COALESCE(BTRIM(pak.auth_config), '') <> ''
)
)
)
);
ALTER TABLE public.stats_daily_model
ADD COLUMN IF NOT EXISTS cache_creation_ephemeral_5m_tokens bigint DEFAULT '0'::bigint NOT NULL,
ADD COLUMN IF NOT EXISTS cache_creation_ephemeral_1h_tokens bigint DEFAULT '0'::bigint NOT NULL;

View File

@@ -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.';

View File

@@ -0,0 +1,500 @@
CREATE OR REPLACE FUNCTION public.aether_canonical_api_format_alias(value text)
RETURNS text
LANGUAGE sql
IMMUTABLE
AS $$
SELECT CASE LOWER(BTRIM(COALESCE(value, '')))
WHEN 'openai:cli' THEN 'openai:responses'
WHEN 'openai:compact' THEN 'openai:responses:compact'
WHEN 'claude:chat' THEN 'claude:messages'
WHEN 'claude:cli' THEN 'claude:messages'
WHEN 'gemini:chat' THEN 'gemini:generate_content'
WHEN 'gemini:cli' THEN 'gemini:generate_content'
ELSE LOWER(BTRIM(COALESCE(value, '')))
END
$$;
CREATE OR REPLACE FUNCTION public.aether_legacy_raw_api_format_auth_type(value text)
RETURNS text
LANGUAGE sql
IMMUTABLE
AS $$
SELECT CASE LOWER(BTRIM(COALESCE(value, '')))
WHEN 'claude:chat' THEN 'api_key'
WHEN 'claude:cli' THEN 'bearer'
WHEN 'gemini:chat' THEN 'api_key'
WHEN 'gemini:cli' THEN 'bearer'
ELSE NULL
END
$$;
ALTER TABLE IF EXISTS public.provider_endpoints
DROP CONSTRAINT IF EXISTS uq_provider_api_format;
CREATE INDEX IF NOT EXISTS idx_provider_endpoints_provider_api_format
ON public.provider_endpoints USING btree (provider_id, api_format);
ALTER TABLE IF EXISTS public.provider_api_keys
ADD COLUMN IF NOT EXISTS auth_type_by_format json;
ALTER TABLE IF EXISTS public.provider_api_keys
ALTER COLUMN api_key DROP NOT NULL;
WITH legacy_key_formats AS (
SELECT
pak.id,
LOWER(BTRIM(COALESCE(provider.provider_type, ''))) AS provider_type,
LOWER(BTRIM(COALESCE(pak.auth_type, ''))) AS current_auth_type,
COALESCE(BOOL_OR(LOWER(BTRIM(format.value)) = 'claude:chat'), false) AS has_claude_chat,
COALESCE(BOOL_OR(LOWER(BTRIM(format.value)) = 'claude:cli'), false) AS has_claude_cli,
COALESCE(BOOL_OR(LOWER(BTRIM(format.value)) = 'gemini:chat'), false) AS has_gemini_chat,
COALESCE(BOOL_OR(LOWER(BTRIM(format.value)) = 'gemini:cli'), false) AS has_gemini_cli
FROM public.provider_api_keys AS pak
INNER JOIN public.providers AS provider
ON provider.id = pak.provider_id
LEFT JOIN LATERAL (
SELECT key_format.value
FROM json_array_elements_text(
CASE
WHEN pak.api_formats IS NOT NULL
AND json_typeof(pak.api_formats) = 'array'
THEN pak.api_formats
ELSE '[]'::json
END
) AS key_format(value)
UNION ALL
SELECT endpoint.api_format AS value
FROM public.provider_endpoints AS endpoint
WHERE pak.api_formats IS NULL
AND endpoint.provider_id = pak.provider_id
) AS format ON true
GROUP BY pak.id, provider_type, current_auth_type
),
inferred_key_auth AS (
SELECT
id,
current_auth_type,
CASE
WHEN provider_type IN ('claude_code', 'kiro')
AND has_claude_cli
THEN 'oauth'
WHEN provider_type IN ('gemini_cli', 'antigravity')
AND has_gemini_cli
THEN 'oauth'
WHEN provider_type NOT IN ('claude_code', 'codex', 'gemini_cli', 'vertex_ai', 'antigravity', 'kiro')
AND (has_claude_chat OR has_gemini_chat)
AND NOT (has_claude_cli OR has_gemini_cli)
THEN 'api_key'
ELSE NULL
END AS inferred_auth_type
FROM legacy_key_formats
)
UPDATE public.provider_api_keys AS pak
SET
auth_type = inferred.inferred_auth_type,
updated_at = NOW()
FROM inferred_key_auth AS inferred
WHERE pak.id = inferred.id
AND inferred.inferred_auth_type IS NOT NULL
AND inferred.current_auth_type IS DISTINCT FROM inferred.inferred_auth_type
AND (
(
inferred.inferred_auth_type = 'oauth'
AND inferred.current_auth_type IN ('', 'api_key', 'bearer')
)
OR (
inferred.inferred_auth_type = 'api_key'
AND inferred.current_auth_type IN ('', 'bearer')
)
);
WITH existing_auth_entries AS (
SELECT
pak.id,
public.aether_canonical_api_format_alias(entry.key) AS api_format,
CASE LOWER(BTRIM(COALESCE(entry.value #>> '{}', '')))
WHEN 'api_key' THEN 'api_key'
WHEN 'apikey' THEN 'api_key'
WHEN 'api-key' THEN 'api_key'
WHEN 'bearer' THEN 'bearer'
WHEN 'bearer_token' THEN 'bearer'
WHEN 'bearer-token' THEN 'bearer'
WHEN 'authorization' THEN 'bearer'
ELSE NULL
END AS auth_type,
0 AS source_priority,
entry.ordinality
FROM public.provider_api_keys AS pak
CROSS JOIN LATERAL json_each(
CASE
WHEN json_typeof(pak.auth_type_by_format) = 'object' THEN pak.auth_type_by_format
ELSE '{}'::json
END
) WITH ORDINALITY AS entry(key, value, ordinality)
WHERE pak.auth_type_by_format IS NOT NULL
),
legacy_auth_entries AS (
SELECT
pak.id,
public.aether_canonical_api_format_alias(format.value) AS api_format,
MIN(public.aether_legacy_raw_api_format_auth_type(format.value)) AS auth_type,
1 AS source_priority,
MIN(format.ordinality) AS ordinality
FROM public.provider_api_keys AS pak
LEFT JOIN LATERAL (
SELECT key_format.value, key_format.ordinality
FROM json_array_elements_text(
CASE
WHEN pak.api_formats IS NOT NULL
AND json_typeof(pak.api_formats) = 'array'
THEN pak.api_formats
ELSE '[]'::json
END
) WITH ORDINALITY AS key_format(value, ordinality)
UNION ALL
SELECT endpoint.api_format AS value, endpoint.ordinality
FROM (
SELECT endpoint.api_format, ROW_NUMBER() OVER (ORDER BY endpoint.id) AS ordinality
FROM public.provider_endpoints AS endpoint
WHERE pak.api_formats IS NULL
AND endpoint.provider_id = pak.provider_id
) AS endpoint
) AS format ON true
WHERE LOWER(BTRIM(COALESCE(pak.auth_type, ''))) IN ('api_key', 'bearer')
AND public.aether_legacy_raw_api_format_auth_type(format.value) IS NOT NULL
GROUP BY
pak.id,
public.aether_canonical_api_format_alias(format.value)
HAVING COUNT(DISTINCT public.aether_legacy_raw_api_format_auth_type(format.value)) = 1
),
auth_entries AS (
SELECT id, api_format, auth_type, source_priority, ordinality
FROM existing_auth_entries
WHERE api_format <> ''
AND auth_type IS NOT NULL
UNION ALL
SELECT
legacy.id,
legacy.api_format,
legacy.auth_type,
legacy.source_priority,
legacy.ordinality
FROM legacy_auth_entries AS legacy
INNER JOIN public.provider_api_keys AS pak
ON pak.id = legacy.id
WHERE legacy.api_format <> ''
AND legacy.auth_type IS NOT NULL
AND legacy.auth_type IS DISTINCT FROM LOWER(BTRIM(COALESCE(pak.auth_type, '')))
),
ranked AS (
SELECT
id,
api_format,
auth_type,
source_priority,
ordinality,
ROW_NUMBER() OVER (
PARTITION BY id, api_format
ORDER BY source_priority, ordinality
) AS rank
FROM auth_entries
),
rebuilt AS (
SELECT
id,
jsonb_object_agg(api_format, to_jsonb(auth_type) ORDER BY source_priority, ordinality) AS auth_type_by_format
FROM ranked
WHERE rank = 1
GROUP BY id
)
UPDATE public.provider_api_keys AS pak
SET
auth_type_by_format = rebuilt.auth_type_by_format::json,
updated_at = NOW()
FROM rebuilt
WHERE pak.id = rebuilt.id
AND pak.auth_type_by_format::jsonb IS DISTINCT FROM rebuilt.auth_type_by_format;
WITH normalized AS (
SELECT
id,
public.aether_canonical_api_format_alias(api_format) AS canonical_api_format
FROM public.provider_endpoints
WHERE api_format IN ('openai:responses', 'openai:cli', 'openai:responses:compact', 'openai:compact', 'claude:messages', 'claude:chat', 'claude:cli', 'gemini:generate_content', 'gemini:chat', 'gemini:cli')
)
UPDATE public.provider_endpoints AS endpoint
SET
api_format = normalized.canonical_api_format,
api_family = SPLIT_PART(normalized.canonical_api_format, ':', 1),
endpoint_kind = SUBSTRING(normalized.canonical_api_format FROM POSITION(':' IN normalized.canonical_api_format) + 1),
updated_at = NOW()
FROM normalized
WHERE endpoint.id = normalized.id
AND (
endpoint.api_format IS DISTINCT FROM normalized.canonical_api_format
OR endpoint.api_family IS DISTINCT FROM SPLIT_PART(normalized.canonical_api_format, ':', 1)
OR endpoint.endpoint_kind IS DISTINCT FROM SUBSTRING(normalized.canonical_api_format FROM POSITION(':' IN normalized.canonical_api_format) + 1)
);
WITH expanded AS (
SELECT
pak.id,
formats.ordinality,
public.aether_canonical_api_format_alias(formats.value) AS api_format
FROM public.provider_api_keys AS pak
CROSS JOIN LATERAL json_array_elements_text(
CASE
WHEN json_typeof(pak.api_formats) = 'array' THEN pak.api_formats
ELSE '[]'::json
END
) WITH ORDINALITY AS formats(value, ordinality)
WHERE pak.api_formats IS NOT NULL
),
deduped AS (
SELECT id, api_format, MIN(ordinality) AS first_ordinality
FROM expanded
WHERE api_format <> ''
GROUP BY id, api_format
),
rebuilt AS (
SELECT id, json_agg(api_format ORDER BY first_ordinality) AS api_formats
FROM deduped
GROUP BY id
)
UPDATE public.provider_api_keys AS pak
SET
api_formats = rebuilt.api_formats,
updated_at = NOW()
FROM rebuilt
WHERE pak.id = rebuilt.id
AND pak.api_formats::jsonb IS DISTINCT FROM rebuilt.api_formats::jsonb;
WITH expanded AS (
SELECT
key.id,
formats.ordinality,
public.aether_canonical_api_format_alias(formats.value) AS api_format
FROM public.api_keys AS key
CROSS JOIN LATERAL json_array_elements_text(
CASE
WHEN json_typeof(key.allowed_api_formats) = 'array' THEN key.allowed_api_formats
ELSE '[]'::json
END
) WITH ORDINALITY AS formats(value, ordinality)
WHERE key.allowed_api_formats IS NOT NULL
),
deduped AS (
SELECT id, api_format, MIN(ordinality) AS first_ordinality
FROM expanded
WHERE api_format <> ''
GROUP BY id, api_format
),
rebuilt AS (
SELECT id, json_agg(api_format ORDER BY first_ordinality) AS allowed_api_formats
FROM deduped
GROUP BY id
)
UPDATE public.api_keys AS key
SET
allowed_api_formats = rebuilt.allowed_api_formats,
updated_at = NOW()
FROM rebuilt
WHERE key.id = rebuilt.id
AND key.allowed_api_formats::jsonb IS DISTINCT FROM rebuilt.allowed_api_formats::jsonb;
WITH expanded AS (
SELECT
users.id,
formats.ordinality,
public.aether_canonical_api_format_alias(formats.value) AS api_format
FROM public.users AS users
CROSS JOIN LATERAL json_array_elements_text(
CASE
WHEN json_typeof(users.allowed_api_formats) = 'array' THEN users.allowed_api_formats
ELSE '[]'::json
END
) WITH ORDINALITY AS formats(value, ordinality)
WHERE users.allowed_api_formats IS NOT NULL
),
deduped AS (
SELECT id, api_format, MIN(ordinality) AS first_ordinality
FROM expanded
WHERE api_format <> ''
GROUP BY id, api_format
),
rebuilt AS (
SELECT id, json_agg(api_format ORDER BY first_ordinality) AS allowed_api_formats
FROM deduped
GROUP BY id
)
UPDATE public.users AS users
SET
allowed_api_formats = rebuilt.allowed_api_formats,
updated_at = NOW()
FROM rebuilt
WHERE users.id = rebuilt.id
AND users.allowed_api_formats::jsonb IS DISTINCT FROM rebuilt.allowed_api_formats::jsonb;
WITH mapping_items AS (
SELECT
models.id,
item.ordinality AS item_ordinality,
item.value AS item
FROM public.models AS models
CROSS JOIN LATERAL jsonb_array_elements(
CASE
WHEN jsonb_typeof(models.provider_model_mappings) = 'array' THEN models.provider_model_mappings
ELSE '[]'::jsonb
END
) WITH ORDINALITY AS item(value, ordinality)
WHERE models.provider_model_mappings IS NOT NULL
),
rebuilt_items AS (
SELECT
id,
item_ordinality,
CASE
WHEN jsonb_typeof(item) = 'object'
AND jsonb_typeof(item->'api_formats') = 'array'
THEN jsonb_set(
item,
'{api_formats}',
COALESCE(
(
SELECT jsonb_agg(api_format ORDER BY first_ordinality)
FROM (
SELECT
public.aether_canonical_api_format_alias(format.value) AS api_format,
MIN(format.ordinality) AS first_ordinality
FROM jsonb_array_elements_text(item->'api_formats') WITH ORDINALITY AS format(value, ordinality)
GROUP BY public.aether_canonical_api_format_alias(format.value)
) AS deduped_formats
WHERE api_format <> ''
),
'[]'::jsonb
),
true
)
ELSE item
END AS item
FROM mapping_items
),
rebuilt AS (
SELECT id, jsonb_agg(item ORDER BY item_ordinality) AS provider_model_mappings
FROM rebuilt_items
GROUP BY id
)
UPDATE public.models AS models
SET
provider_model_mappings = rebuilt.provider_model_mappings,
updated_at = NOW()
FROM rebuilt
WHERE models.id = rebuilt.id
AND models.provider_model_mappings IS DISTINCT FROM rebuilt.provider_model_mappings;
WITH rebuilt AS (
SELECT
pak.id,
jsonb_object_agg(
public.aether_canonical_api_format_alias(entry.key),
entry.value
ORDER BY entry.ordinality
) AS rate_multipliers
FROM public.provider_api_keys AS pak
CROSS JOIN LATERAL json_each(
CASE
WHEN json_typeof(pak.rate_multipliers) = 'object' THEN pak.rate_multipliers
ELSE '{}'::json
END
) WITH ORDINALITY AS entry(key, value, ordinality)
WHERE pak.rate_multipliers IS NOT NULL
GROUP BY pak.id
)
UPDATE public.provider_api_keys AS pak
SET
rate_multipliers = rebuilt.rate_multipliers::json,
updated_at = NOW()
FROM rebuilt
WHERE pak.id = rebuilt.id
AND pak.rate_multipliers::jsonb IS DISTINCT FROM rebuilt.rate_multipliers;
WITH rebuilt AS (
SELECT
pak.id,
jsonb_object_agg(
public.aether_canonical_api_format_alias(entry.key),
entry.value
ORDER BY entry.ordinality
) AS global_priority_by_format
FROM public.provider_api_keys AS pak
CROSS JOIN LATERAL json_each(
CASE
WHEN json_typeof(pak.global_priority_by_format) = 'object' THEN pak.global_priority_by_format
ELSE '{}'::json
END
) WITH ORDINALITY AS entry(key, value, ordinality)
WHERE pak.global_priority_by_format IS NOT NULL
GROUP BY pak.id
)
UPDATE public.provider_api_keys AS pak
SET
global_priority_by_format = rebuilt.global_priority_by_format::json,
updated_at = NOW()
FROM rebuilt
WHERE pak.id = rebuilt.id
AND pak.global_priority_by_format::jsonb IS DISTINCT FROM rebuilt.global_priority_by_format;
WITH rebuilt AS (
SELECT
pak.id,
jsonb_object_agg(
public.aether_canonical_api_format_alias(entry.key),
entry.value
ORDER BY entry.ordinality
) AS health_by_format
FROM public.provider_api_keys AS pak
CROSS JOIN LATERAL jsonb_each(
CASE
WHEN jsonb_typeof(pak.health_by_format) = 'object' THEN pak.health_by_format
ELSE '{}'::jsonb
END
) WITH ORDINALITY AS entry(key, value, ordinality)
WHERE pak.health_by_format IS NOT NULL
GROUP BY pak.id
)
UPDATE public.provider_api_keys AS pak
SET
health_by_format = rebuilt.health_by_format,
updated_at = NOW()
FROM rebuilt
WHERE pak.id = rebuilt.id
AND pak.health_by_format IS DISTINCT FROM rebuilt.health_by_format;
WITH rebuilt AS (
SELECT
pak.id,
jsonb_object_agg(
public.aether_canonical_api_format_alias(entry.key),
entry.value
ORDER BY entry.ordinality
) AS circuit_breaker_by_format
FROM public.provider_api_keys AS pak
CROSS JOIN LATERAL jsonb_each(
CASE
WHEN jsonb_typeof(pak.circuit_breaker_by_format) = 'object' THEN pak.circuit_breaker_by_format
ELSE '{}'::jsonb
END
) WITH ORDINALITY AS entry(key, value, ordinality)
WHERE pak.circuit_breaker_by_format IS NOT NULL
GROUP BY pak.id
)
UPDATE public.provider_api_keys AS pak
SET
circuit_breaker_by_format = rebuilt.circuit_breaker_by_format,
updated_at = NOW()
FROM rebuilt
WHERE pak.id = rebuilt.id
AND pak.circuit_breaker_by_format IS DISTINCT FROM rebuilt.circuit_breaker_by_format;
DROP FUNCTION public.aether_legacy_raw_api_format_auth_type(text);
DROP FUNCTION public.aether_canonical_api_format_alias(text);

View File

@@ -0,0 +1,80 @@
ALTER TABLE public.provider_api_keys
ADD COLUMN IF NOT EXISTS allow_auth_channel_mismatch_formats json;
ALTER TABLE public.provider_api_keys
ADD COLUMN IF NOT EXISTS concurrent_limit integer;
CREATE OR REPLACE FUNCTION public.aether_default_auth_mismatch_api_format(value text)
RETURNS text
LANGUAGE sql
IMMUTABLE
AS $$
SELECT CASE LOWER(BTRIM(COALESCE(value, '')))
WHEN 'openai:cli' THEN 'openai:responses'
WHEN 'openai:compact' THEN 'openai:responses:compact'
WHEN 'claude:chat' THEN 'claude:messages'
WHEN 'claude:cli' THEN 'claude:messages'
WHEN 'gemini:chat' THEN 'gemini:generate_content'
WHEN 'gemini:cli' THEN 'gemini:generate_content'
ELSE LOWER(BTRIM(COALESCE(value, '')))
END
$$;
WITH supported_formats AS (
SELECT
pak.id,
public.aether_default_auth_mismatch_api_format(format.value) AS api_format,
0 AS source_priority,
MIN(format.ordinality) AS first_ordinality
FROM public.provider_api_keys AS pak
CROSS JOIN LATERAL json_array_elements_text(
CASE
WHEN pak.api_formats IS NOT NULL
AND json_typeof(pak.api_formats) = 'array'
THEN pak.api_formats
ELSE '[]'::json
END
) WITH ORDINALITY AS format(value, ordinality)
WHERE pak.api_formats IS NOT NULL
AND json_typeof(pak.api_formats) = 'array'
GROUP BY pak.id, api_format
UNION ALL
SELECT
pak.id,
public.aether_default_auth_mismatch_api_format(endpoint.api_format) AS api_format,
1 AS source_priority,
0 AS first_ordinality
FROM public.provider_api_keys AS pak
INNER JOIN public.provider_endpoints AS endpoint
ON endpoint.provider_id = pak.provider_id
WHERE pak.api_formats IS NULL
OR json_typeof(pak.api_formats) <> 'array'
),
deduplicated_formats AS (
SELECT
id,
api_format,
MIN(source_priority) AS source_priority,
MIN(first_ordinality) AS first_ordinality
FROM supported_formats
WHERE api_format <> ''
GROUP BY id, api_format
),
rebuilt AS (
SELECT
id,
json_agg(api_format ORDER BY source_priority, first_ordinality, api_format) AS api_formats
FROM deduplicated_formats
GROUP BY id
)
UPDATE public.provider_api_keys AS pak
SET
allow_auth_channel_mismatch_formats = rebuilt.api_formats,
updated_at = NOW()
FROM rebuilt
WHERE pak.id = rebuilt.id
AND pak.allow_auth_channel_mismatch_formats IS NULL;
DROP FUNCTION public.aether_default_auth_mismatch_api_format(text);

View File

@@ -0,0 +1,61 @@
ALTER TABLE public.users
ADD COLUMN IF NOT EXISTS external_id character varying(255),
ADD COLUMN IF NOT EXISTS metadata json;
CREATE TABLE IF NOT EXISTS public.auth_modules (
id character varying(36) NOT NULL,
module_type character varying(128) NOT NULL,
enabled boolean DEFAULT true NOT NULL,
config json NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT auth_modules_pkey PRIMARY KEY (id),
CONSTRAINT auth_modules_module_type_key UNIQUE (module_type)
);
ALTER TABLE public.api_keys
ADD COLUMN IF NOT EXISTS key_prefix character varying(64),
ADD COLUMN IF NOT EXISTS status character varying(64) DEFAULT 'active'::character varying NOT NULL,
ADD COLUMN IF NOT EXISTS metadata json;
ALTER TABLE public.providers
ADD COLUMN IF NOT EXISTS enabled boolean DEFAULT true NOT NULL,
ADD COLUMN IF NOT EXISTS priority bigint DEFAULT 0 NOT NULL;
ALTER TABLE public.provider_api_keys
ADD COLUMN IF NOT EXISTS encrypted_key text,
ADD COLUMN IF NOT EXISTS status character varying(64) DEFAULT 'active'::character varying NOT NULL,
ADD COLUMN IF NOT EXISTS weight bigint DEFAULT 1 NOT NULL,
ADD COLUMN IF NOT EXISTS metadata json;
ALTER TABLE public.provider_endpoints
ADD COLUMN IF NOT EXISTS name character varying(255),
ADD COLUMN IF NOT EXISTS enabled boolean DEFAULT true NOT NULL,
ADD COLUMN IF NOT EXISTS weight bigint DEFAULT 1 NOT NULL,
ADD COLUMN IF NOT EXISTS metadata json;
ALTER TABLE public.provider_endpoints
ALTER COLUMN api_format DROP NOT NULL;
ALTER TABLE public.global_models
ADD COLUMN IF NOT EXISTS enabled boolean DEFAULT true NOT NULL,
ADD COLUMN IF NOT EXISTS metadata json;
ALTER TABLE public.global_models
ALTER COLUMN display_name DROP NOT NULL,
ALTER COLUMN default_tiered_pricing DROP NOT NULL;
ALTER TABLE public.models
ADD COLUMN IF NOT EXISTS global_model_name character varying(255),
ADD COLUMN IF NOT EXISTS api_format character varying(128),
ADD COLUMN IF NOT EXISTS enabled boolean DEFAULT true NOT NULL,
ADD COLUMN IF NOT EXISTS metadata json;
ALTER TABLE public.models
ALTER COLUMN global_model_id DROP NOT NULL;
ALTER TABLE public.usage
ADD COLUMN IF NOT EXISTS cache_creation_ephemeral_5m_input_tokens integer DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS cache_creation_ephemeral_1h_input_tokens integer DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS created_at_unix_ms bigint DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS updated_at_unix_secs bigint DEFAULT 0 NOT NULL;

View File

@@ -0,0 +1,233 @@
ALTER TABLE IF EXISTS public.usage
ADD COLUMN IF NOT EXISTS upstream_is_stream boolean;
UPDATE public.usage
SET upstream_is_stream = COALESCE(
CASE
WHEN (request_metadata->>'upstream_is_stream') IN ('true', 'false')
THEN (request_metadata->>'upstream_is_stream')::boolean
ELSE NULL
END,
COALESCE(is_stream, FALSE)
)
WHERE upstream_is_stream IS NULL;
ANALYZE public.usage;
COMMENT ON COLUMN public.usage.upstream_is_stream IS
'Resolved upstream stream mode from request_metadata.upstream_is_stream, falling back to is_stream for legacy rows.';
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,
COALESCE(usage_rows.upstream_is_stream, COALESCE(usage_rows.is_stream, FALSE)) AS upstream_is_stream
FROM public."usage" AS usage_rows
LEFT JOIN public.usage_settlement_snapshots AS settlement
ON settlement.request_id = usage_rows.request_id;
COMMENT ON COLUMN public.usage_billing_facts.upstream_is_stream IS
'Resolved upstream stream mode from public.usage.upstream_is_stream, falling back to usage.is_stream for legacy rows.';