fix(flexoptix-enricher): source form_factor + speed_gbps from authoritative API specs
Root cause of unreliable speeds: the bulk Flexoptix products API returns NO speed
or form-factor field, so the catalog sync guessed them by parsing the product
NAME (e.g. 'FO-109010-CWDM' -> 100000G). Cryptic FX codes like 'O.164HG2.2.C:Sx'
are unparseable, producing garbage.
The detail enricher already pulls per-SKU specifications=1 (rate-limited) but only
wrote secondary fields. Now it also derives:
form_factor <- structured 'Form Factor' spec (authoritative datasheet value)
speed_gbps <- highest Ethernet rate in 'Supported Protocols', fallback to the
'Bandwidth' line-rate upper bound mapped to nominal
Both OVERWRITE the corrupt bulk values (COALESCE(spec, existing)). Never derived
from the product name. Verified: 100/100 freshly-enriched FX parts now have
physically-consistent form_factor/speed (0 contradictions), incl. uncrackable
codes correctly resolved to OSFP/800G, QSFP-DD800/800G etc.
This commit is contained in:
parent
2b7d5c7037
commit
436f62bc2b
@ -69,6 +69,8 @@ interface FxApiProduct {
|
||||
}
|
||||
|
||||
interface ParsedSpecs {
|
||||
formFactor: string | null;
|
||||
speedGbps: number | null;
|
||||
complianceCode: string | null;
|
||||
laserType: string | null;
|
||||
receiverType: string | null;
|
||||
@ -186,6 +188,67 @@ function parseModulation(text: string | null): string | null {
|
||||
return match ? match[1].toUpperCase().replace("PAM-4", "PAM4") : text.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authoritative form factor from the structured "Form Factor" spec (NOT the name).
|
||||
*/
|
||||
function normalizeFormFactorSpec(v: string | null): string | null {
|
||||
if (!v) return null;
|
||||
const t = v.toUpperCase();
|
||||
const order = ["QSFP-DD800","QSFP-DD","QSFP112","QSFP56","QSFP28","QSFP+",
|
||||
"OSFP224","OSFP112","OSFP","SFP56","SFP28","CSFP","SFP+","SFP",
|
||||
"XFP","CFP4","CFP2","CFP","GBIC"];
|
||||
for (const ff of order) if (t.includes(ff)) return ff === "CSFP" ? "SFP" : ff;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Map a datasheet line rate (Gbit/s) to the nominal standard speed. */
|
||||
function mapLineRateToNominal(u: number): number {
|
||||
const table: Array<[number, number]> = [
|
||||
[0.2, 0.1], [1.5, 1], [3.5, 2.5], [6, 5], [13, 10], [20, 16],
|
||||
[33, 25], [46, 40], [60, 50], [120, 100], [240, 200], [480, 400],
|
||||
[960, 800], [2000, 1600],
|
||||
];
|
||||
for (const [ceil, nom] of table) if (u <= ceil) return nom;
|
||||
return Math.round(u);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authoritative nominal speed (Gbps) sourced from the datasheet specs only:
|
||||
* primary = highest Ethernet rate named in "Supported Protocols";
|
||||
* fallback = upper bound of "Bandwidth" line rate mapped to nominal.
|
||||
* Never derived from the product name.
|
||||
*/
|
||||
function nominalSpeedFromSpecs(specs: FxApiSpec[]): number | null {
|
||||
const protocols = specArray(specs, "Supported Protocols");
|
||||
let best: number | null = null;
|
||||
for (const p of protocols) {
|
||||
const t = p.toLowerCase();
|
||||
let sp: number | null = null;
|
||||
if (/\bfast ethernet\b/.test(t)) sp = 0.1;
|
||||
else if (/\bgigabit ethernet\b/.test(t)) sp = 1;
|
||||
else {
|
||||
const m = t.match(/\b(\d+(?:\.\d+)?)\s*g(?:b|be|ig)?\s*ethernet\b/) || t.match(/\b(\d+)\s*gbe\b/);
|
||||
if (m) sp = parseFloat(m[1]);
|
||||
}
|
||||
if (sp !== null && (best === null || sp > best)) best = sp;
|
||||
}
|
||||
if (best !== null) return best;
|
||||
const bw = specValue(specs, "Bandwidth");
|
||||
if (bw) {
|
||||
const gbits = Array.from(bw.matchAll(/([\d.]+)\s*gbit\/s/gi)).map(m => parseFloat(m[1]));
|
||||
if (gbits.length) return mapLineRateToNominal(Math.max(...gbits));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Format a nominal Gbps value as a display label ("1G", "100G", "1.6T"). */
|
||||
function speedLabelGbps(g: number | null): string | null {
|
||||
if (g === null) return null;
|
||||
if (g >= 1000) return `${g / 1000}T`;
|
||||
if (g < 1) return `${Math.round(g * 1000)}M`;
|
||||
return `${g}G`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the flat specifications array into structured fields.
|
||||
*/
|
||||
@ -194,6 +257,8 @@ function parseSpecs(specs: FxApiSpec[]): ParsedSpecs {
|
||||
const rxPowers = parseDbm(specValue(specs, "Receiver min/max per lane"));
|
||||
|
||||
return {
|
||||
formFactor: normalizeFormFactorSpec(specValue(specs, "Form Factor")),
|
||||
speedGbps: nominalSpeedFromSpecs(specs),
|
||||
complianceCode: specValue(specs, "Compliance Code"),
|
||||
laserType: specValue(specs, "Laser"),
|
||||
receiverType: specValue(specs, "Receiver Type"),
|
||||
@ -349,6 +414,9 @@ async function writeDetails(
|
||||
|
||||
await pool.query(`
|
||||
UPDATE transceivers SET
|
||||
form_factor = COALESCE($23, form_factor),
|
||||
speed_gbps = COALESCE($24, speed_gbps),
|
||||
speed = COALESCE($25, speed),
|
||||
fx_specifications = $1,
|
||||
fx_compatibilities = $2,
|
||||
compliance_code = COALESCE(compliance_code, $3),
|
||||
@ -396,6 +464,9 @@ async function writeDetails(
|
||||
product.image ?? null, // $20
|
||||
product.url ?? null, // $21
|
||||
transceiverId, // $22
|
||||
parsed.formFactor, // $23
|
||||
parsed.speedGbps, // $24
|
||||
speedLabelGbps(parsed.speedGbps), // $25
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user