feat: wavelength/connector enrichment schema + enricher robot

- sql/110: add wavelength_tx_nm, wavelength_rx_nm, connector_type,
  data_completeness, enrichment_needed columns + trigger
- sql/111: IEEE/MSA standards wavelength lookup table (SFP→OSFP)
- sql/112: migrate existing wavelengths TEXT → integer columns
- robots/wavelength-enricher.ts: fills missing wavelengths from IEEE
  lookup (deterministic) then product-name regex, runs every 4h
- scheduler: register enrich:wavelength job (4h schedule)

Fixes over-broad matching where 1G SFPs match 500+ competitors
due to missing wavelength discrimination.
This commit is contained in:
Rene Fichtmueller 2026-05-13 17:35:42 +02:00
parent 1edd6c20a8
commit 9979b79434
5 changed files with 405 additions and 0 deletions

View File

@ -0,0 +1,176 @@
/**
* Wavelength Enricher Robot
*
* Füllt fehlende wavelength_tx_nm / wavelength_rx_nm / connector_type
* aus drei Quellen (Priorität absteigend):
* 1. IEEE/MSA Lookup-Tabelle (sql/111) deterministisch, keine Kosten
* 2. Produktname-Regex (heuristisch, pattern-basiert)
* 3. Quarantäne: Produkt bleibt ohne Match bis Daten vorhanden
*
* Kein LLM, kein Scraper, keine externen Calls rein datenbankbasiert.
*/
import { pool } from "../utils/db";
// ── Regex-Patterns für Wellenlänge aus Produktnamen ──────────────────────────
const WAVELENGTH_PATTERNS: Array<{
pattern: RegExp;
tx: number;
rx?: number;
notes: string;
}> = [
// BiDi explizit
{ pattern: /\b1270\s*\/\s*1330\b/i, tx: 1270, rx: 1330, notes: "BiDi 1270/1330" },
{ pattern: /\b1330\s*\/\s*1270\b/i, tx: 1330, rx: 1270, notes: "BiDi 1330/1270" },
{ pattern: /\b1310\s*\/\s*1550\b/i, tx: 1310, rx: 1550, notes: "BiDi 1310/1550" },
{ pattern: /\b1550\s*\/\s*1310\b/i, tx: 1550, rx: 1310, notes: "BiDi 1550/1310" },
{ pattern: /\b1295\s*\/\s*1310\b/i, tx: 1295, rx: 1310, notes: "BiDi CWDM" },
// Direkte nm-Angabe
{ pattern: /\b850\s*nm\b/i, tx: 850, notes: "850nm explicit" },
{ pattern: /\b1310\s*nm\b/i, tx: 1310, notes: "1310nm explicit" },
{ pattern: /\b1550\s*nm\b/i, tx: 1550, notes: "1550nm explicit" },
{ pattern: /\b1270\s*nm\b/i, tx: 1270, notes: "1270nm explicit" },
{ pattern: /\b1330\s*nm\b/i, tx: 1330, notes: "1330nm explicit" },
// DWDM Channels (C-Band ~1530-1565nm)
{ pattern: /\bDWDM\b.*\bC\d{2}\b/i, tx: 1550, notes: "DWDM C-Band" },
{ pattern: /\bDWDM\b/i, tx: 1550, notes: "DWDM generic" },
// Standard-Kurzbezeichnungen → implizite Wellenlänge
{ pattern: /\bSR4?\b/i, tx: 850, notes: "SR/SR4 = 850nm MMF" },
{ pattern: /\bLR4?\b/i, tx: 1310, notes: "LR/LR4 = 1310nm SMF" },
{ pattern: /\bER4?\b/i, tx: 1310, notes: "ER/ER4 = 1310nm SMF" },
{ pattern: /\bFR4?\b/i, tx: 1310, notes: "FR/FR4 = 1310nm SMF" },
{ pattern: /\bDR4?\b/i, tx: 1310, notes: "DR/DR4 = 1310nm SMF" },
{ pattern: /\bZR4?\b/i, tx: 1550, notes: "ZR/ZR4 = 1550nm SMF" },
];
const CONNECTOR_PATTERNS: Array<{ pattern: RegExp; connector: string }> = [
{ pattern: /\bMPO.?16\b/i, connector: "MPO-16" },
{ pattern: /\bMPO.?12\b/i, connector: "MPO-12" },
{ pattern: /\bMPO\b/i, connector: "MPO-12" }, // default MPO = MPO-12
{ pattern: /\bMTP\b/i, connector: "MPO-12" },
{ pattern: /\bCS\s*connector\b/i, connector: "CS" },
{ pattern: /\bSN\s*connector\b/i, connector: "SN" },
{ pattern: /\bRJ.?45\b/i, connector: "RJ45" },
{ pattern: /\bbase.?t\b/i, connector: "RJ45" },
{ pattern: /\bSC\b/i, connector: "SC" },
{ pattern: /\bLC\b/i, connector: "LC" }, // LC zuletzt (häufig im Text)
];
// DAC/AOC haben keinen Fiber-Connector
const DAC_AOC_PATTERN = /\bDAC\b|\bAOC\b|\btwinax\b/i;
function extractWavelengthFromName(name: string): { tx: number; rx?: number; notes: string } | null {
for (const p of WAVELENGTH_PATTERNS) {
if (p.pattern.test(name)) {
return { tx: p.tx, rx: p.rx, notes: p.notes };
}
}
return null;
}
function extractConnectorFromName(name: string): string | null {
if (DAC_AOC_PATTERN.test(name)) return "DAC/AOC";
for (const p of CONNECTOR_PATTERNS) {
if (p.pattern.test(name)) return p.connector;
}
return null;
}
export async function runWavelengthEnricher(): Promise<void> {
console.log("=== Wavelength Enricher Robot ===");
// Alle Transceivers mit fehlenden Pflichtfeldern
const { rows: transceivers } = await pool.query<{
id: string;
standard_name: string;
part_number: string;
form_factor: string;
speed_gbps: number;
fiber_type: string;
reach_meters: number;
wavelength_tx_nm: number | null;
wavelength_rx_nm: number | null;
connector_type: string | null;
}>(`
SELECT id, standard_name, part_number, form_factor, speed_gbps,
fiber_type, reach_meters, wavelength_tx_nm, wavelength_rx_nm, connector_type
FROM transceivers
WHERE enrichment_needed = TRUE
ORDER BY data_completeness DESC -- Produkte mit mehr Daten zuerst
LIMIT 5000
`);
let fromIeee = 0;
let fromRegex = 0;
let stillMissing = 0;
for (const t of transceivers) {
let txNm = t.wavelength_tx_nm;
let rxNm = t.wavelength_rx_nm;
let connector = t.connector_type;
let source = "";
// ── Quelle 1: IEEE/MSA Lookup ───────────────────────────────────────────
if (txNm === null && t.form_factor && t.speed_gbps && t.fiber_type && t.reach_meters) {
const { rows: ieee } = await pool.query<{
wavelength_tx_nm: number;
wavelength_rx_nm: number | null;
connector_type: string;
}>(`
SELECT wavelength_tx_nm, wavelength_rx_nm, connector_type
FROM ieee_wavelength_lookup
WHERE form_factor = $1
AND speed_gbps = $2
AND fiber_type = $3
AND reach_min_m <= $4
AND reach_max_m >= $4
LIMIT 1
`, [t.form_factor, t.speed_gbps, t.fiber_type, t.reach_meters]);
if (ieee.length > 0) {
txNm = ieee[0].wavelength_tx_nm;
rxNm = ieee[0].wavelength_rx_nm ?? null;
if (!connector) connector = ieee[0].connector_type;
source = "ieee_lookup";
fromIeee++;
}
}
// ── Quelle 2: Produktname-Regex ─────────────────────────────────────────
const nameForExtraction = [t.standard_name, t.part_number].filter(Boolean).join(" ");
if (txNm === null && nameForExtraction) {
const extracted = extractWavelengthFromName(nameForExtraction);
if (extracted) {
txNm = extracted.tx;
rxNm = extracted.rx ?? null;
source = `regex:${extracted.notes}`;
fromRegex++;
}
}
if (connector === null && nameForExtraction) {
const extractedConn = extractConnectorFromName(nameForExtraction);
if (extractedConn && extractedConn !== "DAC/AOC") connector = extractedConn;
}
// ── Update wenn etwas gefunden ──────────────────────────────────────────
if (txNm !== null || connector !== null) {
await pool.query(`
UPDATE transceivers SET
wavelength_tx_nm = COALESCE($1, wavelength_tx_nm),
wavelength_rx_nm = COALESCE($2, wavelength_rx_nm),
connector_type = COALESCE($3, connector_type),
updated_at = NOW()
WHERE id = $4
`, [txNm, rxNm, connector, t.id]);
} else {
stillMissing++;
}
}
console.log(` IEEE Lookup: ${fromIeee} enriched`);
console.log(` Regex Extract: ${fromRegex} enriched`);
console.log(` Still missing: ${stillMissing} (Quarantäne bis Daten verfügbar)`);
console.log("=== Wavelength Enricher Complete ===");
}

View File

@ -354,6 +354,8 @@ export async function registerSchedules(boss: PgBoss): Promise<void> {
"discover:vendor:nokia", "discover:vendor:nokia",
"discover:vendor:huawei", "discover:vendor:huawei",
"discover:vendor:ii-vi", "discover:vendor:ii-vi",
// ── Wavelength Enrichment ────────────────────────────────────────────
"enrich:wavelength",
]; ];
for (const q of queues) { for (const q of queues) {
@ -420,6 +422,9 @@ export async function registerSchedules(boss: PgBoss): Promise<void> {
expireInSeconds: 3600, expireInSeconds: 3600,
}); });
// Wavelength Enricher — läuft alle 4 Stunden
await boss.schedule("enrich:wavelength", "0 */4 * * *", {}, {});
// ══════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════
// MANUFACTURER CATALOGS — every 4h (product data, no prices) // MANUFACTURER CATALOGS — every 4h (product data, no prices)
// ══════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════
@ -920,6 +925,13 @@ export async function registerWorkers(boss: PgBoss): Promise<void> {
console.log(`[catalog:reconcile] Done: ${result.newAutoApproved} auto_approved, ${result.newPending} pending`); console.log(`[catalog:reconcile] Done: ${result.newAutoApproved} auto_approved, ${result.newPending} pending`);
}); });
// Wavelength Enricher — fills missing wavelength_tx_nm / wavelength_rx_nm / connector_type
await boss.work("enrich:wavelength", async () => {
console.log(`[${new Date().toISOString()}] Running: Wavelength Enricher`);
const { runWavelengthEnricher } = await import("./robots/wavelength-enricher");
await runWavelengthEnricher();
});
await boss.work("scrape:catalog:smartoptics", async () => { await boss.work("scrape:catalog:smartoptics", async () => {
console.log(`[${new Date().toISOString()}] Running: SmartOptics catalog`); console.log(`[${new Date().toISOString()}] Running: SmartOptics catalog`);
await scrapeSmartOptics(); await scrapeSmartOptics();

View File

@ -0,0 +1,85 @@
-- Migration 110: Wavelength (TX/RX getrennt für BiDi), Connector-Normalisierung,
-- Data-Completeness-Score, Enrichment-Flag
-- 1. Wellenlängen-Felder aufteilen (BiDi hat TX ≠ RX)
ALTER TABLE transceivers
ADD COLUMN IF NOT EXISTS wavelength_tx_nm INTEGER, -- TX-Wellenlänge in nm (z.B. 1270)
ADD COLUMN IF NOT EXISTS wavelength_rx_nm INTEGER, -- RX-Wellenlänge in nm (z.B. 1330)
ADD COLUMN IF NOT EXISTS connector_type TEXT, -- 'LC', 'SC', 'MPO-12', 'MPO-16', 'RJ45', 'CS', 'SN'
ADD COLUMN IF NOT EXISTS data_completeness INTEGER DEFAULT 0 CHECK (data_completeness BETWEEN 0 AND 100),
ADD COLUMN IF NOT EXISTS enrichment_needed BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS enrichment_fields TEXT[] DEFAULT '{}'; -- welche Felder fehlen noch
-- 2. Indices für Performance
CREATE INDEX IF NOT EXISTS idx_tx_wavelength_tx ON transceivers (wavelength_tx_nm) WHERE wavelength_tx_nm IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_tx_wavelength_rx ON transceivers (wavelength_rx_nm) WHERE wavelength_rx_nm IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_tx_completeness ON transceivers (data_completeness);
CREATE INDEX IF NOT EXISTS idx_tx_enrichment ON transceivers (enrichment_needed) WHERE enrichment_needed = TRUE;
-- 3. Completeness-Berechnungsfunktion
CREATE OR REPLACE FUNCTION calc_data_completeness(
p_form_factor TEXT, p_speed_gbps NUMERIC, p_fiber_type TEXT,
p_reach_meters INTEGER, p_wavelength_tx INTEGER, p_connector TEXT
) RETURNS INTEGER AS $$
DECLARE
score INTEGER := 0;
BEGIN
IF p_form_factor IS NOT NULL AND p_form_factor != '' THEN score := score + 20; END IF;
IF p_speed_gbps IS NOT NULL AND p_speed_gbps > 0 THEN score := score + 20; END IF;
IF p_fiber_type IS NOT NULL AND p_fiber_type != '' THEN score := score + 20; END IF;
IF p_reach_meters IS NOT NULL AND p_reach_meters > 0 THEN score := score + 20; END IF;
IF p_wavelength_tx IS NOT NULL AND p_wavelength_tx > 0 THEN score := score + 10; END IF;
IF p_connector IS NOT NULL AND p_connector != '' THEN score := score + 10; END IF;
RETURN score;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 4. Alle bestehenden Transceivers: Completeness initial berechnen
UPDATE transceivers SET
data_completeness = calc_data_completeness(
form_factor, speed_gbps, fiber_type,
reach_meters, wavelength_tx_nm, connector_type
),
enrichment_needed = (
form_factor IS NULL OR speed_gbps IS NULL OR
fiber_type IS NULL OR reach_meters IS NULL OR
wavelength_tx_nm IS NULL OR connector_type IS NULL
);
-- 5. Trigger: Completeness automatisch aktualisieren
CREATE OR REPLACE FUNCTION trg_update_completeness()
RETURNS TRIGGER AS $$
BEGIN
NEW.data_completeness := calc_data_completeness(
NEW.form_factor, NEW.speed_gbps, NEW.fiber_type,
NEW.reach_meters, NEW.wavelength_tx_nm, NEW.connector_type
);
NEW.enrichment_needed := (
NEW.form_factor IS NULL OR NEW.speed_gbps IS NULL OR
NEW.fiber_type IS NULL OR NEW.reach_meters IS NULL OR
NEW.wavelength_tx_nm IS NULL OR NEW.connector_type IS NULL
);
-- Fehlende Felder dokumentieren
NEW.enrichment_fields := ARRAY_REMOVE(ARRAY[
CASE WHEN NEW.form_factor IS NULL THEN 'form_factor' END,
CASE WHEN NEW.speed_gbps IS NULL THEN 'speed_gbps' END,
CASE WHEN NEW.fiber_type IS NULL THEN 'fiber_type' END,
CASE WHEN NEW.reach_meters IS NULL THEN 'reach_meters' END,
CASE WHEN NEW.wavelength_tx_nm IS NULL THEN 'wavelength_tx_nm' END,
CASE WHEN NEW.connector_type IS NULL THEN 'connector_type' END
], NULL);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_completeness ON transceivers;
CREATE TRIGGER trg_completeness
BEFORE INSERT OR UPDATE ON transceivers
FOR EACH ROW EXECUTE FUNCTION trg_update_completeness();
COMMENT ON COLUMN transceivers.wavelength_tx_nm IS 'TX wavelength in nm. For BiDi: TX side. For duplex: both TX=RX.';
COMMENT ON COLUMN transceivers.wavelength_rx_nm IS 'RX wavelength in nm. Only set for BiDi. NULL = same as TX.';
COMMENT ON COLUMN transceivers.connector_type IS 'Physical connector: LC, SC, MPO-12, MPO-16, RJ45, CS, SN';
COMMENT ON COLUMN transceivers.data_completeness IS '0-100: percentage of mandatory fields filled (6 fields × weight)';
COMMENT ON COLUMN transceivers.enrichment_needed IS 'TRUE = one or more mandatory fields missing, enrichment job needed';
COMMENT ON COLUMN transceivers.enrichment_fields IS 'Array of field names that still need enrichment';

View File

@ -0,0 +1,84 @@
-- Migration 111: IEEE/MSA Standards Wavelength Lookup
-- Ground-Truth für Wellenlänge basierend auf Standard-Spezifikation
-- Quelle: IEEE 802.3, SFF-8472, SFF-8436, SFF-8661, SFF-8679, MSA specs
CREATE TABLE IF NOT EXISTS ieee_wavelength_lookup (
id SERIAL PRIMARY KEY,
form_factor TEXT NOT NULL,
speed_gbps NUMERIC NOT NULL,
fiber_type TEXT NOT NULL, -- 'SMF', 'MMF', 'DAC', 'AOC'
reach_min_m INTEGER NOT NULL,
reach_max_m INTEGER NOT NULL,
wavelength_tx_nm INTEGER NOT NULL,
wavelength_rx_nm INTEGER, -- NULL = gleich wie TX (kein BiDi)
connector_type TEXT NOT NULL,
ieee_standard TEXT, -- z.B. '802.3ae', 'SFF-8431'
notes TEXT,
UNIQUE (form_factor, speed_gbps, fiber_type, reach_min_m, reach_max_m)
);
INSERT INTO ieee_wavelength_lookup
(form_factor, speed_gbps, fiber_type, reach_min_m, reach_max_m, wavelength_tx_nm, wavelength_rx_nm, connector_type, ieee_standard, notes)
VALUES
-- ── SFP (1G) ─────────────────────────────────────────────────────────────────
('SFP', 1, 'MMF', 0, 550, 850, NULL, 'LC', '802.3z', '1000BASE-SX'),
('SFP', 1, 'SMF', 0, 10000, 1310, NULL, 'LC', '802.3z', '1000BASE-LX'),
('SFP', 1, 'SMF', 0, 40000, 1310, NULL, 'LC', '802.3z', '1000BASE-EX'),
('SFP', 1, 'SMF', 0, 70000, 1550, NULL, 'LC', '802.3z', '1000BASE-ZX'),
('SFP', 1, 'SMF', 0, 10000, 1270, 1330, 'LC', 'SFF-8472', '1000BASE-BX10-U BiDi'),
('SFP', 1, 'SMF', 0, 10000, 1330, 1270, 'LC', 'SFF-8472', '1000BASE-BX10-D BiDi'),
('SFP', 1, 'Copper', 0, 100, NULL, NULL, 'RJ45','802.3ab', '1000BASE-T'),
-- ── SFP+ (10G) ───────────────────────────────────────────────────────────────
('SFP+', 10, 'MMF', 0, 300, 850, NULL, 'LC', '802.3ae', '10GBASE-SR'),
('SFP+', 10, 'SMF', 0, 10000, 1310, NULL, 'LC', '802.3ae', '10GBASE-LR'),
('SFP+', 10, 'SMF', 0, 40000, 1310, NULL, 'LC', '802.3ae', '10GBASE-ER'),
('SFP+', 10, 'SMF', 0, 80000, 1550, NULL, 'LC', '802.3ae', '10GBASE-ZR'),
('SFP+', 10, 'SMF', 0, 10000, 1270, 1330, 'LC', 'SFF-8431', '10GBASE-BX10-U BiDi'),
('SFP+', 10, 'SMF', 0, 10000, 1330, 1270, 'LC', 'SFF-8431', '10GBASE-BX10-D BiDi'),
('SFP+', 10, 'SMF', 0, 20000, 1270, 1330, 'LC', 'SFF-8431', '10GBASE-BX20-U BiDi'),
('SFP+', 10, 'SMF', 0, 20000, 1330, 1270, 'LC', 'SFF-8431', '10GBASE-BX20-D BiDi'),
('SFP+', 10, 'DAC', 0, 7, NULL, NULL, 'SFP+','SFF-8431','10G DAC Twinax'),
('SFP+', 10, 'AOC', 0, 100, 850, NULL, 'LC', 'SFF-8431', '10G AOC'),
-- ── SFP28 (25G) ──────────────────────────────────────────────────────────────
('SFP28', 25, 'MMF', 0, 100, 850, NULL, 'LC', '802.3by', '25GBASE-SR'),
('SFP28', 25, 'SMF', 0, 10000, 1310, NULL, 'LC', '802.3cc', '25GBASE-LR'),
('SFP28', 25, 'SMF', 0, 2000, 1310, NULL, 'LC', '25GBASE-DR','25GBASE-DR'),
('SFP28', 25, 'SMF', 0, 10000, 1270, 1330, 'LC', '802.3cc', '25GBASE-BX10-U BiDi'),
('SFP28', 25, 'SMF', 0, 10000, 1330, 1270, 'LC', '802.3cc', '25GBASE-BX10-D BiDi'),
('SFP28', 25, 'DAC', 0, 5, NULL, NULL, 'SFP28','802.3by','25G DAC'),
-- ── QSFP+ (40G) ──────────────────────────────────────────────────────────────
('QSFP+', 40, 'MMF', 0, 150, 850, NULL, 'MPO-12','802.3ba','40GBASE-SR4'),
('QSFP+', 40, 'SMF', 0, 10000, 1310, NULL, 'LC', '802.3ba', '40GBASE-LR4 CWDM4'),
('QSFP+', 40, 'SMF', 0, 2000, 1310, NULL, 'MPO-12','802.3bm','40GBASE-PSM4'),
('QSFP+', 40, 'DAC', 0, 7, NULL, NULL, 'QSFP+','802.3ba','40G DAC'),
-- ── QSFP28 (100G) ────────────────────────────────────────────────────────────
('QSFP28', 100, 'MMF', 0, 100, 850, NULL, 'MPO-12','802.3bm','100GBASE-SR4'),
('QSFP28', 100, 'SMF', 0, 10000, 1310, NULL, 'LC', '802.3cd', '100GBASE-LR4 CWDM4'),
('QSFP28', 100, 'SMF', 0, 500, 1310, NULL, 'MPO-12','802.3bj','100GBASE-DR (PSM4)'),
('QSFP28', 100, 'SMF', 0, 40000, 1310, NULL, 'LC', '802.3ba', '100GBASE-ER4'),
('QSFP28', 100, 'SMF', 0, 2000, 1310, NULL, 'LC', 'CWDM4-MSA','100G CWDM4 2km'),
('QSFP28', 100, 'DAC', 0, 5, NULL, NULL, 'QSFP28','802.3bj','100G DAC'),
('QSFP28', 100, 'AOC', 0, 100, 850, NULL, 'MPO-12','802.3bm','100G AOC SR4'),
-- ── QSFP56 (200G) ────────────────────────────────────────────────────────────
('QSFP56', 200, 'MMF', 0, 100, 850, NULL, 'MPO-16','802.3cd','200GBASE-SR4'),
('QSFP56', 200, 'SMF', 0, 2000, 1310, NULL, 'LC', '802.3cd', '200GBASE-DR4'),
('QSFP56', 200, 'SMF', 0, 10000, 1310, NULL, 'LC', '802.3cd', '200GBASE-FR4'),
('QSFP56', 200, 'SMF', 0, 40000, 1310, NULL, 'LC', '802.3cd', '200GBASE-LR4'),
-- ── QSFP-DD (400G) ───────────────────────────────────────────────────────────
('QSFP-DD', 400, 'MMF', 0, 100, 850, NULL, 'MPO-16','802.3bs','400GBASE-SR8'),
('QSFP-DD', 400, 'SMF', 0, 500, 1310, NULL, 'MPO-12','802.3bs','400GBASE-DR4'),
('QSFP-DD', 400, 'SMF', 0, 2000, 1310, NULL, 'LC', '802.3bs', '400GBASE-FR4'),
('QSFP-DD', 400, 'SMF', 0, 10000, 1310, NULL, 'LC', '802.3bs', '400GBASE-LR4'),
('QSFP-DD', 400, 'SMF', 0, 10000, 1310, NULL, 'MPO-12','800G MSA','400GBASE-PSM4'),
('QSFP-DD', 400, 'DAC', 0, 5, NULL, NULL, 'QSFP-DD','802.3bs','400G DAC'),
-- ── OSFP / QSFP-DD800 (800G) ─────────────────────────────────────────────────
('OSFP', 800, 'MMF', 0, 100, 850, NULL, 'MPO-16','802.3df','800GBASE-SR8'),
('OSFP', 800, 'SMF', 0, 500, 1310, NULL, 'MPO-12','802.3df','800GBASE-DR8'),
('OSFP', 800, 'SMF', 0, 2000, 1310, NULL, 'LC', '802.3df', '800GBASE-FR4 2x400G'),
('OSFP', 800, 'SMF', 0, 10000, 1310, NULL, 'LC', '802.3df', '800GBASE-LR4'),
('QSFP-DD800', 800, 'MMF', 0, 100, 850, NULL, 'MPO-16','802.3df','800GBASE-SR8'),
('QSFP-DD800', 800, 'SMF', 0, 500, 1310, NULL, 'MPO-12','802.3df','800GBASE-DR8')
ON CONFLICT DO NOTHING;
CREATE INDEX IF NOT EXISTS idx_ieee_lookup ON ieee_wavelength_lookup
(form_factor, speed_gbps, fiber_type, reach_min_m, reach_max_m);

View File

@ -0,0 +1,48 @@
-- Migration 112: wavelengths TEXT → wavelength_tx_nm / wavelength_rx_nm INTEGER
UPDATE transceivers SET
wavelength_tx_nm = CASE
WHEN wavelengths ~ '^\s*850' THEN 850
WHEN wavelengths ~ '^\s*1270' THEN 1270
WHEN wavelengths ~ '^\s*1310' THEN 1310
WHEN wavelengths ~ '^\s*1330' THEN 1330
WHEN wavelengths ~ '^\s*1490' THEN 1490
WHEN wavelengths ~ '^\s*1550' THEN 1550
WHEN wavelengths ~ '^\s*1270\s*/\s*1330' THEN 1270
WHEN wavelengths ~ '^\s*1330\s*/\s*1270' THEN 1330
ELSE NULL
END,
wavelength_rx_nm = CASE
WHEN wavelengths ~ '1270\s*/\s*1330' THEN 1330
WHEN wavelengths ~ '1330\s*/\s*1270' THEN 1270
WHEN wavelengths ~ '1310\s*/\s*1550' THEN 1550
WHEN wavelengths ~ '1550\s*/\s*1310' THEN 1310
ELSE NULL
END
WHERE wavelengths IS NOT NULL
AND wavelength_tx_nm IS NULL;
-- Connector aus alter connector-Spalte übernehmen (falls vorhanden)
UPDATE transceivers SET
connector_type = CASE connector
WHEN 'LC' THEN 'LC'
WHEN 'SC' THEN 'SC'
WHEN 'MPO' THEN 'MPO-12'
WHEN 'MPO-12' THEN 'MPO-12'
WHEN 'MPO-16' THEN 'MPO-16'
WHEN 'RJ45' THEN 'RJ45'
ELSE connector
END
WHERE connector IS NOT NULL AND connector_type IS NULL;
-- Completeness neu berechnen nach Migration
UPDATE transceivers SET
data_completeness = calc_data_completeness(
form_factor, speed_gbps, fiber_type,
reach_meters, wavelength_tx_nm, connector_type
),
enrichment_needed = (
form_factor IS NULL OR speed_gbps IS NULL OR
fiber_type IS NULL OR reach_meters IS NULL OR
wavelength_tx_nm IS NULL OR connector_type IS NULL
);