/** * WS1: Switch → Flexoptix Transceiver Finder * * "Customer has a Cisco Nexus 93180YC-FX3 — which Flexoptix transceivers fit?" */ import { Router } from "express"; import { pool } from "../db/client"; export const finderRouter = Router(); /** * GET /api/finder?switch=&speed=&form_factor= * * Finds Flexoptix-compatible transceivers for a given switch model. * If no direct Flexoptix match, shows generic compatible transceivers * with a note about Flexoptix FlexBox coding capability. */ finderRouter.get("/", async (req, res) => { try { const { switch: switchQuery, speed, form_factor, limit = "20" } = req.query; if (!switchQuery) { return res.status(400).json({ error: "Parameter 'switch' is required" }); } // Step 1: Find the switch — try multiple search strategies const q = String(switchQuery); // Normalized form: remove hyphens/spaces for fuzzy match (sg350-28 → sg35028) const qNorm = q.replace(/[\s\-_]/g, ""); const switchResult = await pool.query( `SELECT sw.id, sw.model, sw.series, sw.ports_config, sw.max_speed_gbps, v.name AS vendor_name, sw.image_url, sw.datasheet_r2_key FROM switches sw JOIN vendors v ON sw.vendor_id = v.id WHERE sw.model ILIKE $1 OR sw.model ILIKE '%' || $1 || '%' OR REPLACE(REPLACE(sw.model, '-', ''), ' ', '') ILIKE '%' || $2 || '%' OR sw.search_vector @@ plainto_tsquery('english', $3) ORDER BY CASE WHEN sw.model ILIKE $1 THEN 0 WHEN sw.model ILIKE $1 || '%' THEN 1 WHEN REPLACE(REPLACE(sw.model, '-', ''), ' ', '') ILIKE $2 || '%' THEN 2 ELSE 3 END LIMIT 5`, [q, qNorm, q.replace(/[^\w\s]/g, " ")] ); if (switchResult.rows.length === 0) { // Try one more time with individual tokens const tokens = q.split(/[\s\-_]+/).filter((t) => t.length >= 3); let fallbackResult = { rows: [] as any[] }; if (tokens.length > 0) { fallbackResult = await pool.query( `SELECT sw.id, sw.model, sw.series, sw.ports_config, sw.max_speed_gbps, v.name AS vendor_name, sw.image_url, sw.datasheet_r2_key FROM switches sw JOIN vendors v ON sw.vendor_id = v.id WHERE ${tokens.map((_, i) => `sw.model ILIKE '%' || $${i + 1} || '%'`).join(" AND ")} LIMIT 5`, tokens ); } if (fallbackResult.rows.length === 0) { return res.status(404).json({ error: `Switch "${q}" not found in the database`, suggestion: `Try a partial model name, e.g. "${tokens[0] || q.substring(0, 6)}" — or check spelling. Available: Cisco Nexus, Arista, Juniper QFX, Edgecore, Mellanox.` }); } switchResult.rows.push(...fallbackResult.rows); } const sw = switchResult.rows[0]; // Step 2: Find compatible transceivers via compatibility table let compatSql = ` SELECT t.id, t.slug, t.form_factor, t.speed, t.speed_gbps, t.reach_label, t.reach_meters, t.fiber_type, t.wavelengths, t.connector, t.power_consumption_w, t.image_url, t.image_r2_key, t.part_number, t.product_page_url, -- Verification tags t.price_verified, t.price_verified_eur, t.price_verified_url, t.price_verified_at, t.image_verified, t.details_verified, t.fully_verified, t.fully_verified_at, tv.name AS transceiver_vendor, tv.type AS vendor_type, c.status AS compat_status, c.firmware_min, c.verified_by, c.notes AS compat_notes, -- Latest price (verified preferred) COALESCE(t.price_verified_eur, (SELECT po.price FROM price_observations po WHERE po.transceiver_id = t.id ORDER BY po.time DESC LIMIT 1) ) AS latest_price, CASE WHEN t.price_verified_eur IS NOT NULL THEN 'EUR' ELSE (SELECT po.currency FROM price_observations po WHERE po.transceiver_id = t.id ORDER BY po.time DESC LIMIT 1) END AS latest_currency, (SELECT po.stock_level FROM price_observations po WHERE po.transceiver_id = t.id ORDER BY po.time DESC LIMIT 1) AS stock_level, -- Flexoptix mapping fpm.flexoptix_sku, fpm.flexoptix_url, fpm.flexoptix_price_eur, fpm.match_type AS flexoptix_match FROM compatibility c JOIN transceivers t ON c.transceiver_id = t.id JOIN vendors tv ON t.vendor_id = tv.id LEFT JOIN flexoptix_product_map fpm ON ( fpm.form_factor = t.form_factor AND fpm.speed_gbps = t.speed_gbps AND (fpm.reach_label = t.reach_label OR fpm.reach_label IS NULL) ) WHERE c.switch_id = $1 AND c.status = 'compatible' `; const params: any[] = [sw.id]; let idx = 2; if (speed) { compatSql += ` AND t.speed_gbps = $${idx}`; params.push(parseFloat(speed as string)); idx++; } if (form_factor) { compatSql += ` AND t.form_factor = $${idx}`; params.push(form_factor); idx++; } compatSql += ` ORDER BY t.speed_gbps DESC, t.reach_meters ASC LIMIT $${idx}`; params.push(parseInt(limit as string)); const compatResult = await pool.query(compatSql, params); // Step 3: Group results by speed class const bySpeed: Record = {}; for (const row of compatResult.rows) { const key = `${row.speed_gbps}G ${row.form_factor}`; if (!bySpeed[key]) bySpeed[key] = []; bySpeed[key].push({ ...row, flexoptix_available: !!row.flexoptix_sku, flexbox_codable: true, // All Flexoptix modules are FlexBox-codable buy_url: row.flexoptix_url || `https://www.flexoptix.net/en/catalogsearch/result/?q=${encodeURIComponent(row.form_factor + ' ' + row.speed_gbps + 'G ' + row.reach_label)}`, }); } // Step 4: Extract port types from switch for "what can this switch accept?" const portTypes = sw.ports_config || {}; res.json({ switch: { id: sw.id, model: sw.model, series: sw.series, vendor: sw.vendor_name, max_speed_gbps: sw.max_speed_gbps, ports: portTypes, image_url: sw.image_url, }, compatible_transceivers: compatResult.rows.map(r => ({ id: r.id, slug: r.slug, part_number: r.part_number, form_factor: r.form_factor, speed: r.speed, speed_gbps: r.speed_gbps, reach: r.reach_label, fiber_type: r.fiber_type, connector: r.connector, vendor: r.transceiver_vendor, vendor_type: r.vendor_type, image_url: r.image_url, product_page_url: r.product_page_url, compat_status: r.compat_status, firmware_min: r.firmware_min, // Pricing price: r.latest_price ? parseFloat(r.latest_price) : null, currency: r.latest_currency, stock: r.stock_level, // Verification tags price_verified: r.price_verified === true, price_verified_eur: r.price_verified_eur ? parseFloat(r.price_verified_eur) : null, price_verified_url: r.price_verified_url || null, price_verified_at: r.price_verified_at || null, image_verified: r.image_verified === true, details_verified: r.details_verified === true, fully_verified: r.fully_verified === true, fully_verified_at: r.fully_verified_at || null, // Flexoptix flexoptix_sku: r.flexoptix_sku, flexoptix_url: r.flexoptix_url, flexoptix_price_eur: r.flexoptix_price_eur ? parseFloat(r.flexoptix_price_eur) : null, flexoptix_match: r.flexoptix_match, flexbox_codable: true, buy_url: r.flexoptix_url || `https://www.flexoptix.net/en/catalogsearch/result/?q=${encodeURIComponent(r.form_factor + ' ' + r.speed_gbps + 'G ' + r.reach_label)}`, })), by_speed_class: bySpeed, total: compatResult.rowCount, flexoptix_note: "All Flexoptix transceivers support FlexBox coding for OEM compatibility.", }); } catch (err) { console.error("Finder error:", err); res.status(500).json({ error: "Internal server error" }); } }); /** * GET /api/finder/suggest?q= * * Free-text query: "100G LR4 for Cisco Nexus" → suggests switch + transceiver combos */ finderRouter.get("/suggest", async (req, res) => { try { const { q } = req.query; if (!q) return res.status(400).json({ error: "Parameter 'q' is required" }); // Extract speed, form factor, vendor hints from query const queryStr = (q as string).toLowerCase(); let speed: number | null = null; let vendor: string | null = null; let reach: string | null = null; // Speed detection const speedMatch = queryStr.match(/(\d+)\s*g\b/i); if (speedMatch) speed = parseInt(speedMatch[1]!); // Reach detection if (queryStr.includes('sr')) reach = 'SR'; else if (queryStr.includes('lr')) reach = 'LR'; else if (queryStr.includes('er')) reach = 'ER'; else if (queryStr.includes('zr')) reach = 'ZR'; else if (queryStr.includes('dr')) reach = 'DR'; // Vendor detection const vendorPatterns: [RegExp, string][] = [ [/cisco|nexus|catalyst/i, 'Cisco'], [/juniper|qfx|ex\d{4}/i, 'Juniper'], [/arista|dcs-/i, 'Arista'], [/dell|powerswitch/i, 'Dell'], [/hpe|aruba/i, 'HPE'], ]; for (const [pattern, name] of vendorPatterns) { if (pattern.test(queryStr)) { vendor = name; break; } } // Search switches matching the query const switches = await pool.query( `SELECT sw.id, sw.model, sw.series, sw.max_speed_gbps, v.name AS vendor_name FROM switches sw JOIN vendors v ON sw.vendor_id = v.id WHERE sw.search_vector @@ plainto_tsquery('english', $1) ${vendor ? `AND v.name ILIKE '%' || $2 || '%'` : ''} ORDER BY sw.max_speed_gbps DESC LIMIT 10`, vendor ? [q, vendor] : [q] ); // Search transceivers matching speed/reach let tcvrSql = `SELECT t.id, t.slug, t.form_factor, t.speed_gbps, t.reach_label, t.fiber_type, tv.name AS vendor, t.image_url FROM transceivers t JOIN vendors v ON t.vendor_id = v.id JOIN vendors tv ON t.vendor_id = tv.id WHERE 1=1`; const tcvrParams: any[] = []; let tidx = 1; if (speed) { tcvrSql += ` AND t.speed_gbps = $${tidx}`; tcvrParams.push(speed); tidx++; } if (reach) { tcvrSql += ` AND t.reach_label ILIKE $${tidx}`; tcvrParams.push(reach + '%'); tidx++; } tcvrSql += ` ORDER BY t.speed_gbps DESC LIMIT 10`; const transceivers = await pool.query(tcvrSql, tcvrParams); res.json({ query: q, parsed: { speed, vendor, reach }, switches: switches.rows, transceivers: transceivers.rows, tip: "Use GET /api/finder?switch= for detailed compatibility results", }); } catch (err) { console.error("Suggest error:", err); res.status(500).json({ error: "Internal server error" }); } });