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