-- ============================================================================ -- SCHEMA: ml -- TABLE: predictions -- DESCRIPTION: Tabla de predicciones individuales generadas -- VERSION: 1.0.0 -- CREATED: 2026-01-10 -- DEPENDS: ml.prediction_purchases -- ============================================================================ -- Enum para tipo de prediccion DO $$ BEGIN CREATE TYPE ml.prediction_type AS ENUM ( 'AMD', -- Accumulation, Manipulation, Distribution 'RANGE', -- Range prediction 'TPSL', -- Take Profit / Stop Loss 'ICT_SMC', -- ICT Smart Money Concepts 'STRATEGY_ENSEMBLE' -- Combined strategies ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para clase de activo DO $$ BEGIN CREATE TYPE ml.asset_class AS ENUM ( 'FOREX', 'CRYPTO', 'INDICES', 'COMMODITIES' ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para estado de prediccion DO $$ BEGIN CREATE TYPE ml.prediction_status AS ENUM ( 'pending', -- Generada, esperando entrega 'delivered', -- Entregada al usuario 'expired', -- Expirada sin validar 'validated', -- Validada con resultado 'invalidated' -- Invalidada por error ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Tabla de predicciones individuales CREATE TABLE IF NOT EXISTS ml.predictions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, user_id UUID NOT NULL, -- Referencia a la compra purchase_id UUID NOT NULL REFERENCES ml.prediction_purchases(id), -- Tipo y activo prediction_type ml.prediction_type NOT NULL, asset VARCHAR(20) NOT NULL, asset_class ml.asset_class NOT NULL, timeframe VARCHAR(10) NOT NULL, -- Predicción direction VARCHAR(10) NOT NULL CHECK (direction IN ('LONG', 'SHORT', 'NEUTRAL')), entry_price DECIMAL(20, 8), target_price DECIMAL(20, 8), stop_loss DECIMAL(20, 8), confidence DECIMAL(5, 4) CHECK (confidence >= 0 AND confidence <= 1), -- Estado status ml.prediction_status NOT NULL DEFAULT 'pending', expires_at TIMESTAMPTZ NOT NULL, delivered_at TIMESTAMPTZ, -- Datos adicionales prediction_data JSONB DEFAULT '{}', metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW() ); COMMENT ON TABLE ml.predictions IS 'Predicciones individuales generadas por modelos ML'; COMMENT ON COLUMN ml.predictions.prediction_type IS 'Tipo de modelo usado para la prediccion'; COMMENT ON COLUMN ml.predictions.asset_class IS 'Clase de activo (FOREX, CRYPTO, etc.)'; COMMENT ON COLUMN ml.predictions.status IS 'Estado actual de la prediccion'; COMMENT ON COLUMN ml.predictions.confidence IS 'Nivel de confianza del modelo (0-1)'; -- Indices CREATE INDEX IF NOT EXISTS idx_pred_tenant ON ml.predictions(tenant_id); CREATE INDEX IF NOT EXISTS idx_pred_user ON ml.predictions(user_id); CREATE INDEX IF NOT EXISTS idx_pred_purchase ON ml.predictions(purchase_id); CREATE INDEX IF NOT EXISTS idx_pred_status ON ml.predictions(status); CREATE INDEX IF NOT EXISTS idx_pred_type ON ml.predictions(prediction_type); CREATE INDEX IF NOT EXISTS idx_pred_asset ON ml.predictions(asset); CREATE INDEX IF NOT EXISTS idx_pred_asset_class ON ml.predictions(asset_class); CREATE INDEX IF NOT EXISTS idx_pred_created ON ml.predictions(created_at DESC); CREATE INDEX IF NOT EXISTS idx_pred_expires ON ml.predictions(expires_at) WHERE status = 'pending'; -- Indice compuesto para busquedas comunes CREATE INDEX IF NOT EXISTS idx_pred_user_status ON ml.predictions(user_id, status, created_at DESC); -- RLS ALTER TABLE ml.predictions ENABLE ROW LEVEL SECURITY; CREATE POLICY pred_tenant ON ml.predictions FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); -- Grants GRANT SELECT, INSERT, UPDATE ON ml.predictions TO trading_app; GRANT SELECT ON ml.predictions TO trading_readonly; -- Actualizar FK en prediction_outcomes para referenciar predictions DO $$ BEGIN ALTER TABLE ml.prediction_outcomes DROP CONSTRAINT IF EXISTS prediction_outcomes_prediction_id_fkey; EXCEPTION WHEN undefined_object THEN null; END $$; ALTER TABLE ml.prediction_outcomes ADD CONSTRAINT prediction_outcomes_prediction_id_fkey FOREIGN KEY (prediction_id) REFERENCES ml.predictions(id) ON DELETE SET NULL; -- Funcion para generar prediccion desde un purchase CREATE OR REPLACE FUNCTION ml.generate_prediction( p_purchase_id UUID, p_prediction_type ml.prediction_type, p_asset VARCHAR(20), p_asset_class ml.asset_class, p_timeframe VARCHAR(10), p_direction VARCHAR(10), p_entry DECIMAL(20, 8) DEFAULT NULL, p_target DECIMAL(20, 8) DEFAULT NULL, p_sl DECIMAL(20, 8) DEFAULT NULL, p_confidence DECIMAL(5, 4) DEFAULT 0.5, p_expires_hours INT DEFAULT 24, p_prediction_data JSONB DEFAULT '{}' ) RETURNS UUID AS $$ DECLARE v_purchase ml.prediction_purchases%ROWTYPE; v_prediction_id UUID; BEGIN -- Validar purchase SELECT * INTO v_purchase FROM ml.prediction_purchases WHERE id = p_purchase_id FOR UPDATE; IF NOT FOUND THEN RAISE EXCEPTION 'Purchase not found: %', p_purchase_id; END IF; IF NOT v_purchase.is_active THEN RAISE EXCEPTION 'Purchase is not active'; END IF; IF v_purchase.valid_until IS NOT NULL AND v_purchase.valid_until < NOW() THEN RAISE EXCEPTION 'Purchase has expired'; END IF; IF v_purchase.predictions_used >= v_purchase.predictions_total THEN RAISE EXCEPTION 'No predictions remaining'; END IF; -- Crear prediccion INSERT INTO ml.predictions ( tenant_id, user_id, purchase_id, prediction_type, asset, asset_class, timeframe, direction, entry_price, target_price, stop_loss, confidence, expires_at, prediction_data ) VALUES ( v_purchase.tenant_id, v_purchase.user_id, p_purchase_id, p_prediction_type, p_asset, p_asset_class, p_timeframe, p_direction, p_entry, p_target, p_sl, p_confidence, NOW() + (p_expires_hours || ' hours')::INTERVAL, p_prediction_data ) RETURNING id INTO v_prediction_id; -- Incrementar contador UPDATE ml.prediction_purchases SET predictions_used = predictions_used + 1 WHERE id = p_purchase_id; RETURN v_prediction_id; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION ml.generate_prediction IS 'Genera una nueva prediccion desde un purchase, validando limites y estado';