- Migration 016: data_confidence column (vendor_verified/enriched_estimated/scraped_unverified) - Migration 015: blog_feedback table with 8 quality scores + free text - Validation script: 8 physics-based rules (wavelength↔fiber, reach plausibility, power limits) - Blog feedback API: POST /api/blog/:id/feedback + training data export - FO Blog Pipeline v3: 10-step Flexoptix Style prompts (Less bullshit. More engineering.) - Auto-fix: wavelength↔fiber mismatches corrected automatically
159 lines
5.8 KiB
TypeScript
159 lines
5.8 KiB
TypeScript
/**
|
|
* 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<string, number> = {
|
|
"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); });
|