trading-platform-database-v2/ddl/schemas/llm/tables/004_llm_proactive_notifications.sql
rckrdmrd 62c811be45 [DDL] feat: Sprint 5 - Add llm schema with 5 tables
- conversations: Conversaciones con agente LLM
- conversation_messages: Mensajes individuales
- llm_tools_usage: Registro de uso de herramientas
- llm_proactive_notifications: Notificaciones proactivas
- llm_usage_limits: Limites de uso por usuario/plan VIP

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 20:13:33 -06:00

285 lines
9.8 KiB
PL/PgSQL

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