-- +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;