- Migration 099: flexoptix_internal_demand table with RLS + v_demand_by_speed view - Import script: AES-256-CBC decrypt → parse 8585 SKUs → upsert with velocity class - 279 SKUs cross-referenced to transceiver catalog; 1288 with real demand data - New /api/internal/demand/* routes (by-speed, velocity, hype-weights, forecast-input) — protected by JWT auth + localhost/LAN IP restriction middleware - Forecast engine calibrated with real Flexoptix run-rates (demand_calibrated flag) - Dashboard: real Flexoptix Sales Velocity panel replaces DEMO DATA in Warehouse tab with momentum indicators, velocity class breakdown, trend arrows - Security: data stays on private server; RLS enforces is_internal=TRUE at DB layer
72 lines
3.8 KiB
SQL
72 lines
3.8 KiB
SQL
-- Migration 099: Flexoptix Internal Demand Data Schema
|
|
-- ⚠️ SECURITY: This table contains proprietary Flexoptix business intelligence.
|
|
-- Raw records MUST NEVER be exposed via any public-facing API endpoint.
|
|
-- Use ONLY for aggregated forecasting and hype cycle calibration.
|
|
-- Data source: internal XLSX export (AES-256-CBC encrypted at rest on local infra)
|
|
--
|
|
-- velocity_class breakdown:
|
|
-- fast_mover — demand_12m >= 100 units/month
|
|
-- regular — demand_12m >= 10 and < 100
|
|
-- slow_mover — demand_12m > 0 and < 10
|
|
-- dead_stock — demand_12m = 0
|
|
|
|
CREATE TABLE IF NOT EXISTS flexoptix_internal_demand (
|
|
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
sku TEXT NOT NULL, -- Artikel-Nr (e.g. AT.L.1BU)
|
|
description TEXT, -- Kurzbeschreibung
|
|
transceiver_id UUID REFERENCES transceivers(id) ON DELETE SET NULL,
|
|
demand_12m NUMERIC(12,2) NOT NULL DEFAULT 0, -- Bedarf/Monat letzte 12 Monate
|
|
demand_3m NUMERIC(12,2) NOT NULL DEFAULT 0, -- Bedarf/Monat letzte 3 Monate
|
|
demand_trend_pct NUMERIC(8,2), -- (3m-12m)/12m*100 (+= accelerating)
|
|
velocity_class TEXT CHECK (velocity_class IN ('fast_mover','regular','slow_mover','dead_stock')),
|
|
imported_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
data_source TEXT NOT NULL DEFAULT 'flexoptix_internal',
|
|
is_internal BOOLEAN NOT NULL DEFAULT TRUE, -- security guard — always TRUE
|
|
|
|
CONSTRAINT flexoptix_demand_sku_unique UNIQUE (sku)
|
|
);
|
|
|
|
COMMENT ON TABLE flexoptix_internal_demand IS
|
|
'INTERNAL — Flexoptix proprietary sales velocity data. Never expose raw rows publicly.';
|
|
|
|
-- Indexes
|
|
CREATE INDEX IF NOT EXISTS idx_foxd_sku ON flexoptix_internal_demand (sku);
|
|
CREATE INDEX IF NOT EXISTS idx_foxd_transceiver ON flexoptix_internal_demand (transceiver_id)
|
|
WHERE transceiver_id IS NOT NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_foxd_velocity ON flexoptix_internal_demand (velocity_class);
|
|
CREATE INDEX IF NOT EXISTS idx_foxd_demand_12m ON flexoptix_internal_demand (demand_12m DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_foxd_is_internal ON flexoptix_internal_demand (is_internal);
|
|
|
|
-- Row-level security: prevent accidental exposure
|
|
-- (API layer enforces localhost-only on top of this)
|
|
ALTER TABLE flexoptix_internal_demand ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Only the tip user (application) can read — no PUBLIC access
|
|
CREATE POLICY foxd_tip_read ON flexoptix_internal_demand
|
|
FOR SELECT TO tip USING (is_internal = TRUE);
|
|
|
|
CREATE POLICY foxd_tip_write ON flexoptix_internal_demand
|
|
FOR ALL TO tip USING (TRUE) WITH CHECK (is_internal = TRUE);
|
|
|
|
-- Aggregated demand view: technology-level rollup (safe to expose — no individual SKUs)
|
|
CREATE OR REPLACE VIEW v_demand_by_speed AS
|
|
SELECT
|
|
t.speed_gbps,
|
|
t.form_factor,
|
|
COUNT(DISTINCT d.sku) AS sku_count,
|
|
SUM(d.demand_12m) AS total_demand_12m,
|
|
SUM(d.demand_3m) AS total_demand_3m,
|
|
ROUND(AVG(d.demand_12m), 2) AS avg_demand_12m,
|
|
ROUND(AVG(d.demand_trend_pct), 1) AS avg_trend_pct,
|
|
COUNT(*) FILTER (WHERE d.velocity_class = 'fast_mover') AS fast_movers,
|
|
COUNT(*) FILTER (WHERE d.velocity_class = 'regular') AS regular,
|
|
COUNT(*) FILTER (WHERE d.velocity_class = 'slow_mover') AS slow_movers,
|
|
COUNT(*) FILTER (WHERE d.velocity_class = 'dead_stock') AS dead_stock
|
|
FROM flexoptix_internal_demand d
|
|
JOIN transceivers t ON t.id = d.transceiver_id
|
|
GROUP BY t.speed_gbps, t.form_factor
|
|
ORDER BY total_demand_12m DESC;
|
|
|
|
COMMENT ON VIEW v_demand_by_speed IS
|
|
'Aggregated demand rollup by technology — safe for API exposure (no individual SKUs).';
|