Files
descrybe/apps/api/sql/schema/046_ai_usage_cost.sql
T
2026-08-23 22:52:13 +02:00

66 lines
3.0 KiB
SQL

-- +goose Up
-- Durable AI cost accounting, deliberately separate from ai_call_logs.
--
-- ai_call_logs holds full prompts and is pruned after 7 days. Cost has to outlive
-- that by years, so usage is rolled up per day / company / user / model / role at
-- capture time. One row per combination per day keeps three years small: a busy
-- tenant with 5 users and 3 roles adds ~45 rows a day, not one per product.
--
-- Cost is stored in micro-USD integers, never floats: summing millions of
-- fractional-cent calls in float drifts, and money must add up exactly.
CREATE TABLE IF NOT EXISTS ai_model_prices (
model TEXT PRIMARY KEY,
-- Micro-USD per 1,000,000 tokens. $0.20/1M = 200000 micros.
input_micros_per_mtok BIGINT NOT NULL DEFAULT 0,
cached_input_micros_per_mtok BIGINT NOT NULL DEFAULT 0,
output_micros_per_mtok BIGINT NOT NULL DEFAULT 0,
source TEXT NOT NULL DEFAULT '',
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- OpenAI GPT-5.6 family, rates effective 2026-07-30.
-- Admin can UPDATE these when list prices change; already-recorded cost is never
-- recalculated, so history keeps the price that applied at the time.
INSERT INTO ai_model_prices (model, input_micros_per_mtok, cached_input_micros_per_mtok, output_micros_per_mtok, source)
VALUES
('gpt-5.6-luna', 200000, 20000, 1200000, 'openai list price 2026-07-30'),
('gpt-5.6-terra', 2000000, 200000, 12000000, 'openai list price 2026-07-30'),
('gpt-5.6-sol', 5000000, 500000, 30000000, 'openai list price 2026-07-30')
ON CONFLICT (model) DO NOTHING;
CREATE TABLE IF NOT EXISTS ai_usage_daily (
day DATE NOT NULL,
-- Nil UUID rather than NULL: these are primary-key columns and NULL would let
-- duplicate rows accumulate instead of upserting.
company_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
user_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
model TEXT NOT NULL DEFAULT '',
role TEXT NOT NULL DEFAULT 'other',
calls BIGINT NOT NULL DEFAULT 0,
failed_calls BIGINT NOT NULL DEFAULT 0,
prompt_tokens BIGINT NOT NULL DEFAULT 0,
cached_prompt_tokens BIGINT NOT NULL DEFAULT 0,
completion_tokens BIGINT NOT NULL DEFAULT 0,
total_tokens BIGINT NOT NULL DEFAULT 0,
cost_micros BIGINT NOT NULL DEFAULT 0,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (day, company_id, user_id, model, role)
);
-- No FK to companies/users: cost history must survive a tenant or user being
-- deleted, otherwise the yearly total silently drops.
CREATE INDEX IF NOT EXISTS ai_usage_daily_company_day_idx
ON ai_usage_daily (company_id, day DESC);
CREATE INDEX IF NOT EXISTS ai_usage_daily_user_day_idx
ON ai_usage_daily (user_id, day DESC);
CREATE INDEX IF NOT EXISTS ai_usage_daily_day_idx
ON ai_usage_daily (day DESC);
-- +goose Down
DROP TABLE IF EXISTS ai_usage_daily;
DROP TABLE IF EXISTS ai_model_prices;