nxtgauge-backend-rust/crates/db/migrations/20260706600000_ai_observability.up.sql
Ashwin Kumar Sivakumar 0dd5045676 feat: Complete Ask Ash AI Credits implementation on high-performance branch (Tasks 1-10)
- Task 1: Admin endpoints for wallet management
- Task 2: AI Credits admin UI (pricing.tsx, credit.tsx)
- Task 3: Ollama security (NetworkPolicy, prompt validation, audit)
- 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)
- Task 7: Subscription lifecycle (plan upgrades/downgrades, 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)

Cherry-picked from main branch commit 3c0f45f
2026-07-06 01:49:16 +05:30

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;