-- Forecasting Pipeline Tables -- Run once to create the schema. Safe to re-run (IF NOT EXISTS). -- Precomputed reference decay curves per brand (or brand x category at any hierarchy level) CREATE TABLE IF NOT EXISTS brand_lifecycle_curves ( id SERIAL PRIMARY KEY, brand TEXT NOT NULL, root_category TEXT, -- NULL = brand-level fallback curve, else category name cat_id BIGINT, -- NULL = brand-only; else category_hierarchy.cat_id for precise matching category_level SMALLINT, -- NULL = brand-only; 0-3 = hierarchy depth amplitude NUMERIC(10,4), -- A in: sales(t) = A * exp(-λt) + C decay_rate NUMERIC(10,6), -- λ (higher = faster decay) baseline NUMERIC(10,4), -- C (long-tail steady-state daily sales) r_squared NUMERIC(6,4), -- goodness of fit sample_size INT, -- number of products that informed this curve median_first_week_sales NUMERIC(10,2), -- for scaling new launches median_preorder_sales NUMERIC(10,2), -- for scaling pre-order products median_preorder_days NUMERIC(10,2), -- median pre-order accumulation window (days) computed_at TIMESTAMP DEFAULT NOW(), UNIQUE(brand, cat_id) ); -- Per-product daily forecasts (next 90 days, regenerated each run) CREATE TABLE IF NOT EXISTS product_forecasts ( pid BIGINT NOT NULL, forecast_date DATE NOT NULL, forecast_units NUMERIC(10,2), forecast_revenue NUMERIC(14,4), lifecycle_phase TEXT, -- preorder, launch, decay, mature, slow_mover, dormant forecast_method TEXT, -- lifecycle_curve, exp_smoothing, velocity, zero confidence_lower NUMERIC(10,2), confidence_upper NUMERIC(10,2), generated_at TIMESTAMP DEFAULT NOW(), PRIMARY KEY (pid, forecast_date) ); CREATE INDEX IF NOT EXISTS idx_pf_date ON product_forecasts(forecast_date); CREATE INDEX IF NOT EXISTS idx_pf_phase ON product_forecasts(lifecycle_phase); -- Forecast run history (for monitoring) CREATE TABLE IF NOT EXISTS forecast_runs ( id SERIAL PRIMARY KEY, started_at TIMESTAMP NOT NULL, finished_at TIMESTAMP, status TEXT DEFAULT 'running', -- running, completed, failed products_forecast INT, phase_counts JSONB, -- {"launch": 50, "decay": 200, ...} curve_count INT, -- brand curves computed error_message TEXT, duration_seconds NUMERIC(10,2) );