-- Phase 4: Analytics rollup tables + per-user AI prefs + KB article views. -- The aggregator job in ai_phase4::aggregate_daily_stats rolls up -- ai_conversations into ai_daily_stats every 5 minutes. BEGIN; -- Daily rollup: one row per (date, user, persona, pillar, intent). -- All metrics come from ai_conversations + ai_feedback, never the raw rows. CREATE TABLE IF NOT EXISTS ai_daily_stats ( date DATE NOT NULL, user_id UUID, persona TEXT, pillar TEXT, intent TEXT, request_count INTEGER NOT NULL DEFAULT 0, kb_match_count INTEGER NOT NULL DEFAULT 0, ollama_calls INTEGER NOT NULL DEFAULT 0, fallback_count INTEGER NOT NULL DEFAULT 0, avg_response_ms INTEGER, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (date, user_id, persona, pillar, intent) ); CREATE INDEX IF NOT EXISTS idx_ai_daily_stats_date ON ai_daily_stats (date DESC); -- Per-user AI preferences (e.g. preferred model, language override). -- Kept small and simple; one row per user. CREATE TABLE IF NOT EXISTS ai_user_prefs ( user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, preferred_model TEXT, preferred_language VARCHAR(8) NOT NULL DEFAULT 'en', -- Bump when this user changes their prefs so the clients can re-fetch. updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Track which KB articles users actually look at after Ask Ash surfaces -- them. Used by the enhanced KB matcher for recency boosting. CREATE TABLE IF NOT EXISTS ai_article_views ( id BIGSERIAL PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- kb_articles.id is UUID. article_id UUID NOT NULL REFERENCES kb_articles(id) ON DELETE CASCADE, helpful BOOLEAN, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_ai_article_views_article ON ai_article_views (article_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_ai_article_views_user ON ai_article_views (user_id, created_at DESC); COMMIT;