Spaces:
Running
Running
| -- ===================================================== | |
| -- USER LOGS MIGRATION | |
| -- Run this in: Supabase Dashboard → SQL Editor → New query | |
| -- ===================================================== | |
| -- Table 1: user_logs | |
| -- Records every chat message sent through the AI. | |
| CREATE TABLE IF NOT EXISTS public.user_logs ( | |
| id UUID DEFAULT gen_random_uuid() PRIMARY KEY, | |
| user_id UUID REFERENCES auth.users(id) NOT NULL, | |
| query TEXT, | |
| route TEXT, -- 'sql_query' | 'rag_retrieval' | 'tavily_search' | |
| sql_query TEXT, -- generated SQL (only for sql_query route) | |
| answer TEXT, -- truncated bot reply (up to 8 000 chars) | |
| answer_length INTEGER, -- character count of the full bot reply | |
| total_tokens INTEGER, | |
| prompt_tokens INTEGER, | |
| completion_tokens INTEGER, | |
| has_error BOOLEAN DEFAULT FALSE, | |
| duration_ms INTEGER, -- total end-to-end request duration (ms) | |
| -- Observability & eval fields | |
| trace_id UUID, -- unique ID per request for distributed tracing | |
| session_id TEXT, -- client session UUID for conversation continuity | |
| routing_confidence NUMERIC(5,3), -- 0–1 keyword-density score that drove agent selection | |
| eval_score NUMERIC(5,3), -- 0–1 answer quality score from AnswerEvalHook | |
| created_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| ALTER TABLE public.user_logs ENABLE ROW LEVEL SECURITY; | |
| -- Users can read their own chat logs (e.g. history view in the dashboard). | |
| -- The backend uses the service role key and bypasses RLS on INSERT. | |
| CREATE POLICY "Users can view their own logs" | |
| ON public.user_logs FOR SELECT | |
| USING (auth.uid() = user_id); | |
| -- Index for fast per-user analytics queries | |
| CREATE INDEX IF NOT EXISTS idx_user_logs_user_id ON public.user_logs (user_id); | |
| CREATE INDEX IF NOT EXISTS idx_user_logs_created_at ON public.user_logs (created_at DESC); | |
| -- trace_id identifies exactly one chat turn; unique so view joins stay 1:1. | |
| DROP INDEX IF EXISTS idx_user_logs_trace_id; | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_user_logs_trace_id | |
| ON public.user_logs (trace_id) WHERE trace_id IS NOT NULL; | |
| CREATE INDEX IF NOT EXISTS idx_user_logs_session_id ON public.user_logs (session_id); | |
| -- ===================================================== | |
| -- ALTER scripts: add new columns to an existing table | |
| -- Safe to run even if the table already exists. | |
| -- ===================================================== | |
| ALTER TABLE public.user_logs ADD COLUMN IF NOT EXISTS answer TEXT; | |
| ALTER TABLE public.user_logs ADD COLUMN IF NOT EXISTS trace_id UUID; | |
| ALTER TABLE public.user_logs ADD COLUMN IF NOT EXISTS session_id TEXT; | |
| ALTER TABLE public.user_logs ADD COLUMN IF NOT EXISTS routing_confidence NUMERIC(5,3); | |
| ALTER TABLE public.user_logs ADD COLUMN IF NOT EXISTS eval_score NUMERIC(5,3); | |
| -- ===================================================== | |
| -- Table 2: user_login_logs | |
| -- Records every time a user signs in from the frontend. | |
| -- ===================================================== | |
| CREATE TABLE IF NOT EXISTS public.user_login_logs ( | |
| id UUID DEFAULT gen_random_uuid() PRIMARY KEY, | |
| user_id UUID REFERENCES auth.users(id) NOT NULL, | |
| ip_address TEXT, | |
| user_agent TEXT, | |
| login_method TEXT, | |
| login_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| ALTER TABLE public.user_login_logs ENABLE ROW LEVEL SECURITY; | |
| CREATE POLICY "Users can view their own login logs" | |
| ON public.user_login_logs FOR SELECT | |
| USING (auth.uid() = user_id); | |
| CREATE INDEX IF NOT EXISTS idx_user_login_logs_user_id ON public.user_login_logs (user_id); | |
| -- Backfill columns that may have been added after table creation | |
| ALTER TABLE public.user_login_logs ADD COLUMN IF NOT EXISTS ip_address TEXT; | |
| ALTER TABLE public.user_login_logs ADD COLUMN IF NOT EXISTS user_agent TEXT; | |
| ALTER TABLE public.user_login_logs ADD COLUMN IF NOT EXISTS login_method TEXT; | |
| -- ===================================================== | |
| -- Table 3: feedback_logs | |
| -- Records thumbs-up / thumbs-down ratings per chat turn. | |
| -- Joined to user_logs via trace_id for eval analysis. | |
| -- ===================================================== | |
| CREATE TABLE IF NOT EXISTS public.feedback_logs ( | |
| id UUID DEFAULT gen_random_uuid() PRIMARY KEY, | |
| user_id UUID REFERENCES auth.users(id) NOT NULL, | |
| trace_id UUID NOT NULL, | |
| rating SMALLINT NOT NULL CHECK (rating IN (-1, 1)), | |
| comment TEXT CHECK (char_length(comment) <= 500), | |
| created_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| ALTER TABLE public.feedback_logs ENABLE ROW LEVEL SECURITY; | |
| -- Users can read their own feedback; backend inserts via service role. | |
| CREATE POLICY "Users can view their own feedback" | |
| ON public.feedback_logs FOR SELECT | |
| USING (auth.uid() = user_id); | |
| CREATE INDEX IF NOT EXISTS idx_feedback_logs_trace_id ON public.feedback_logs (trace_id); | |
| CREATE INDEX IF NOT EXISTS idx_feedback_logs_created_at ON public.feedback_logs (created_at DESC); | |
| -- One rating per user per chat turn; the backend upserts on re-rating. | |
| -- Also serves as the per-user lookup index. | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_feedback_logs_user_trace | |
| ON public.feedback_logs (user_id, trace_id); | |
| -- ===================================================== | |
| -- Convenience view: eval summary per route | |
| -- Useful for a quick health-check dashboard. | |
| -- ===================================================== | |
| -- security_invoker: run with the caller's permissions so RLS on the | |
| -- underlying tables applies (otherwise the view bypasses RLS entirely). | |
| CREATE OR REPLACE VIEW public.v_eval_summary | |
| WITH (security_invoker = true) AS | |
| SELECT | |
| route, | |
| COUNT(*) AS total_requests, | |
| ROUND(AVG(eval_score)::NUMERIC, 3) AS avg_eval_score, | |
| ROUND(AVG(routing_confidence)::NUMERIC,3) AS avg_routing_confidence, | |
| ROUND(AVG(duration_ms)) AS avg_duration_ms, | |
| SUM(CASE WHEN has_error THEN 1 ELSE 0 END) AS error_count, | |
| SUM(total_tokens) AS total_tokens_used, | |
| DATE_TRUNC('day', created_at) AS day | |
| FROM public.user_logs | |
| GROUP BY route, DATE_TRUNC('day', created_at); | |
| -- ===================================================== | |
| -- Convenience view: feedback join with eval scores | |
| -- ===================================================== | |
| -- DROP first: CREATE OR REPLACE cannot reorder/insert view columns. | |
| DROP VIEW IF EXISTS public.v_feedback_with_eval; | |
| CREATE VIEW public.v_feedback_with_eval | |
| WITH (security_invoker = true) AS | |
| SELECT | |
| f.id, | |
| f.user_id, | |
| f.trace_id, | |
| f.rating, | |
| f.comment, | |
| f.created_at AS rated_at, | |
| l.route, | |
| l.query, | |
| l.session_id, | |
| l.has_error, | |
| l.eval_score, | |
| l.routing_confidence, | |
| l.duration_ms, | |
| l.total_tokens, | |
| f.created_at - l.created_at AS time_to_feedback, | |
| -- Rows where the human rating and the automated eval disagree. | |
| (f.rating = -1 AND l.eval_score >= 0.8) | |
| OR (f.rating = 1 AND l.eval_score <= 0.3) AS eval_disagreement | |
| FROM public.feedback_logs f | |
| LEFT JOIN public.user_logs l ON l.trace_id = f.trace_id; | |