/** * Network Standards & Draft Activity Tracker * * Monitors the status of key networking standards that directly * affect transceiver form factor adoption timelines: * * - IEEE 802.3 (Ethernet PHY standards) * - IETF (network protocols) * - OIF (Optical Internetworking Forum — MSA agreements) * - CMIS (Common Management Interface Specification) * * Standard status is a 12-24 month leading indicator: * "Draft 3.0 approved" → ~18 months to mass-market products * "Published" → OEMs start shipping within 12 months * "Ballot closed" → final spec in ~3 months */ import * as cheerio from "cheerio"; import { pool } from "../utils/db"; import { logger } from "../utils/logger"; const HEADERS = { "User-Agent": "TIP-DataCollector/1.0 contact@context-x.org", "Accept": "text/html,application/xhtml+xml", }; interface StandardStatus { standard_body: string; standard_name: string; technology: string; current_status: string; draft_version: string | null; approval_date: string | null; source_url: string; notes: string | null; } // ─── IEEE 802.3 ──────────────────────────────────────────────────────────── async function scrapeIeee802(): Promise { const url = "https://www.ieee802.org/3/"; const res = await fetch(url, { headers: HEADERS, signal: AbortSignal.timeout(20000) }); if (!res.ok) throw new Error(`IEEE ${res.status}`); const $ = cheerio.load(await res.text()); const results: StandardStatus[] = []; // IEEE 802.3 project table $("table tr").each((_, row) => { const cells = $(row).find("td, th"); if (cells.length < 2) return; const taskName = $(cells[0]).text().trim(); const statusText = $(cells[1]).text().trim(); const link = $(cells[0]).find("a").first().attr("href"); // Filter for high-speed Ethernet projects const speedMatch = taskName.match(/\b(100G|200G|400G|800G|1\.6T|1600G)\b/i) || taskName.match(/802\.3(bs|cd|cu|ck|df|dk|dj|dl)\b/i); if (!speedMatch) return; const tech = taskName.match(/\b(400G|800G|1\.6T|100G|200G)\b/i)?.[1] ?? "high-speed"; let status = "in-progress"; if (/published|approved/i.test(statusText)) status = "published"; else if (/ballot/i.test(statusText)) status = "ballot"; else if (/withdrawn|cancelled/i.test(statusText)) status = "cancelled"; results.push({ standard_body: "ieee", standard_name: taskName.substring(0, 100), technology: tech, current_status: status, draft_version: null, approval_date: null, source_url: link ? `https://www.ieee802.org/3/${link}` : url, notes: statusText.substring(0, 200), }); }); return results; } // ─── OIF ────────────────────────────────────────────────────────────────── async function scrapeOif(): Promise { const url = "https://www.oiforum.com/technical-work/hot-topics/"; const res = await fetch(url, { headers: HEADERS, signal: AbortSignal.timeout(20000) }); if (!res.ok) throw new Error(`OIF ${res.status}`); const $ = cheerio.load(await res.text()); const results: StandardStatus[] = []; $(".entry-content h2, .entry-content h3").each((_, el) => { const heading = $(el).text().trim(); if (!heading) return; const link = $(el).find("a").first().attr("href") || url; const techMatch = heading.match(/\b(400ZR|800ZR|CMIS|400G|800G|CPO|OIF-)\S*/i); if (!techMatch) return; const tech = heading.includes("ZR") ? heading.includes("800") ? "800G-ZR" : "400G-ZR" : heading.includes("CMIS") ? "CMIS" : heading.includes("800") ? "800G" : heading.includes("400") ? "400G" : "optical"; results.push({ standard_body: "oif", standard_name: heading.substring(0, 100), technology: tech, current_status: "in-progress", // OIF IA are usually in-progress or published draft_version: null, approval_date: null, source_url: link, notes: null, }); }); return results; } // ─── IETF Datatracker ───────────────────────────────────────────────────── async function scrapeIetf(): Promise { // IETF has a proper REST API const url = "https://datatracker.ietf.org/api/v1/doc/document/?type=draft&name__contains=optical&limit=20&offset=0"; const res = await fetch(url, { headers: { ...HEADERS, Accept: "application/json" }, signal: AbortSignal.timeout(15000), }); if (!res.ok) throw new Error(`IETF API ${res.status}`); const data = await res.json() as { objects: Array<{ name: string; title: string; abstract: string; std_level: string | null; stream: string; }> }; return (data.objects ?? []).map(doc => { const tech = doc.name.includes("400g") ? "400G" : doc.name.includes("800g") ? "800G" : doc.title.match(/\b(400G|800G|ZR|coherent)\b/i)?.[1] ?? "optical"; return { standard_body: "ietf", standard_name: doc.name.substring(0, 100), technology: tech, current_status: doc.std_level ? "published" : "in-progress", draft_version: null, approval_date: null, source_url: `https://datatracker.ietf.org/doc/${doc.name}/`, notes: doc.title.substring(0, 200), }; }); } export async function scrapeStandardsTracker(): Promise { logger.info("Standards tracker starting"); let updated = 0; const scrapers: Array<{ name: string; fn: () => Promise }> = [ { name: "IEEE 802.3", fn: scrapeIeee802 }, { name: "OIF", fn: scrapeOif }, { name: "IETF", fn: scrapeIetf }, ]; for (const s of scrapers) { try { await new Promise(r => setTimeout(r, 2000)); logger.info(`Checking ${s.name}`); const standards = await s.fn(); for (const std of standards) { await pool.query(` INSERT INTO standards_activity (standard_body, standard_name, technology, current_status, draft_version, approval_date, source_url, notes, last_checked) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NOW()) ON CONFLICT (standard_body, standard_name) DO UPDATE SET current_status = EXCLUDED.current_status, technology = EXCLUDED.technology, notes = COALESCE(EXCLUDED.notes, standards_activity.notes), last_checked = NOW() `, [std.standard_body, std.standard_name, std.technology, std.current_status, std.draft_version, std.approval_date, std.source_url, std.notes]); updated++; } logger.info(`${s.name}: ${standards.length} standards checked`); } catch (err) { logger.warn(`Standards scraper failed: ${s.name}`, { err }); } } logger.info(`Standards tracker done — ${updated} records updated`); }