66 lines
3.0 KiB
SQL
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;
|