/** * Spec Validation Script * * Cross-checks enriched data against optical physics rules: * - 850nm MUST be MMF (not SMF) * - 1310nm/1550nm MUST be SMF (not MMF) * - Copper transceivers MUST NOT have optical wavelengths * - DAC/AOC reach must be < 100m (DAC) / < 300m (AOC) * - Power consumption must be plausible for form factor * - Connector must match fiber type (MPO for parallel, LC for duplex) */ import { config } from "dotenv"; import { join } from "path"; import { Pool } from "pg"; config({ path: join(__dirname, "..", ".env") }); const pool = new Pool({ host: process.env.POSTGRES_HOST || "localhost", port: parseInt(process.env.POSTGRES_PORT || "5433"), database: process.env.POSTGRES_DB || "transceiver_db", user: process.env.POSTGRES_USER || "tip", password: process.env.POSTGRES_PASSWORD || "***REDACTED***", max: 3, }); interface ValidationError { id: string; slug: string; field: string; value: string; rule: string; severity: "error" | "warning"; } async function main() { console.log("Running spec validation...\n"); const result = await pool.query(` SELECT id, slug, form_factor, speed_gbps, reach_label, reach_meters, fiber_type, connector, wavelengths, power_consumption_w, category FROM transceivers WHERE data_confidence = 'enriched_estimated' OR data_confidence = 'unknown' `); console.log(`Validating ${result.rows.length} enriched/unknown products\n`); const errors: ValidationError[] = []; for (const row of result.rows) { const ft = row.fiber_type || ""; const conn = row.connector || ""; const wl = row.wavelengths || ""; const reach = row.reach_meters || 0; const speed = parseFloat(row.speed_gbps || 0); const power = row.power_consumption_w ? parseFloat(row.power_consumption_w) : null; const ff = row.form_factor || ""; // Rule 1: 850nm wavelength MUST be MMF (not SMF) if (wl.includes("850") && ft === "SMF") { errors.push({ id: row.id, slug: row.slug, field: "fiber_type", value: ft, rule: "850nm wavelength requires MMF, not SMF", severity: "error" }); } // Rule 2: 1310nm/1550nm wavelength MUST be SMF (not MMF, unless SWDM) if ((wl.includes("1310") || wl.includes("1550")) && ft === "MMF" && !wl.includes("SWDM")) { errors.push({ id: row.id, slug: row.slug, field: "fiber_type", value: ft, rule: "1310/1550nm requires SMF, not MMF", severity: "error" }); } // Rule 3: Copper must not have optical wavelengths if (ft === "Copper" && wl !== "N/A" && wl !== "" && !wl.includes("N/A")) { errors.push({ id: row.id, slug: row.slug, field: "wavelengths", value: wl, rule: "Copper transceiver should have wavelengths=N/A", severity: "warning" }); } // Rule 4: DAC reach must be <= 7m (passive) or <= 30m (active) if (conn === "DAC" && reach > 30) { errors.push({ id: row.id, slug: row.slug, field: "reach_meters", value: String(reach), rule: "DAC reach > 30m is implausible (max ~7m passive, ~30m active)", severity: "warning" }); } // Rule 5: AOC reach should be <= 300m if ((conn === "AOC" || ft === "AOC") && reach > 300) { errors.push({ id: row.id, slug: row.slug, field: "reach_meters", value: String(reach), rule: "AOC reach > 300m is implausible", severity: "warning" }); } // Rule 6: Power consumption plausibility if (power !== null) { const maxPower: Record = { "SFP": 1.5, "SFP+": 2.0, "SFP28": 2.0, "SFP56": 2.5, "QSFP+": 4.0, "QSFP28": 5.0, "QSFP56": 7.0, "QSFP-DD": 18.0, "OSFP": 22.0, "QSFP-DD800": 25.0, "CFP2": 12.0, "CFP2-DCO": 25.0, "XFP": 5.0, }; const max = maxPower[ff]; if (max && power > max * 1.5) { errors.push({ id: row.id, slug: row.slug, field: "power_consumption_w", value: String(power), rule: `Power ${power}W exceeds plausible max ${max}W for ${ff}`, severity: "error" }); } } // Rule 7: MPO connector should be for parallel optics (40G+, SR4/DR4/PSM4) if (conn.startsWith("MPO") && speed < 40 && ft !== "SMF") { errors.push({ id: row.id, slug: row.slug, field: "connector", value: conn, rule: "MPO connector unusual for <40G non-SMF", severity: "warning" }); } // Rule 8: LC connector for SMF is standard, but QSFP+ SR4 should be MPO if (conn === "LC" && ft === "MMF" && speed >= 40 && row.reach_label?.includes("SR")) { errors.push({ id: row.id, slug: row.slug, field: "connector", value: conn, rule: "SR4 on MMF at 40G+ typically uses MPO, not LC", severity: "warning" }); } } // Print results const errs = errors.filter(e => e.severity === "error"); const warns = errors.filter(e => e.severity === "warning"); console.log(`\nValidation Results:`); console.log(` Errors: ${errs.length}`); console.log(` Warnings: ${warns.length}`); console.log(` Total: ${errors.length}`); if (errs.length > 0) { console.log(`\n=== ERRORS (need fixing) ===`); for (const e of errs.slice(0, 30)) { console.log(` ${e.slug}: ${e.field}="${e.value}" — ${e.rule}`); } } if (warns.length > 0) { console.log(`\n=== WARNINGS (review) ===`); for (const w of warns.slice(0, 20)) { console.log(` ${w.slug}: ${w.field}="${w.value}" — ${w.rule}`); } } // Auto-fix clear errors let fixed = 0; for (const e of errs) { if (e.rule.includes("850nm wavelength requires MMF")) { await pool.query(`UPDATE transceivers SET fiber_type = 'MMF' WHERE id = $1`, [e.id]); fixed++; } if (e.rule.includes("1310/1550nm requires SMF")) { await pool.query(`UPDATE transceivers SET fiber_type = 'SMF' WHERE id = $1`, [e.id]); fixed++; } } if (fixed > 0) console.log(`\nAuto-fixed ${fixed} errors`); await pool.end(); } main().catch(err => { console.error(err); process.exit(1); });