-- 004_roles_usage.sql -- v0.10.0: Model Roles + Usage Tracking -- -- New tables: usage_log, model_pricing -- Seed: model_roles in global_settings -- ── Model roles seed ────────────────────────── -- Uses existing global_settings table. No new tables for roles. -- Team overrides go in teams.settings JSONB (existing column). INSERT INTO global_settings (key, value) VALUES ('model_roles', '{ "utility": { "primary": null, "fallback": null }, "embedding": { "primary": null, "fallback": null }, "generation": { "primary": null, "fallback": null } }'::jsonb) ON CONFLICT (key) DO NOTHING; -- ── Usage tracking ──────────────────────────── -- Every completion (streaming and non-streaming) logs token counts and cost. -- Cost is calculated at insert time from model_pricing. -- provider_scope is denormalized for efficient admin filtering (excludes BYOK). CREATE TABLE IF NOT EXISTS usage_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel_id UUID REFERENCES channels(id) ON DELETE SET NULL, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, provider_config_id UUID REFERENCES provider_configs(id) ON DELETE SET NULL, provider_scope TEXT NOT NULL DEFAULT 'global', model_id TEXT NOT NULL, role TEXT, -- NULL = user chat, 'utility', 'embedding', 'generation' prompt_tokens INT NOT NULL DEFAULT 0, completion_tokens INT NOT NULL DEFAULT 0, cache_creation_tokens INT NOT NULL DEFAULT 0, cache_read_tokens INT NOT NULL DEFAULT 0, cost_input NUMERIC(12,6), cost_output NUMERIC(12,6), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_usage_log_user ON usage_log(user_id); CREATE INDEX IF NOT EXISTS idx_usage_log_created ON usage_log(created_at); CREATE INDEX IF NOT EXISTS idx_usage_log_provider ON usage_log(provider_config_id); CREATE INDEX IF NOT EXISTS idx_usage_log_model ON usage_log(model_id); CREATE INDEX IF NOT EXISTS idx_usage_log_scope ON usage_log(provider_scope); -- ── Model pricing ───────────────────────────── -- Two sources: 'catalog' (auto-populated from provider API sync) and -- 'manual' (admin override). Manual entries are never overwritten by sync. CREATE TABLE IF NOT EXISTS model_pricing ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), provider_config_id UUID NOT NULL REFERENCES provider_configs(id) ON DELETE CASCADE, model_id TEXT NOT NULL, input_per_m NUMERIC(10,6), output_per_m NUMERIC(10,6), cache_create_per_m NUMERIC(10,6), cache_read_per_m NUMERIC(10,6), currency TEXT NOT NULL DEFAULT 'USD', source TEXT NOT NULL DEFAULT 'manual', updated_at TIMESTAMPTZ DEFAULT NOW(), updated_by UUID REFERENCES users(id), UNIQUE(provider_config_id, model_id) );