- 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>
285 lines
9.8 KiB
PL/PgSQL
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;
|