nxtgauge-backend-rust/crates/db/migrations/20260706500000_ai_token_cost_engine.up.sql
Ashwin Kumar Sivakumar 3c0f45f1ee feat: Complete Ask Ash AI Credits implementation (Tasks 1-10)
- Task 1: Admin endpoints for wallet management (balance, ledger, adjust, reconcile)
- Task 2: AI Credits admin UI (pricing.tsx, credit.tsx)
- Task 3: Ollama security (NetworkPolicy, prompt validation, audit logging)
- Task 4: LiteLLM integration (litellm.rs, migrated AI feature handlers)
- Task 5: Refund architecture (ai_refunds table, endpoints)
- Task 6: Coupons, promotions, referrals (order creation with coupon validation)
- Task 7: Subscription lifecycle (plan upgrades/downgrades/cancellations, cron jobs)
- Task 8: Credit expiration enforcement (daily cron task)
- Task 9: Token cost engine (ai_model_cost_config, margin view)
- Task 10: Observability (metrics tables, aggregation function)

New files:
- apps/users/src/litellm.rs (LiteLLM client)
- apps/users/src/ai_credits.rs (Charging primitives)
- apps/users/src/ai_subscription.rs (Plan lifecycle)
- apps/users/src/handlers/ai_credits.rs (Admin endpoints)
- apps/payments/src/ai_credits.rs (Package purchase)
- apps/payments/src/payu.rs (PayU integration)
- apps/cron/src/tasks/ai_credits.rs (Cron jobs)
- 9 SQL migrations for schema
- crates/db/src/models/ai_credits.rs (Repository)
- tests for ai_credits
2026-07-06 01:47:54 +05:30

119 lines
4.7 KiB
PL/PgSQL

-- Token cost engine and provider cost tracking (Task 9)
-- Configures model costs and tracks actual usage costs for margin analysis
BEGIN;
-- Model cost configuration table
CREATE TABLE ai_model_cost_config (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
model_alias VARCHAR(100) NOT NULL,
-- Cost per 1K tokens
cost_per_1k_input_tokens NUMERIC(12, 8) NOT NULL,
cost_per_1k_output_tokens NUMERIC(12, 8) NOT NULL,
-- Cost basis: compute_amortized (self-hosted) or provider_metered (paid APIs)
cost_basis VARCHAR(30) NOT NULL CHECK (cost_basis IN ('compute_amortized', 'provider_metered')),
-- For compute_amortized: optional metadata about calculation
cost_calculation_notes TEXT,
-- Effective date range
effective_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
effective_until TIMESTAMPTZ,
-- Status
is_active BOOLEAN NOT NULL DEFAULT TRUE,
-- Audit
created_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(model_alias, effective_from)
);
-- Add cost tracking columns to ai_usage_logs if not exist
DO $$
BEGIN
-- These columns already exist per the initial migration, but verify they're populated
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'ai_usage_logs'
AND column_name = 'input_tokens') THEN
ALTER TABLE ai_usage_logs ADD COLUMN input_tokens INT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'ai_usage_logs'
AND column_name = 'output_tokens') THEN
ALTER TABLE ai_usage_logs ADD COLUMN output_tokens INT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'ai_usage_logs'
AND column_name = 'total_tokens') THEN
ALTER TABLE ai_usage_logs ADD COLUMN total_tokens INT;
END IF;
END $$;
-- Create or replace view for margin calculation
-- This provides real-time margin analysis without storing computed values
CREATE OR REPLACE VIEW ai_usage_margin_view AS
SELECT
l.id,
l.user_id,
l.feature_code,
l.model_alias,
l.credits_charged,
l.input_tokens,
l.output_tokens,
l.total_tokens,
l.status,
l.created_at,
-- Calculate internal cost using current cost config
CASE
WHEN l.input_tokens IS NOT NULL AND l.output_tokens IS NOT NULL THEN
(l.input_tokens::NUMERIC / 1000.0 * COALESCE(c.cost_per_1k_input_tokens, 0)) +
(l.output_tokens::NUMERIC / 1000.0 * COALESCE(c.cost_per_1k_output_tokens, 0))
ELSE NULL
END as calculated_internal_cost,
-- Cost basis
c.cost_basis,
-- Margin (credits charged are in some credit unit, convert to currency for margin calc)
-- Note: This assumes credits have a monetary value; adjust conversion rate as needed
CASE
WHEN l.input_tokens IS NOT NULL AND l.output_tokens IS NOT NULL THEN
l.credits_charged - (
(l.input_tokens::NUMERIC / 1000.0 * COALESCE(c.cost_per_1k_input_tokens, 0)) +
(l.output_tokens::NUMERIC / 1000.0 * COALESCE(c.cost_per_1k_output_tokens, 0))
)
ELSE NULL
END as margin_credits
FROM ai_usage_logs l
LEFT JOIN ai_model_cost_config c ON l.model_alias = c.model_alias
AND c.is_active = TRUE
AND c.effective_from <= l.created_at
AND (c.effective_until IS NULL OR c.effective_until > l.created_at)
WHERE l.status = 'success';
-- Indexes
CREATE INDEX idx_ai_model_cost_config_alias ON ai_model_cost_config(model_alias) WHERE is_active = TRUE;
CREATE INDEX idx_ai_usage_logs_tokens ON ai_usage_logs(user_id, created_at DESC)
WHERE input_tokens IS NOT NULL OR output_tokens IS NOT NULL;
-- Seed initial cost config for self-hosted Ollama models (compute amortized)
-- These are example values - adjust based on actual GPU costs and throughput
INSERT INTO ai_model_cost_config (model_alias, cost_per_1k_input_tokens, cost_per_1k_output_tokens, cost_basis, cost_calculation_notes)
VALUES
('askash-fast', 0.0001, 0.0002, 'compute_amortized', 'qwen3:4b on shared GPU - cost from GPU node amortization'),
('askash-main', 0.0002, 0.0004, 'compute_amortized', 'qwen3:8b on shared GPU - cost from GPU node amortization'),
('ultra-fast', 0.00005, 0.0001, 'compute_amortized', 'gemma3:270m on shared GPU - cost from GPU node amortization'),
('jd-generator', 0.0002, 0.0004, 'compute_amortized', 'qwen3:8b - specialized for job descriptions'),
('profile-writer', 0.0002, 0.0004, 'compute_amortized', 'qwen3:8b - specialized for profile writing')
ON CONFLICT (model_alias, effective_from) DO NOTHING;
COMMIT;