transceiver-db/sql/099-flexoptix-internal-demand-schema.sql
Rene Fichtmueller f162e03978 feat: Flexoptix internal demand intelligence + real forecast calibration
- 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
2026-04-25 17:44:20 +02:00

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).';