-- ══════════════════════════════════════════════════════════════════════════════ -- 118 — Stock Velocity & Sell-Through Analysis -- -- Evaluates implied Abverkauf (sell-through) from time-series stock_observations: -- • Negative stock delta → implied units sold (sell event) -- • Positive stock delta after backorder → Zulauf (incoming replenishment) -- • FS.com units_sold counter delta → high-confidence sell signal -- -- Stores per-product velocity results in stock_velocity for API / dashboard use. -- ══════════════════════════════════════════════════════════════════════════════ -- ── Main results table ──────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS stock_velocity ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, transceiver_id UUID NOT NULL REFERENCES transceivers(id) ON DELETE CASCADE, vendor_id UUID NOT NULL REFERENCES vendors(id) ON DELETE CASCADE, computed_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, -- Observation window window_start TIMESTAMPTZ NOT NULL, window_end TIMESTAMPTZ NOT NULL, obs_count INTEGER NOT NULL, -- Sell-through metrics avg_daily_sell_rate NUMERIC(12, 2), -- units/day (implied) peak_daily_sell_rate NUMERIC(12, 2), -- highest single-interval rate total_sell_events INTEGER DEFAULT 0, total_units_sold_implied INTEGER DEFAULT 0, -- FS.com direct counter (more reliable when available) units_sold_counter_delta BIGINT, -- delta in FS.com units_sold between first/last obs units_sold_daily_rate NUMERIC(12, 2), -- counter_delta / window_days -- Zulauf (incoming stock / replenishment) total_zulauf_events INTEGER DEFAULT 0, total_units_zulauf INTEGER DEFAULT 0, last_zulauf_at TIMESTAMPTZ, next_expected_delivery DATE, -- backorder_estimated_date from latest obs -- Current stock state (from latest observation) current_qty INTEGER, current_backorder_qty INTEGER, current_price_net NUMERIC(10, 2), -- Sell-through prediction estimated_stockout_days NUMERIC(8, 1), -- NULL if no velocity or stock = 0 estimated_stockout_date DATE, -- Signal quality velocity_confidence TEXT CHECK (velocity_confidence IN ('high', 'medium', 'low', 'insufficient')), -- high = ≥14 observations with meaningful deltas -- medium = ≥5 observations -- low = 2–4 observations -- insufficient = only 1 observation or no change detected UNIQUE (transceiver_id, vendor_id) ); CREATE INDEX IF NOT EXISTS idx_stock_velocity_vendor ON stock_velocity (vendor_id); CREATE INDEX IF NOT EXISTS idx_stock_velocity_computed ON stock_velocity (computed_at); CREATE INDEX IF NOT EXISTS idx_stock_velocity_stockout ON stock_velocity (estimated_stockout_date) WHERE estimated_stockout_date IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_stock_velocity_confidence ON stock_velocity (velocity_confidence); COMMENT ON TABLE stock_velocity IS 'Computed sell-through velocity per transceiver per vendor, derived from ' 'time-series stock_observations. Refreshed by analyze:stock:velocity job.'; -- ── Sell event log (raw events for trend analysis) ──────────────────────────── CREATE TABLE IF NOT EXISTS stock_velocity_events ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, transceiver_id UUID NOT NULL REFERENCES transceivers(id) ON DELETE CASCADE, vendor_id UUID NOT NULL REFERENCES vendors(id) ON DELETE CASCADE, event_at TIMESTAMPTZ NOT NULL, event_type TEXT NOT NULL CHECK (event_type IN ('sold', 'zulauf', 'unchanged', 'data_gap')), units_delta INTEGER, -- negative = sold, positive = arrived daily_rate NUMERIC(10, 2), -- implied rate for this interval qty_before INTEGER, qty_after INTEGER, hours_elapsed NUMERIC(8, 2) ); CREATE INDEX IF NOT EXISTS idx_velocity_events_tx ON stock_velocity_events (transceiver_id, vendor_id, event_at); CREATE INDEX IF NOT EXISTS idx_velocity_events_type ON stock_velocity_events (event_type, event_at);