transceiver-db/scripts/validate-specs.ts
Rene Fichtmueller 73ef5766e6 feat(v0.2.1): data confidence tracking + validation + blog feedback system
- 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
2026-03-31 09:12:37 +02:00

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 || "tip_prod_2026",
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); });