-- Model pricing (micro-EUR per 1M tokens) CREATE TABLE IF NOT EXISTS ai_model_pricing ( model_id TEXT NOT NULL, provider_type TEXT NOT NULL DEFAULT 'generic', input_micro_eur_per_mtok BIGINT NOT NULL, cached_input_micro_eur_per_mtok BIGINT, output_micro_eur_per_mtok BIGINT NOT NULL, reasoning_micro_eur_per_mtok BIGINT, effective_from DATE NOT NULL DEFAULT CURRENT_DATE, source TEXT NOT NULL DEFAULT 'manual', PRIMARY KEY (model_id, effective_from) ); -- Detailed usage ledger CREATE TABLE IF NOT EXISTS ai_usage_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), feature TEXT NOT NULL, model_id TEXT NOT NULL, provider_id TEXT NOT NULL, billing_scope TEXT NOT NULL CHECK (billing_scope IN ('org', 'user')), provider_key_fingerprint TEXT NOT NULL DEFAULT '', prompt_tokens INT NOT NULL DEFAULT 0, completion_tokens INT NOT NULL DEFAULT 0, cached_input_tokens INT NOT NULL DEFAULT 0, reasoning_tokens INT NOT NULL DEFAULT 0, cost_micro_eur BIGINT NOT NULL DEFAULT 0, estimated BOOLEAN NOT NULL DEFAULT false, request_id TEXT ); CREATE INDEX IF NOT EXISTS idx_ai_usage_events_user_time ON ai_usage_events(user_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_ai_usage_events_scope_key ON ai_usage_events(billing_scope, provider_key_fingerprint, created_at DESC); CREATE INDEX IF NOT EXISTS idx_ai_usage_events_created ON ai_usage_events(created_at DESC); -- Extend daily rollups ALTER TABLE ai_usage_daily ADD COLUMN IF NOT EXISTS cost_micro_eur_org BIGINT NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS cost_micro_eur_user BIGINT NOT NULL DEFAULT 0; -- Extend monthly rollups ALTER TABLE ai_usage_monthly ADD COLUMN IF NOT EXISTS cost_micro_eur_org BIGINT NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS cost_micro_eur_user BIGINT NOT NULL DEFAULT 0; -- Org-wide aggregates CREATE TABLE IF NOT EXISTS ai_org_usage_daily ( usage_date DATE NOT NULL PRIMARY KEY, cost_micro_eur_org BIGINT NOT NULL DEFAULT 0, cost_micro_eur_user BIGINT NOT NULL DEFAULT 0, requests INT NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS ai_org_usage_monthly ( usage_month DATE NOT NULL PRIMARY KEY, cost_micro_eur_org BIGINT NOT NULL DEFAULT 0, cost_micro_eur_user BIGINT NOT NULL DEFAULT 0 ); -- Cost limit policies (org / group / user) CREATE TABLE IF NOT EXISTS ai_cost_policies ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), scope_type TEXT NOT NULL CHECK (scope_type IN ('org', 'group', 'user')), scope_id UUID, daily_limit_micro_eur BIGINT, monthly_limit_micro_eur BIGINT, warn_threshold_pct INT NOT NULL DEFAULT 80, priority INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_cost_policies_org ON ai_cost_policies(scope_type) WHERE scope_type = 'org'; CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_cost_policies_group ON ai_cost_policies(scope_type, scope_id) WHERE scope_type = 'group'; CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_cost_policies_user ON ai_cost_policies(scope_type, scope_id) WHERE scope_type = 'user'; -- Default org policy: ~10 EUR/day, ~100 EUR/month INSERT INTO ai_cost_policies (scope_type, scope_id, daily_limit_micro_eur, monthly_limit_micro_eur, warn_threshold_pct, priority) SELECT 'org', NULL, 10000000, 100000000, 80, 0 WHERE NOT EXISTS (SELECT 1 FROM ai_cost_policies WHERE scope_type = 'org'); -- Seed common model pricing (approximate public rates in EUR) INSERT INTO ai_model_pricing (model_id, provider_type, input_micro_eur_per_mtok, cached_input_micro_eur_per_mtok, output_micro_eur_per_mtok, source) VALUES ('gpt-4o-mini', 'openai', 140000, 70000, 560000, 'seed'), ('gpt-4o', 'openai', 2300000, 1150000, 9200000, 'seed'), ('gpt-4.1-mini', 'openai', 360000, 90000, 1440000, 'seed'), ('gpt-4.1', 'openai', 1800000, 450000, 7200000, 'seed'), ('o3-mini', 'openai', 990000, 250000, 3960000, 'seed'), ('claude-sonnet-4-6', 'anthropic', 2700000, 270000, 13500000, 'seed'), ('claude-haiku-4-5', 'anthropic', 900000, 90000, 4500000, 'seed'), ('mistral-small-latest', 'mistral', 180000, 90000, 540000, 'seed'), ('mistral-large-latest', 'mistral', 1800000, 450000, 5400000, 'seed'), ('gemini-2.0-flash', 'google_gemini', 90000, 23000, 360000, 'seed'), ('gemini-2.5-pro', 'google_gemini', 1100000, 280000, 9000000, 'seed') ON CONFLICT DO NOTHING;