-- ============================================================ -- schema.sql — monitor-ml-aceites -- Ejecutar en el SQL Editor de Supabase (una sola vez) -- ============================================================ -- ─── Extensiones ────────────────────────────────────────── CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- ─── Agentes ────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS agents ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, name VARCHAR(100) UNIQUE NOT NULL, description TEXT, system_prompt TEXT NOT NULL DEFAULT '', model VARCHAR(100) DEFAULT 'claude-sonnet-4-6', tools JSONB DEFAULT '[]', skills JSONB DEFAULT '[]', memory JSONB DEFAULT '{}', version_hash VARCHAR(64), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ─── Sesiones de conversación ───────────────────────────── CREATE TABLE IF NOT EXISTS sessions ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, telegram_user_id BIGINT NOT NULL, agent_name VARCHAR(100), history JSONB DEFAULT '[]', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions (telegram_user_id); -- ─── Modelos ML ─────────────────────────────────────────── CREATE TABLE IF NOT EXISTS ml_models ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, name VARCHAR(100) UNIQUE NOT NULL, type VARCHAR(50) NOT NULL, -- 'price_forecast' | 'demand_forecast' description TEXT, metrics JSONB DEFAULT '{}', -- MAE, RMSE, MAPE, R², etc. last_evaluated TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); -- Insertar modelos base INSERT INTO ml_models (name, type, description) VALUES ('palm_price_forecast', 'price_forecast', 'Forecasting de precio mundial aceite de palma (FRED/World Bank)'), ('sales_demand_forecast', 'demand_forecast', 'Forecasting de demanda y ventas de aceites y mantecas') ON CONFLICT (name) DO NOTHING; -- ─── Ejecuciones de modelos ML ──────────────────────────── CREATE TABLE IF NOT EXISTS ml_model_runs ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, model_id UUID REFERENCES ml_models(id) ON DELETE CASCADE, run_date TIMESTAMPTZ DEFAULT NOW(), metrics JSONB NOT NULL DEFAULT '{}', data_source VARCHAR(100), rows_processed INTEGER, status VARCHAR(20) DEFAULT 'success' CHECK (status IN ('success', 'failed', 'warning')), notes TEXT ); CREATE INDEX IF NOT EXISTS idx_model_runs_model ON ml_model_runs (model_id, run_date DESC); -- ─── Serie de precios (aceite de palma) ─────────────────── CREATE TABLE IF NOT EXISTS price_data ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, date DATE NOT NULL, actual_price DECIMAL(10,2), -- USD/tonelada métrica predicted_price DECIMAL(10,2), source VARCHAR(50), -- 'FRED' | 'WorldBank' | 'CFN' | 'ANCUPA' currency VARCHAR(3) DEFAULT 'USD', unit VARCHAR(20) DEFAULT 'USD/MT', created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE (date, source) ); CREATE INDEX IF NOT EXISTS idx_price_data_date ON price_data (date DESC); -- ─── Datos de ventas ────────────────────────────────────── -- Diseñado para recibir datos reales del negocio (Excel plano en el futuro) CREATE TABLE IF NOT EXISTS sales_data ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, source_id UUID, -- referencia a data_sources date DATE NOT NULL, product_name VARCHAR(200), product_category VARCHAR(100), -- 'aceite', 'manteca', 'otros' quantity DECIMAL(10,2), unit_price DECIMAL(10,2), total_amount DECIMAL(10,2), currency VARCHAR(3) DEFAULT 'USD', region VARCHAR(100), channel VARCHAR(50), -- 'retail', 'wholesale', 'ecommerce' metadata JSONB DEFAULT '{}', -- columnas extra del Excel original created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_sales_date ON sales_data (date DESC); CREATE INDEX IF NOT EXISTS idx_sales_category ON sales_data (product_category); -- ─── Inventario ─────────────────────────────────────────── CREATE TABLE IF NOT EXISTS inventory_status ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, product_name VARCHAR(200) NOT NULL, product_category VARCHAR(100), current_stock DECIMAL(10,2), unit VARCHAR(20) DEFAULT 'kg', reorder_point DECIMAL(10,2), risk_level VARCHAR(20) DEFAULT 'ok' CHECK (risk_level IN ('ok', 'low', 'critical')), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ─── Fuentes de datos ───────────────────────────────────── CREATE TABLE IF NOT EXISTS data_sources ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, name VARCHAR(100) NOT NULL, type VARCHAR(50), -- 'kaggle' | 'fred' | 'world_bank' | 'excel_import' | 'ancupa' description TEXT, url TEXT, last_updated TIMESTAMPTZ, rows_count INTEGER, columns JSONB DEFAULT '[]', metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW() ); -- Insertar fuentes conocidas INSERT INTO data_sources (name, type, description, url) VALUES ('FRED PPOILUSDM', 'fred', 'Precio mundial aceite de palma mensual (IMF vía FRED)', 'https://fred.stlouisfed.org/series/PPOILUSDM'), ('World Bank Pink Sheet', 'world_bank', 'Commodities price data mensual desde 1960', 'https://thedocs.worldbank.org/en/doc/18675f1d1639c7a34d463f59263ba0a2-0050012025/world-bank-commodities-price-data-the-pink-sheet'), ('FMCG Daily Sales 2022-2024', 'kaggle', 'Ventas diarias FMCG sintéticas (proxy hasta tener datos reales)', 'https://www.kaggle.com/datasets/beatafaron/fmcg-daily-sales-data-to-2022-2024'), ('CFN Ficha Sectorial Palma 2024', 'cfn_ecuador', 'Precios productor y estadísticas palma africana Ecuador 2024', 'https://www.cfn.fin.ec/wp-content/uploads/2024/07/Ficha-Sectorial-Palma-Africana.pdf'), ('ANCUPA Ecuador', 'ancupa', 'Estadísticas nacionales palma aceitera Ecuador desde 1994', 'http://ancupa.com/estadisticas/') ON CONFLICT DO NOTHING; -- ─── Eventos de mercado (news-aware forecasting) ────────── -- Eventos globales que afectan el precio del aceite de palma. -- Scrapeados diariamente por research_pipeline.py usando DuckDuckGo. -- Los agentes leen estos eventos para contextualizar sus forecasts. CREATE TABLE IF NOT EXISTS market_events ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, event_date DATE NOT NULL, -- fecha del evento (no del scraping) discovered_at TIMESTAMPTZ DEFAULT NOW(), -- cuándo lo encontramos title TEXT NOT NULL, description TEXT, source_url TEXT UNIQUE, source_name VARCHAR(100), event_type VARCHAR(50), -- 'weather' | 'geopolitical' | 'policy' | 'supply' | 'demand' | 'trade' | 'macro' region VARCHAR(100), -- 'Malaysia' | 'Indonesia' | 'Ecuador' | 'global' | 'EU' | 'India' | etc. price_direction VARCHAR(10), -- 'bullish' | 'bearish' | 'neutral' price_impact_pct DECIMAL(5,2), -- impacto estimado en % sobre precio (+ sube, - baja) confidence DECIMAL(3,2), -- confianza de la clasificación (0.0 a 1.0) tags JSONB DEFAULT '[]', created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_market_events_date ON market_events (event_date DESC); CREATE INDEX IF NOT EXISTS idx_market_events_type ON market_events (event_type, price_direction); -- ─── Agent Lab: Research ────────────────────────────────── CREATE TABLE IF NOT EXISTS agent_lab_research ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, source_url TEXT NOT NULL UNIQUE, source_tier VARCHAR(50), -- 'anthropic' | 'huggingface' | 'langchain' | etc. title TEXT NOT NULL, summary TEXT, relevance_score DECIMAL(3,2) DEFAULT 0.00 CHECK (relevance_score BETWEEN 0 AND 1), tags JSONB DEFAULT '[]', created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_research_score ON agent_lab_research (relevance_score DESC); -- ─── Agent Lab: Recomendaciones ─────────────────────────── CREATE TABLE IF NOT EXISTS agent_lab_recommendations ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, type VARCHAR(20) NOT NULL CHECK (type IN ('reactive', 'proactive')), category VARCHAR(100), -- 'prompt' | 'tool' | 'model' | 'skill' | 'memory' target_agent VARCHAR(100) NOT NULL, title TEXT NOT NULL, description TEXT, rationale TEXT, status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected', 'applied')), priority INTEGER DEFAULT 5 CHECK (priority BETWEEN 1 AND 10), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_recommendations_status ON agent_lab_recommendations (status, type); -- ─── Agent Lab: Audit Log ───────────────────────────────── CREATE TABLE IF NOT EXISTS agent_lab_changes ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, recommendation_id UUID REFERENCES agent_lab_recommendations(id), change_type VARCHAR(20) CHECK (change_type IN ('auto', 'approved')), target_agent VARCHAR(100), description TEXT, before_state JSONB, after_state JSONB, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ─── Forecasts de precios ───────────────────────────────── -- Almacena las predicciones generadas por el modelo Prophet -- El modelo se reentrena mensualmente via GitHub Actions cron CREATE TABLE IF NOT EXISTS price_forecasts ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, forecast_run_at TIMESTAMPTZ DEFAULT NOW(), -- cuándo se generó el forecast target_date DATE NOT NULL, -- fecha que se predice horizon VARCHAR(20) NOT NULL, -- 'tomorrow' | 'next_week' | 'month_1' | 'month_2' predicted_price DECIMAL(10,2) NOT NULL, -- USD/MT lower_bound DECIMAL(10,2), -- intervalo 80% upper_bound DECIMAL(10,2), -- intervalo 80% model_name VARCHAR(100) DEFAULT 'palm_price_forecast', data_points INTEGER, -- registros usados para entrenar mape DECIMAL(6,2), -- error del modelo al momento del entrenamiento (%) created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_price_forecasts_run ON price_forecasts (forecast_run_at DESC); CREATE INDEX IF NOT EXISTS idx_price_forecasts_horizon ON price_forecasts (horizon, forecast_run_at DESC); -- ─── Triggers: updated_at automático ───────────────────── CREATE OR REPLACE FUNCTION update_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_agents_updated_at BEFORE UPDATE ON agents FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER trg_sessions_updated_at BEFORE UPDATE ON sessions FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER trg_recommendations_updated_at BEFORE UPDATE ON agent_lab_recommendations FOR EACH ROW EXECUTE FUNCTION update_updated_at();