ultisuite-backend/migrations/000052_ai_cost_metering.up.sql
R3D347HR4Y 3978622050
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
refactor(ai): update AI gateway and cost management features
- Refactored AI gateway to utilize new cost management structures for usage tracking.
- Replaced deprecated token extraction methods with a unified cost parsing approach.
- Enhanced usage fallback mechanisms and introduced detailed usage metrics in responses.
- Added new metering functionality to record AI usage and costs effectively.
- Updated tests to reflect changes in usage parsing and cost calculations.
- Introduced new API endpoints for retrieving AI usage summaries and pricing information.
2026-06-16 10:46:33 +02:00

97 lines
4.5 KiB
SQL

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