-- ============================================================================ -- SCHEMA: llm -- TABLE: llm_proactive_notifications -- DESCRIPTION: Notificaciones proactivas generadas por el agente LLM -- VERSION: 1.0.0 -- CREATED: 2026-01-16 -- SPRINT: Sprint 5 - DDL Implementation Roadmap Q1-2026 -- ============================================================================ -- Enum para tipo de notificacion proactiva DO $$ BEGIN CREATE TYPE llm.proactive_notification_type AS ENUM ( 'market_alert', -- Alerta de mercado 'trading_opportunity', -- Oportunidad de trading 'portfolio_update', -- Actualizacion de portafolio 'risk_warning', -- Advertencia de riesgo 'news_digest', -- Resumen de noticias 'learning_reminder', -- Recordatorio de aprendizaje 'goal_progress', -- Progreso hacia metas 'weekly_summary', -- Resumen semanal 'strategy_insight', -- Insight de estrategia 'market_open', -- Apertura de mercado 'market_close', -- Cierre de mercado 'custom' -- Personalizado ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para prioridad DO $$ BEGIN CREATE TYPE llm.notification_priority AS ENUM ( 'low', 'normal', 'high', 'urgent' ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para estado de notificacion DO $$ BEGIN CREATE TYPE llm.notification_status AS ENUM ( 'pending', -- Pendiente de enviar 'scheduled', -- Programada 'sent', -- Enviada 'delivered', -- Entregada 'read', -- Leida 'clicked', -- Click en accion 'dismissed', -- Descartada 'failed', -- Fallo en envio 'expired' -- Expirada ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Tabla de Notificaciones Proactivas CREATE TABLE IF NOT EXISTS llm.llm_proactive_notifications ( -- Identificadores id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE, -- Tipo y prioridad type llm.proactive_notification_type NOT NULL, priority llm.notification_priority NOT NULL DEFAULT 'normal', status llm.notification_status NOT NULL DEFAULT 'pending', -- Contenido title VARCHAR(255) NOT NULL, body TEXT NOT NULL, summary TEXT, -- Resumen corto para push -- Accion action_type VARCHAR(50), -- 'open_chart', 'view_position', 'start_lesson' action_url TEXT, action_data JSONB DEFAULT '{}'::JSONB, -- Contexto context_type VARCHAR(50), -- 'symbol', 'position', 'course' context_id UUID, related_symbols VARCHAR(20)[], -- Canales de entrega channels VARCHAR(20)[] DEFAULT ARRAY['in_app'], -- 'in_app', 'push', 'email', 'sms' channel_results JSONB DEFAULT '{}'::JSONB, -- Resultado por canal -- Generacion generated_by VARCHAR(100), -- Modelo/servicio que genero generation_reason TEXT, generation_data JSONB DEFAULT '{}'::JSONB, -- Datos usados para generar -- Confianza y relevancia confidence_score DECIMAL(5, 4), -- 0-1 relevance_score DECIMAL(5, 4), -- 0-1 personalization_score DECIMAL(5, 4), -- 0-1 -- Programacion scheduled_for TIMESTAMPTZ, send_window_start TIME, -- Ventana de envio preferida send_window_end TIME, timezone VARCHAR(50) DEFAULT 'UTC', -- Envio sent_at TIMESTAMPTZ, delivered_at TIMESTAMPTZ, read_at TIMESTAMPTZ, clicked_at TIMESTAMPTZ, dismissed_at TIMESTAMPTZ, -- Expiracion expires_at TIMESTAMPTZ, -- Feedback user_feedback VARCHAR(20), -- 'helpful', 'not_helpful', 'spam' feedback_text TEXT, feedback_at TIMESTAMPTZ, -- Agrupacion group_key VARCHAR(100), -- Para agrupar notificaciones similares is_grouped BOOLEAN NOT NULL DEFAULT FALSE, group_count INTEGER DEFAULT 1, -- Metadata tags VARCHAR(50)[], metadata JSONB DEFAULT '{}'::JSONB, -- Timestamps created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); COMMENT ON TABLE llm.llm_proactive_notifications IS 'Notificaciones proactivas generadas por el agente LLM para engagement del usuario'; COMMENT ON COLUMN llm.llm_proactive_notifications.confidence_score IS 'Confianza del modelo en la relevancia de la notificacion'; -- Indices CREATE INDEX IF NOT EXISTS idx_proactive_notif_tenant ON llm.llm_proactive_notifications(tenant_id); CREATE INDEX IF NOT EXISTS idx_proactive_notif_user ON llm.llm_proactive_notifications(user_id); CREATE INDEX IF NOT EXISTS idx_proactive_notif_status ON llm.llm_proactive_notifications(status); CREATE INDEX IF NOT EXISTS idx_proactive_notif_pending ON llm.llm_proactive_notifications(user_id, status, scheduled_for) WHERE status IN ('pending', 'scheduled'); CREATE INDEX IF NOT EXISTS idx_proactive_notif_type ON llm.llm_proactive_notifications(type, created_at DESC); CREATE INDEX IF NOT EXISTS idx_proactive_notif_priority ON llm.llm_proactive_notifications(priority, status) WHERE priority IN ('high', 'urgent') AND status = 'pending'; CREATE INDEX IF NOT EXISTS idx_proactive_notif_scheduled ON llm.llm_proactive_notifications(scheduled_for) WHERE status = 'scheduled' AND scheduled_for IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_proactive_notif_unread ON llm.llm_proactive_notifications(user_id, read_at) WHERE status IN ('sent', 'delivered') AND read_at IS NULL; CREATE INDEX IF NOT EXISTS idx_proactive_notif_group ON llm.llm_proactive_notifications(group_key) WHERE group_key IS NOT NULL; -- Trigger para updated_at DROP TRIGGER IF EXISTS proactive_notif_updated_at ON llm.llm_proactive_notifications; CREATE TRIGGER proactive_notif_updated_at BEFORE UPDATE ON llm.llm_proactive_notifications FOR EACH ROW EXECUTE FUNCTION llm.update_llm_timestamp(); -- Funcion para marcar como leida CREATE OR REPLACE FUNCTION llm.mark_notification_read( p_notification_id UUID ) RETURNS VOID AS $$ BEGIN UPDATE llm.llm_proactive_notifications SET status = 'read', read_at = NOW() WHERE id = p_notification_id AND read_at IS NULL; END; $$ LANGUAGE plpgsql; -- Funcion para obtener notificaciones pendientes CREATE OR REPLACE FUNCTION llm.get_pending_notifications( p_user_id UUID, p_limit INTEGER DEFAULT 20 ) RETURNS SETOF llm.llm_proactive_notifications AS $$ BEGIN RETURN QUERY SELECT * FROM llm.llm_proactive_notifications WHERE user_id = p_user_id AND status IN ('sent', 'delivered') AND read_at IS NULL AND (expires_at IS NULL OR expires_at > NOW()) ORDER BY CASE priority WHEN 'urgent' THEN 1 WHEN 'high' THEN 2 WHEN 'normal' THEN 3 ELSE 4 END, created_at DESC LIMIT p_limit; END; $$ LANGUAGE plpgsql; -- Funcion para contar no leidas CREATE OR REPLACE FUNCTION llm.count_unread_notifications(p_user_id UUID) RETURNS INTEGER AS $$ DECLARE v_count INTEGER; BEGIN SELECT COUNT(*) INTO v_count FROM llm.llm_proactive_notifications WHERE user_id = p_user_id AND status IN ('sent', 'delivered') AND read_at IS NULL AND (expires_at IS NULL OR expires_at > NOW()); RETURN v_count; END; $$ LANGUAGE plpgsql; -- Vista de notificaciones para enviar CREATE OR REPLACE VIEW llm.v_notifications_to_send AS SELECT * FROM llm.llm_proactive_notifications WHERE status = 'scheduled' AND scheduled_for <= NOW() AND (expires_at IS NULL OR expires_at > NOW()) ORDER BY priority DESC, scheduled_for; -- Vista de efectividad de notificaciones CREATE OR REPLACE VIEW llm.v_notification_effectiveness AS SELECT type, priority, COUNT(*) AS total_sent, COUNT(*) FILTER (WHERE read_at IS NOT NULL) AS read_count, COUNT(*) FILTER (WHERE clicked_at IS NOT NULL) AS clicked_count, ROUND((COUNT(*) FILTER (WHERE read_at IS NOT NULL)::DECIMAL / NULLIF(COUNT(*), 0) * 100), 2) AS read_rate, ROUND((COUNT(*) FILTER (WHERE clicked_at IS NOT NULL)::DECIMAL / NULLIF(COUNT(*) FILTER (WHERE read_at IS NOT NULL), 0) * 100), 2) AS click_rate, COUNT(*) FILTER (WHERE user_feedback = 'helpful') AS helpful_count, COUNT(*) FILTER (WHERE user_feedback = 'not_helpful') AS not_helpful_count FROM llm.llm_proactive_notifications WHERE status IN ('sent', 'delivered', 'read', 'clicked', 'dismissed') GROUP BY type, priority ORDER BY total_sent DESC; -- RLS Policies ALTER TABLE llm.llm_proactive_notifications ENABLE ROW LEVEL SECURITY; CREATE POLICY proactive_notif_tenant ON llm.llm_proactive_notifications FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY proactive_notif_user ON llm.llm_proactive_notifications FOR ALL USING (user_id = current_setting('app.current_user_id', true)::UUID); -- Grants GRANT SELECT, INSERT, UPDATE, DELETE ON llm.llm_proactive_notifications TO trading_app; GRANT SELECT ON llm.llm_proactive_notifications TO trading_readonly; GRANT SELECT ON llm.v_notifications_to_send TO trading_app; GRANT SELECT ON llm.v_notification_effectiveness TO trading_app; GRANT EXECUTE ON FUNCTION llm.mark_notification_read TO trading_app; GRANT EXECUTE ON FUNCTION llm.get_pending_notifications TO trading_app; GRANT EXECUTE ON FUNCTION llm.count_unread_notifications TO trading_app;