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 {
|
interface ParsedSpecs {
|
||||||
|
formFactor: string | null;
|
||||||
|
speedGbps: number | null;
|
||||||
complianceCode: string | null;
|
complianceCode: string | null;
|
||||||
laserType: string | null;
|
laserType: string | null;
|
||||||
receiverType: 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();
|
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.
|
* 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"));
|
const rxPowers = parseDbm(specValue(specs, "Receiver min/max per lane"));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
formFactor: normalizeFormFactorSpec(specValue(specs, "Form Factor")),
|
||||||
|
speedGbps: nominalSpeedFromSpecs(specs),
|
||||||
complianceCode: specValue(specs, "Compliance Code"),
|
complianceCode: specValue(specs, "Compliance Code"),
|
||||||
laserType: specValue(specs, "Laser"),
|
laserType: specValue(specs, "Laser"),
|
||||||
receiverType: specValue(specs, "Receiver Type"),
|
receiverType: specValue(specs, "Receiver Type"),
|
||||||
@ -349,6 +414,9 @@ async function writeDetails(
|
|||||||
|
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
UPDATE transceivers SET
|
UPDATE transceivers SET
|
||||||
|
form_factor = COALESCE($23, form_factor),
|
||||||
|
speed_gbps = COALESCE($24, speed_gbps),
|
||||||
|
speed = COALESCE($25, speed),
|
||||||
fx_specifications = $1,
|
fx_specifications = $1,
|
||||||
fx_compatibilities = $2,
|
fx_compatibilities = $2,
|
||||||
compliance_code = COALESCE(compliance_code, $3),
|
compliance_code = COALESCE(compliance_code, $3),
|
||||||
@ -396,6 +464,9 @@ async function writeDetails(
|
|||||||
product.image ?? null, // $20
|
product.image ?? null, // $20
|
||||||
product.url ?? null, // $21
|
product.url ?? null, // $21
|
||||||
transceiverId, // $22
|
transceiverId, // $22
|
||||||
|
parsed.formFactor, // $23
|
||||||
|
parsed.speedGbps, // $24
|
||||||
|
speedLabelGbps(parsed.speedGbps), // $25
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user