diff --git a/packages/scraper/src/robots/wavelength-enricher.ts b/packages/scraper/src/robots/wavelength-enricher.ts new file mode 100644 index 0000000..70945f6 --- /dev/null +++ b/packages/scraper/src/robots/wavelength-enricher.ts @@ -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 { + 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 ==="); +} diff --git a/packages/scraper/src/scheduler.ts b/packages/scraper/src/scheduler.ts index f013b5e..8bf7f4e 100644 --- a/packages/scraper/src/scheduler.ts +++ b/packages/scraper/src/scheduler.ts @@ -354,6 +354,8 @@ export async function registerSchedules(boss: PgBoss): Promise { "discover:vendor:nokia", "discover:vendor:huawei", "discover:vendor:ii-vi", + // ── Wavelength Enrichment ──────────────────────────────────────────── + "enrich:wavelength", ]; for (const q of queues) { @@ -420,6 +422,9 @@ export async function registerSchedules(boss: PgBoss): Promise { expireInSeconds: 3600, }); + // Wavelength Enricher — läuft alle 4 Stunden + await boss.schedule("enrich:wavelength", "0 */4 * * *", {}, {}); + // ══════════════════════════════════════════════════════════════════════ // MANUFACTURER CATALOGS — every 4h (product data, no prices) // ══════════════════════════════════════════════════════════════════════ @@ -920,6 +925,13 @@ export async function registerWorkers(boss: PgBoss): Promise { 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 () => { console.log(`[${new Date().toISOString()}] Running: SmartOptics catalog`); await scrapeSmartOptics(); diff --git a/sql/110-wavelength-connector-completeness.sql b/sql/110-wavelength-connector-completeness.sql new file mode 100644 index 0000000..24c4239 --- /dev/null +++ b/sql/110-wavelength-connector-completeness.sql @@ -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'; diff --git a/sql/111-ieee-msa-wavelength-lookup.sql b/sql/111-ieee-msa-wavelength-lookup.sql new file mode 100644 index 0000000..b081730 --- /dev/null +++ b/sql/111-ieee-msa-wavelength-lookup.sql @@ -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); diff --git a/sql/112-migrate-wavelengths-text-to-int.sql b/sql/112-migrate-wavelengths-text-to-int.sql new file mode 100644 index 0000000..9629c10 --- /dev/null +++ b/sql/112-migrate-wavelengths-text-to-int.sql @@ -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 + );