-- Migration: Dashboard & Real-Time Metrics -- Created: 2026-04-19 -- Purpose: Support management dashboard with real-time request tracking and aggregated metrics -- Table: Dashboard request log (append-only, 72-hour retention) CREATE TABLE IF NOT EXISTS dashboard_request_log ( id SERIAL PRIMARY KEY, request_id VARCHAR(50) NOT NULL UNIQUE, caller VARCHAR(100) NOT NULL, task_type VARCHAR(50), model VARCHAR(100) NOT NULL, status VARCHAR(50) NOT NULL, confidence_score DECIMAL(3,2), tokens_in INT NOT NULL DEFAULT 0, tokens_out INT NOT NULL DEFAULT 0, cost_usd DECIMAL(10,6) NOT NULL DEFAULT 0, latency_ms INT NOT NULL DEFAULT 0, fallback_used BOOLEAN DEFAULT FALSE, error_message TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at_epoch INT NOT NULL, INDEX idx_created_desc (created_at DESC), INDEX idx_caller_created (caller, created_at DESC), INDEX idx_status_created (status, created_at DESC), INDEX idx_model_created (model, created_at DESC), INDEX idx_task_created (task_type, created_at DESC), INDEX idx_epoch (created_at_epoch DESC) ); -- Table: Pre-aggregated metrics timeseries (1-minute buckets, 90-day retention) CREATE TABLE IF NOT EXISTS metrics_timeseries ( id SERIAL PRIMARY KEY, bucket_time TIMESTAMP NOT NULL, bucket_time_epoch INT NOT NULL, -- Counts request_count INT NOT NULL DEFAULT 0, success_count INT NOT NULL DEFAULT 0, error_count INT NOT NULL DEFAULT 0, fallback_count INT NOT NULL DEFAULT 0, -- Latency metrics (ms) avg_latency_ms DECIMAL(10,2), p50_latency_ms INT, p95_latency_ms INT, p99_latency_ms INT, max_latency_ms INT, -- Token metrics total_tokens_in INT NOT NULL DEFAULT 0, total_tokens_out INT NOT NULL DEFAULT 0, avg_tokens_in DECIMAL(10,2), avg_tokens_out DECIMAL(10,2), -- Cost metrics (USD) total_cost_usd DECIMAL(10,6) NOT NULL DEFAULT 0, avg_cost_usd DECIMAL(10,6), -- Confidence metrics avg_confidence DECIMAL(3,2), min_confidence DECIMAL(3,2), -- Model distribution (top 3) top_model_1 VARCHAR(100), top_model_1_count INT, top_model_2 VARCHAR(100), top_model_2_count INT, top_model_3 VARCHAR(100), top_model_3_count INT, -- Status distribution status_approved INT DEFAULT 0, status_warning INT DEFAULT 0, status_rejected INT DEFAULT 0, status_pending INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY unique_bucket_time (bucket_time), INDEX idx_bucket_time_desc (bucket_time DESC), INDEX idx_bucket_epoch (bucket_time_epoch DESC) ); -- Table: Per-caller metrics (1-minute buckets) CREATE TABLE IF NOT EXISTS caller_metrics_timeseries ( id SERIAL PRIMARY KEY, bucket_time TIMESTAMP NOT NULL, caller VARCHAR(100) NOT NULL, request_count INT NOT NULL DEFAULT 0, success_count INT NOT NULL DEFAULT 0, error_count INT NOT NULL DEFAULT 0, avg_latency_ms DECIMAL(10,2), total_cost_usd DECIMAL(10,6) NOT NULL DEFAULT 0, avg_confidence DECIMAL(3,2), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY unique_bucket_caller (bucket_time, caller), INDEX idx_bucket_time_desc (bucket_time DESC), INDEX idx_caller (caller) ); -- Table: Per-model metrics (1-minute buckets) CREATE TABLE IF NOT EXISTS model_metrics_timeseries ( id SERIAL PRIMARY KEY, bucket_time TIMESTAMP NOT NULL, model VARCHAR(100) NOT NULL, request_count INT NOT NULL DEFAULT 0, success_count INT NOT NULL DEFAULT 0, error_count INT NOT NULL DEFAULT 0, avg_latency_ms DECIMAL(10,2), total_cost_usd DECIMAL(10,6) NOT NULL DEFAULT 0, avg_confidence DECIMAL(3,2), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY unique_bucket_model (bucket_time, model), INDEX idx_bucket_time_desc (bucket_time DESC), INDEX idx_model (model) ); -- Table: Dashboard cache (frequently accessed aggregates) CREATE TABLE IF NOT EXISTS dashboard_cache ( id SERIAL PRIMARY KEY, cache_key VARCHAR(255) NOT NULL UNIQUE, cache_value JSON NOT NULL, ttl_seconds INT NOT NULL DEFAULT 60, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, INDEX idx_expires_at (expires_at) ); -- Create event for auto-cleanup of old dashboard request logs (72 hour retention) CREATE EVENT IF NOT EXISTS cleanup_dashboard_requests ON SCHEDULE EVERY 1 HOUR STARTS CURRENT_TIMESTAMP DO DELETE FROM dashboard_request_log WHERE created_at < DATE_SUB(NOW(), INTERVAL 72 HOUR); -- Create event for auto-cleanup of old metrics (90 day retention) CREATE EVENT IF NOT EXISTS cleanup_metrics_timeseries ON SCHEDULE EVERY 1 HOUR STARTS CURRENT_TIMESTAMP DO DELETE FROM metrics_timeseries WHERE bucket_time < DATE_SUB(NOW(), INTERVAL 90 DAY); -- Create event for auto-cleanup of expired cache entries CREATE EVENT IF NOT EXISTS cleanup_dashboard_cache ON SCHEDULE EVERY 5 MINUTE STARTS CURRENT_TIMESTAMP DO DELETE FROM dashboard_cache WHERE expires_at < NOW(); -- Create procedure to aggregate dashboard_request_log into metrics_timeseries DELIMITER // CREATE PROCEDURE IF NOT EXISTS aggregate_metrics_to_timeseries() BEGIN INSERT INTO metrics_timeseries ( bucket_time, bucket_time_epoch, request_count, success_count, error_count, fallback_count, avg_latency_ms, p50_latency_ms, p95_latency_ms, p99_latency_ms, max_latency_ms, total_tokens_in, total_tokens_out, avg_tokens_in, avg_tokens_out, total_cost_usd, avg_cost_usd, avg_confidence, min_confidence, top_model_1, top_model_1_count, top_model_2, top_model_2_count, top_model_3, top_model_3_count, status_approved, status_warning, status_rejected, status_pending ) SELECT DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:00') AS bucket_time, UNIX_TIMESTAMP(DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:00')) AS bucket_time_epoch, COUNT(*) AS request_count, SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) AS success_count, SUM(CASE WHEN status IN ('rejected', 'error') THEN 1 ELSE 0 END) AS error_count, SUM(CASE WHEN fallback_used = TRUE THEN 1 ELSE 0 END) AS fallback_count, AVG(latency_ms) AS avg_latency_ms, NULL AS p50_latency_ms, NULL AS p95_latency_ms, NULL AS p99_latency_ms, MAX(latency_ms) AS max_latency_ms, SUM(tokens_in) AS total_tokens_in, SUM(tokens_out) AS total_tokens_out, AVG(tokens_in) AS avg_tokens_in, AVG(tokens_out) AS avg_tokens_out, SUM(cost_usd) AS total_cost_usd, AVG(cost_usd) AS avg_cost_usd, AVG(confidence_score) AS avg_confidence, MIN(confidence_score) AS min_confidence, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0 FROM dashboard_request_log WHERE created_at >= DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 MINUTE), '%Y-%m-%d %H:%i:00') AND created_at < DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:00') GROUP BY bucket_time ON DUPLICATE KEY UPDATE request_count = VALUES(request_count), success_count = VALUES(success_count), error_count = VALUES(error_count), fallback_count = VALUES(fallback_count), avg_latency_ms = VALUES(avg_latency_ms), max_latency_ms = VALUES(max_latency_ms), total_tokens_in = VALUES(total_tokens_in), total_tokens_out = VALUES(total_tokens_out), avg_tokens_in = VALUES(avg_tokens_in), avg_tokens_out = VALUES(avg_tokens_out), total_cost_usd = VALUES(total_cost_usd), avg_cost_usd = VALUES(avg_cost_usd), avg_confidence = VALUES(avg_confidence), min_confidence = VALUES(min_confidence); END // DELIMITER ; -- Schedule the aggregation procedure to run every minute CREATE EVENT IF NOT EXISTS aggregate_metrics_every_minute ON SCHEDULE EVERY 1 MINUTE STARTS CURRENT_TIMESTAMP DO CALL aggregate_metrics_to_timeseries();