- 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
166 lines
5.5 KiB
PL/PgSQL
166 lines
5.5 KiB
PL/PgSQL
-- Observability: AI credits metrics tables (Task 10)
|
|
-- Stores aggregated metrics for dashboard and monitoring
|
|
|
|
BEGIN;
|
|
|
|
-- Daily aggregated metrics for AI credits usage
|
|
CREATE TABLE ai_metrics_daily (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
metric_date DATE NOT NULL,
|
|
|
|
-- Usage metrics
|
|
total_requests INT NOT NULL DEFAULT 0,
|
|
total_credits_charged INT NOT NULL DEFAULT 0,
|
|
total_credits_refunded INT NOT NULL DEFAULT 0,
|
|
|
|
-- Token metrics
|
|
total_input_tokens BIGINT NOT NULL DEFAULT 0,
|
|
total_output_tokens BIGINT NOT NULL DEFAULT 0,
|
|
total_tokens BIGINT NOT NULL DEFAULT 0,
|
|
|
|
-- Financial metrics
|
|
total_revenue NUMERIC(12, 2) NOT NULL DEFAULT 0, -- From purchases
|
|
estimated_cost NUMERIC(12, 6) NOT NULL DEFAULT 0, -- Computed from tokens
|
|
estimated_margin NUMERIC(12, 2) NOT NULL DEFAULT 0, -- revenue - cost
|
|
|
|
-- Error metrics
|
|
failed_requests INT NOT NULL DEFAULT 0,
|
|
timeout_requests INT NOT NULL DEFAULT 0,
|
|
|
|
-- Unique users
|
|
unique_active_users INT NOT NULL DEFAULT 0,
|
|
new_wallet_creations INT NOT NULL DEFAULT 0,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
UNIQUE(metric_date)
|
|
);
|
|
|
|
-- Hourly metrics for more granular monitoring
|
|
CREATE TABLE ai_metrics_hourly (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
metric_hour TIMESTAMPTZ NOT NULL, -- Truncated to hour
|
|
|
|
total_requests INT NOT NULL DEFAULT 0,
|
|
total_credits_charged INT NOT NULL DEFAULT 0,
|
|
avg_response_time_ms INT, -- Average response time in milliseconds
|
|
error_count INT NOT NULL DEFAULT 0,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
UNIQUE(metric_hour)
|
|
);
|
|
|
|
-- Feature usage breakdown
|
|
CREATE TABLE ai_metrics_by_feature (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
metric_date DATE NOT NULL,
|
|
feature_code VARCHAR(100) NOT NULL,
|
|
|
|
request_count INT NOT NULL DEFAULT 0,
|
|
credits_charged INT NOT NULL DEFAULT 0,
|
|
unique_users INT NOT NULL DEFAULT 0,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
UNIQUE(metric_date, feature_code)
|
|
);
|
|
|
|
-- Model usage breakdown
|
|
CREATE TABLE ai_metrics_by_model (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
metric_date DATE NOT NULL,
|
|
model_alias VARCHAR(100) NOT NULL,
|
|
|
|
request_count INT NOT NULL DEFAULT 0,
|
|
input_tokens BIGINT NOT NULL DEFAULT 0,
|
|
output_tokens BIGINT NOT NULL DEFAULT 0,
|
|
estimated_cost NUMERIC(12, 6) NOT NULL DEFAULT 0,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
UNIQUE(metric_date, model_alias)
|
|
);
|
|
|
|
-- Indexes for metrics queries
|
|
CREATE INDEX idx_ai_metrics_daily_date ON ai_metrics_daily(metric_date DESC);
|
|
CREATE INDEX idx_ai_metrics_hourly_hour ON ai_metrics_hourly(metric_hour DESC);
|
|
CREATE INDEX idx_ai_metrics_feature ON ai_metrics_by_feature(metric_date DESC, feature_code);
|
|
CREATE INDEX idx_ai_metrics_model ON ai_metrics_by_model(metric_date DESC, model_alias);
|
|
|
|
-- Function to aggregate daily metrics
|
|
-- This should be called by a cron job daily
|
|
CREATE OR REPLACE FUNCTION aggregate_ai_metrics_daily(target_date DATE)
|
|
RETURNS VOID AS $$
|
|
BEGIN
|
|
-- Insert or update daily aggregate
|
|
INSERT INTO ai_metrics_daily (
|
|
metric_date,
|
|
total_requests,
|
|
total_credits_charged,
|
|
total_credits_refunded,
|
|
total_input_tokens,
|
|
total_output_tokens,
|
|
total_tokens,
|
|
failed_requests,
|
|
unique_active_users
|
|
)
|
|
SELECT
|
|
target_date,
|
|
COUNT(*),
|
|
SUM(CASE WHEN status = 'success' THEN credits_charged ELSE 0 END),
|
|
0, -- Refunds handled separately
|
|
SUM(COALESCE(input_tokens, 0)),
|
|
SUM(COALESCE(output_tokens, 0)),
|
|
SUM(COALESCE(total_tokens, 0)),
|
|
COUNT(CASE WHEN status = 'error' THEN 1 END),
|
|
COUNT(DISTINCT user_id)
|
|
FROM ai_usage_logs
|
|
WHERE DATE(created_at) = target_date
|
|
ON CONFLICT (metric_date) DO UPDATE SET
|
|
total_requests = EXCLUDED.total_requests,
|
|
total_credits_charged = EXCLUDED.total_credits_charged,
|
|
total_input_tokens = EXCLUDED.total_input_tokens,
|
|
total_output_tokens = EXCLUDED.total_output_tokens,
|
|
total_tokens = EXCLUDED.total_tokens,
|
|
failed_requests = EXCLUDED.failed_requests,
|
|
unique_active_users = EXCLUDED.unique_active_users,
|
|
updated_at = NOW();
|
|
|
|
-- Aggregate by feature
|
|
INSERT INTO ai_metrics_by_feature (metric_date, feature_code, request_count, credits_charged, unique_users)
|
|
SELECT
|
|
target_date,
|
|
feature_code,
|
|
COUNT(*),
|
|
SUM(credits_charged),
|
|
COUNT(DISTINCT user_id)
|
|
FROM ai_usage_logs
|
|
WHERE DATE(created_at) = target_date
|
|
GROUP BY feature_code
|
|
ON CONFLICT (metric_date, feature_code) DO UPDATE SET
|
|
request_count = EXCLUDED.request_count,
|
|
credits_charged = EXCLUDED.credits_charged,
|
|
unique_users = EXCLUDED.unique_users;
|
|
|
|
-- Aggregate by model
|
|
INSERT INTO ai_metrics_by_model (metric_date, model_alias, request_count, input_tokens, output_tokens)
|
|
SELECT
|
|
target_date,
|
|
model_alias,
|
|
COUNT(*),
|
|
SUM(COALESCE(input_tokens, 0)),
|
|
SUM(COALESCE(output_tokens, 0))
|
|
FROM ai_usage_logs
|
|
WHERE DATE(created_at) = target_date
|
|
GROUP BY model_alias
|
|
ON CONFLICT (metric_date, model_alias) DO UPDATE SET
|
|
request_count = EXCLUDED.request_count,
|
|
input_tokens = EXCLUDED.input_tokens,
|
|
output_tokens = EXCLUDED.output_tokens;
|
|
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
COMMIT;
|