trading-platform-database-v2/ddl/schemas/admin/tables/002_platform_analytics.sql
rckrdmrd b86dfa2e06 [DDL] feat: Sprint 1 - Add 12 tables for users, admin, notifications, market_data
## New Tables Created (Sprint 1 - DDL Roadmap Q1-2026)

### users schema (4 tables):
- profiles: Extended user profile information
- user_settings: User preferences and configurations
- kyc_verifications: KYC/AML verification records
- risk_profiles: Trading risk assessment profiles

### admin schema (3 tables):
- admin_roles: Platform administrative roles
- platform_analytics: Aggregated platform metrics
- api_keys: Programmatic API access keys

### notifications schema (1 table):
- notifications: Multi-channel notification system

### market_data schema (4 tables):
- tickers: Financial instruments catalog
- ohlcv_5m: 5-minute OHLCV price data
- technical_indicators: Pre-calculated TA indicators
- ohlcv_5m_staging: Staging table for data ingestion

## Features:
- Multi-tenancy with RLS policies
- Comprehensive indexes for query optimization
- Triggers for computed fields and timestamps
- Helper functions for common operations
- Views for dashboard and reporting
- Full GRANTS configuration

Roadmap: orchestration/planes/ROADMAP-IMPLEMENTACION-DDL-2026-Q1.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 19:41:53 -06:00

277 lines
9.2 KiB
PL/PgSQL

-- ============================================================================
-- SCHEMA: admin
-- TABLE: platform_analytics
-- DESCRIPTION: Metricas agregadas de la plataforma
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 1 - DDL Implementation Roadmap Q1-2026
-- ============================================================================
-- Enum para tipo de metrica
DO $$ BEGIN
CREATE TYPE admin.metric_type AS ENUM (
'counter', -- Contador simple
'gauge', -- Valor actual
'histogram', -- Distribucion
'summary', -- Resumen estadistico
'rate' -- Tasa por periodo
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Enum para granularidad
DO $$ BEGIN
CREATE TYPE admin.time_granularity AS ENUM (
'minute',
'hour',
'day',
'week',
'month',
'quarter',
'year'
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Tabla de Metricas de Plataforma
CREATE TABLE IF NOT EXISTS admin.platform_analytics (
-- Identificadores
id BIGSERIAL PRIMARY KEY,
-- Scope
tenant_id UUID REFERENCES tenants.tenants(id), -- NULL = plataforma global
-- Metrica
metric_name VARCHAR(100) NOT NULL,
metric_type admin.metric_type NOT NULL DEFAULT 'gauge',
category VARCHAR(50) NOT NULL, -- 'users', 'trading', 'revenue', 'performance'
-- Periodo
period_start TIMESTAMPTZ NOT NULL,
period_end TIMESTAMPTZ NOT NULL,
granularity admin.time_granularity NOT NULL DEFAULT 'day',
-- Valores
value DECIMAL(20, 6) NOT NULL,
previous_value DECIMAL(20, 6), -- Valor periodo anterior (para calcular delta)
delta_value DECIMAL(20, 6), -- Cambio vs periodo anterior
delta_percent DECIMAL(10, 4), -- Cambio porcentual
-- Estadisticas (para histogramas/summaries)
min_value DECIMAL(20, 6),
max_value DECIMAL(20, 6),
avg_value DECIMAL(20, 6),
median_value DECIMAL(20, 6),
p95_value DECIMAL(20, 6),
p99_value DECIMAL(20, 6),
stddev_value DECIMAL(20, 6),
sample_count BIGINT,
-- Dimensiones adicionales
dimensions JSONB DEFAULT '{}'::JSONB, -- Dimensiones para desglose
-- Metadata
source VARCHAR(50) NOT NULL DEFAULT 'system', -- Origen del dato
is_estimated BOOLEAN NOT NULL DEFAULT FALSE, -- Dato estimado vs real
confidence_level DECIMAL(5, 4), -- Nivel de confianza (0-1)
-- Timestamps
collected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Constraints
CONSTRAINT analytics_period_check CHECK (period_end > period_start),
CONSTRAINT analytics_unique_metric UNIQUE (tenant_id, metric_name, granularity, period_start)
);
COMMENT ON TABLE admin.platform_analytics IS
'Metricas agregadas de la plataforma para dashboards y reportes';
COMMENT ON COLUMN admin.platform_analytics.tenant_id IS
'NULL para metricas globales de plataforma, UUID para metricas por tenant';
COMMENT ON COLUMN admin.platform_analytics.dimensions IS
'Dimensiones adicionales como {"country": "MX", "plan": "premium"}';
-- Particionamiento por fecha (recomendado para produccion)
-- CREATE TABLE admin.platform_analytics_partitioned (
-- LIKE admin.platform_analytics INCLUDING ALL
-- ) PARTITION BY RANGE (period_start);
-- Indices
CREATE INDEX IF NOT EXISTS idx_analytics_metric_period
ON admin.platform_analytics(metric_name, period_start DESC);
CREATE INDEX IF NOT EXISTS idx_analytics_tenant_metric
ON admin.platform_analytics(tenant_id, metric_name, period_start DESC)
WHERE tenant_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_analytics_category
ON admin.platform_analytics(category, period_start DESC);
CREATE INDEX IF NOT EXISTS idx_analytics_granularity
ON admin.platform_analytics(granularity, period_start DESC);
CREATE INDEX IF NOT EXISTS idx_analytics_global
ON admin.platform_analytics(metric_name, granularity, period_start DESC)
WHERE tenant_id IS NULL;
-- GIN index para dimensiones
CREATE INDEX IF NOT EXISTS idx_analytics_dimensions_gin
ON admin.platform_analytics USING GIN (dimensions);
-- Trigger para calcular deltas automaticamente
CREATE OR REPLACE FUNCTION admin.calculate_analytics_delta()
RETURNS TRIGGER AS $$
DECLARE
v_prev RECORD;
v_period_interval INTERVAL;
BEGIN
-- Calcular intervalo basado en granularidad
CASE NEW.granularity
WHEN 'minute' THEN v_period_interval := INTERVAL '1 minute';
WHEN 'hour' THEN v_period_interval := INTERVAL '1 hour';
WHEN 'day' THEN v_period_interval := INTERVAL '1 day';
WHEN 'week' THEN v_period_interval := INTERVAL '1 week';
WHEN 'month' THEN v_period_interval := INTERVAL '1 month';
WHEN 'quarter' THEN v_period_interval := INTERVAL '3 months';
WHEN 'year' THEN v_period_interval := INTERVAL '1 year';
END CASE;
-- Buscar valor anterior
SELECT value INTO v_prev
FROM admin.platform_analytics
WHERE metric_name = NEW.metric_name
AND granularity = NEW.granularity
AND (tenant_id = NEW.tenant_id OR (tenant_id IS NULL AND NEW.tenant_id IS NULL))
AND period_start = NEW.period_start - v_period_interval
LIMIT 1;
IF v_prev IS NOT NULL THEN
NEW.previous_value := v_prev.value;
NEW.delta_value := NEW.value - v_prev.value;
IF v_prev.value != 0 THEN
NEW.delta_percent := ((NEW.value - v_prev.value) / v_prev.value) * 100;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS analytics_calc_delta ON admin.platform_analytics;
CREATE TRIGGER analytics_calc_delta
BEFORE INSERT ON admin.platform_analytics
FOR EACH ROW
EXECUTE FUNCTION admin.calculate_analytics_delta();
-- Funcion para insertar o actualizar metrica
CREATE OR REPLACE FUNCTION admin.upsert_metric(
p_metric_name VARCHAR(100),
p_category VARCHAR(50),
p_value DECIMAL(20, 6),
p_granularity admin.time_granularity DEFAULT 'day',
p_tenant_id UUID DEFAULT NULL,
p_metric_type admin.metric_type DEFAULT 'gauge',
p_dimensions JSONB DEFAULT '{}'::JSONB
)
RETURNS BIGINT AS $$
DECLARE
v_period_start TIMESTAMPTZ;
v_period_end TIMESTAMPTZ;
v_id BIGINT;
BEGIN
-- Calcular periodo basado en granularidad
CASE p_granularity
WHEN 'minute' THEN
v_period_start := DATE_TRUNC('minute', NOW());
v_period_end := v_period_start + INTERVAL '1 minute';
WHEN 'hour' THEN
v_period_start := DATE_TRUNC('hour', NOW());
v_period_end := v_period_start + INTERVAL '1 hour';
WHEN 'day' THEN
v_period_start := DATE_TRUNC('day', NOW());
v_period_end := v_period_start + INTERVAL '1 day';
WHEN 'week' THEN
v_period_start := DATE_TRUNC('week', NOW());
v_period_end := v_period_start + INTERVAL '1 week';
WHEN 'month' THEN
v_period_start := DATE_TRUNC('month', NOW());
v_period_end := v_period_start + INTERVAL '1 month';
WHEN 'quarter' THEN
v_period_start := DATE_TRUNC('quarter', NOW());
v_period_end := v_period_start + INTERVAL '3 months';
WHEN 'year' THEN
v_period_start := DATE_TRUNC('year', NOW());
v_period_end := v_period_start + INTERVAL '1 year';
END CASE;
INSERT INTO admin.platform_analytics (
tenant_id, metric_name, metric_type, category,
period_start, period_end, granularity,
value, dimensions
) VALUES (
p_tenant_id, p_metric_name, p_metric_type, p_category,
v_period_start, v_period_end, p_granularity,
p_value, p_dimensions
)
ON CONFLICT (tenant_id, metric_name, granularity, period_start)
DO UPDATE SET
value = EXCLUDED.value,
dimensions = EXCLUDED.dimensions,
collected_at = NOW()
RETURNING id INTO v_id;
RETURN v_id;
END;
$$ LANGUAGE plpgsql;
-- Vista de metricas recientes
CREATE OR REPLACE VIEW admin.v_recent_metrics AS
SELECT
metric_name,
category,
granularity,
tenant_id,
value,
delta_percent,
period_start,
collected_at
FROM admin.platform_analytics
WHERE collected_at > NOW() - INTERVAL '24 hours'
ORDER BY metric_name, period_start DESC;
-- Vista de KPIs principales
CREATE OR REPLACE VIEW admin.v_platform_kpis AS
SELECT
metric_name,
value AS current_value,
previous_value,
delta_percent AS change_percent,
period_start,
granularity
FROM admin.platform_analytics
WHERE tenant_id IS NULL -- Solo metricas globales
AND granularity = 'day'
AND metric_name IN (
'total_users',
'active_users_daily',
'total_trades',
'total_volume_usd',
'revenue_usd',
'new_signups',
'churn_rate'
)
AND period_start = DATE_TRUNC('day', NOW())
ORDER BY metric_name;
-- Grants
GRANT SELECT, INSERT, UPDATE ON admin.platform_analytics TO trading_app;
GRANT SELECT ON admin.platform_analytics TO trading_readonly;
GRANT USAGE, SELECT ON SEQUENCE admin.platform_analytics_id_seq TO trading_app;
GRANT SELECT ON admin.v_recent_metrics TO trading_app;
GRANT SELECT ON admin.v_platform_kpis TO trading_app;
GRANT EXECUTE ON FUNCTION admin.upsert_metric TO trading_app;