-- Trend history table for time-series charting. -- trend_windows stores the latest snapshot per (entity, window) via upsert. -- trend_history stores every snapshot so the frontend can plot trend evolution. -- -- Note: migration 023 already creates this table and seeds it from -- trend_windows. This migration ensures the table exists if 023 was -- run before this version, and backfills from recommendations for -- richer historical data. CREATE TABLE IF NOT EXISTS trend_history ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), entity_type VARCHAR(50) NOT NULL DEFAULT 'company', entity_id VARCHAR(100) NOT NULL, "window" VARCHAR(20) NOT NULL, trend_direction VARCHAR(20) NOT NULL DEFAULT 'neutral', trend_strength FLOAT DEFAULT 0.5, confidence FLOAT DEFAULT 0.5, contradiction_score FLOAT DEFAULT 0.0, dominant_catalysts JSONB DEFAULT '[]', material_risks JSONB DEFAULT '[]', generated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_trend_history_lookup ON trend_history (entity_id, "window", generated_at DESC); CREATE INDEX IF NOT EXISTS idx_trend_history_generated ON trend_history (generated_at DESC); -- Backfill from recommendations if trend_history is sparse. -- This reconstructs approximate trend history from the recommendation -- records that were generated throughout the day, giving one data point -- per ticker per window per hour. INSERT INTO trend_history ( entity_type, entity_id, "window", trend_direction, trend_strength, confidence, contradiction_score, dominant_catalysts, material_risks, generated_at ) SELECT 'company', r.ticker, CASE WHEN r.time_horizon LIKE 'intraday%' THEN 'intraday' WHEN r.time_horizon LIKE 'swing_1d_3d%' THEN '1d' WHEN r.time_horizon LIKE 'swing_1d_10d%' THEN '7d' WHEN r.time_horizon LIKE 'position_10d_30d%' THEN '30d' WHEN r.time_horizon LIKE 'position_30d_90d%' THEN '90d' ELSE '7d' END, CASE WHEN r.action = 'buy' THEN 'bullish' WHEN r.action = 'sell' THEN 'bearish' WHEN r.action = 'hold' THEN 'neutral' ELSE 'mixed' END, r.confidence * 0.8, r.confidence, CASE WHEN r.risk_classification = 'high' THEN 0.4 WHEN r.risk_classification = 'very_high' THEN 0.6 WHEN r.risk_classification = 'moderate' THEN 0.2 ELSE 0.1 END, '[]'::jsonb, '[]'::jsonb, r.generated_at FROM ( SELECT DISTINCT ON (ticker, time_horizon, date_trunc('hour', generated_at)) ticker, action, confidence, time_horizon, risk_classification, generated_at FROM recommendations WHERE generated_at >= NOW() - INTERVAL '7 days' ORDER BY ticker, time_horizon, date_trunc('hour', generated_at), generated_at DESC ) r WHERE NOT EXISTS ( SELECT 1 FROM trend_history th WHERE th.entity_id = r.ticker AND th.generated_at = r.generated_at );