- Add OPN-based equivalence matcher robot (7,245 manufacturer-confirmed matches, confidence=1.0) - Add spec-based equivalence matcher robot (683 matches, confidence=0.85) - Matches by form_factor + speed_gbps + reach_tier + wavelength ±10nm - Safety cap: skip FX products matching >30 competitors (too generic) - Daily schedule: 04:30 UTC via pg-boss - SQL migrations 116 (OPN) + 117 (spec) with tip_extract_wavelength_nm() + tip_reach_tier() helpers - Fix tenGtek.ts: add 3 missing 400G categories (QSFP-DD, QSFP112) — closes pricing gap - Generate tip-llm-pricing-v1.jsonl: 80 DB-grounded QA pairs (pricing, equivalences, 400G) - Rebuild TIP_LLM training pool: 11,999 pairs (+127 vs prev), deployed to Erik - FX product equivalence coverage: 88.1% (959/1089)
170 lines
6.3 KiB
TypeScript
170 lines
6.3 KiB
TypeScript
/**
|
|
* Spec-Based Equivalence Matcher
|
|
*
|
|
* Matches FX products with competitor products by technical specification
|
|
* when no OPN-based equivalence exists. Spec-matching is a fallback:
|
|
* OPN-confirmed matches (confidence=1.0) always take priority.
|
|
*
|
|
* Match criteria:
|
|
* - Same form_factor (exact)
|
|
* - Same speed_gbps (exact)
|
|
* - Same reach tier (SR/IR/LR/ER/ZR)
|
|
* - Same primary wavelength within ±10nm (CWDM/WDM safe)
|
|
* OR both have no wavelength data (broadband products)
|
|
* - Max 30 competitor matches per FX product (safety cap)
|
|
*
|
|
* Match quality:
|
|
* confidence = 0.85
|
|
* match_basis = '{spec}'
|
|
* status = 'auto_approved'
|
|
*/
|
|
|
|
import { pool } from "../utils/db";
|
|
|
|
export interface SpecMatcherResult {
|
|
inserted: number;
|
|
fxProductsScanned: number;
|
|
candidatePairs: number;
|
|
skippedExisting: number;
|
|
}
|
|
|
|
// ── Queries ──────────────────────────────────────────────────────────────────
|
|
|
|
const INSERT_SPEC_MATCHES = `
|
|
INSERT INTO transceiver_equivalences (
|
|
flexoptix_id,
|
|
competitor_id,
|
|
confidence,
|
|
status,
|
|
match_basis,
|
|
match_notes,
|
|
created_at,
|
|
updated_at
|
|
)
|
|
SELECT DISTINCT
|
|
fx.id AS flexoptix_id,
|
|
comp.id AS competitor_id,
|
|
0.85 AS confidence,
|
|
'auto_approved' AS status,
|
|
ARRAY['spec'] AS match_basis,
|
|
'Spec match: ' || fx.form_factor || ' ' || fx.speed_gbps || 'G ' ||
|
|
CASE WHEN fx.reach_meters <= 300 THEN 'SR'
|
|
WHEN fx.reach_meters <= 2000 THEN 'IR'
|
|
WHEN fx.reach_meters <= 10000 THEN 'LR'
|
|
WHEN fx.reach_meters <= 40000 THEN 'ER'
|
|
ELSE 'ZR' END ||
|
|
CASE WHEN tip_extract_wavelength_nm(fx.wavelengths) IS NOT NULL
|
|
THEN ' @' || tip_extract_wavelength_nm(fx.wavelengths) || 'nm'
|
|
ELSE '' END AS match_notes,
|
|
NOW() AS created_at,
|
|
NOW() AS updated_at
|
|
FROM transceivers fx
|
|
JOIN vendors vfx ON vfx.id = fx.vendor_id AND UPPER(vfx.name) LIKE '%FLEXOPTIX%'
|
|
JOIN transceivers comp
|
|
ON comp.form_factor = fx.form_factor
|
|
AND comp.speed_gbps = fx.speed_gbps
|
|
AND comp.reach_meters >= 10
|
|
AND tip_reach_tier(comp.reach_meters) = tip_reach_tier(fx.reach_meters)
|
|
AND (
|
|
(tip_extract_wavelength_nm(fx.wavelengths) IS NULL
|
|
AND tip_extract_wavelength_nm(comp.wavelengths) IS NULL)
|
|
OR ABS( COALESCE(tip_extract_wavelength_nm(comp.wavelengths), 0)
|
|
- COALESCE(tip_extract_wavelength_nm(fx.wavelengths), 0) ) <= 10
|
|
)
|
|
JOIN vendors vcomp ON vcomp.id = comp.vendor_id AND vcomp.is_competitor = true
|
|
WHERE fx.reach_meters >= 10
|
|
AND fx.speed_gbps > 0
|
|
-- OPN match already exists → skip (spec is fallback only)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM transceiver_equivalences e
|
|
WHERE e.flexoptix_id = fx.id AND 'opn' = ANY(e.match_basis)
|
|
)
|
|
-- Skip pairs that already have ANY equivalence
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM transceiver_equivalences e
|
|
WHERE e.flexoptix_id = fx.id AND e.competitor_id = comp.id
|
|
)
|
|
-- Safety cap: skip if > 30 competitors would match (too generic)
|
|
AND (
|
|
SELECT COUNT(DISTINCT c2.id)
|
|
FROM transceivers c2
|
|
JOIN vendors vc2 ON vc2.id = c2.vendor_id AND vc2.is_competitor = true
|
|
WHERE c2.form_factor = fx.form_factor
|
|
AND c2.speed_gbps = fx.speed_gbps
|
|
AND c2.reach_meters >= 10
|
|
AND tip_reach_tier(c2.reach_meters) = tip_reach_tier(fx.reach_meters)
|
|
AND (
|
|
(tip_extract_wavelength_nm(fx.wavelengths) IS NULL
|
|
AND tip_extract_wavelength_nm(c2.wavelengths) IS NULL)
|
|
OR ABS( COALESCE(tip_extract_wavelength_nm(c2.wavelengths), 0)
|
|
- COALESCE(tip_extract_wavelength_nm(fx.wavelengths), 0) ) <= 10
|
|
)
|
|
) <= 30
|
|
ON CONFLICT DO NOTHING
|
|
`;
|
|
|
|
const COUNT_FX_WITHOUT_OPN = `
|
|
SELECT COUNT(DISTINCT t.id) AS cnt
|
|
FROM transceivers t
|
|
JOIN vendors v ON v.id = t.vendor_id AND UPPER(v.name) LIKE '%FLEXOPTIX%'
|
|
WHERE t.reach_meters >= 10
|
|
AND t.speed_gbps > 0
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM transceiver_equivalences e
|
|
WHERE e.flexoptix_id = t.id AND 'opn' = ANY(e.match_basis)
|
|
)
|
|
`;
|
|
|
|
const COUNT_SPEC_CANDIDATES = `
|
|
SELECT COUNT(DISTINCT (fx.id, comp.id)) AS cnt
|
|
FROM transceivers fx
|
|
JOIN vendors vfx ON vfx.id = fx.vendor_id AND UPPER(vfx.name) LIKE '%FLEXOPTIX%'
|
|
JOIN transceivers comp
|
|
ON comp.form_factor = fx.form_factor
|
|
AND comp.speed_gbps = fx.speed_gbps
|
|
AND comp.reach_meters >= 10
|
|
AND tip_reach_tier(comp.reach_meters) = tip_reach_tier(fx.reach_meters)
|
|
AND (
|
|
(tip_extract_wavelength_nm(fx.wavelengths) IS NULL
|
|
AND tip_extract_wavelength_nm(comp.wavelengths) IS NULL)
|
|
OR ABS( COALESCE(tip_extract_wavelength_nm(comp.wavelengths), 0)
|
|
- COALESCE(tip_extract_wavelength_nm(fx.wavelengths), 0) ) <= 10
|
|
)
|
|
JOIN vendors vcomp ON vcomp.id = comp.vendor_id AND vcomp.is_competitor = true
|
|
WHERE fx.reach_meters >= 10
|
|
AND fx.speed_gbps > 0
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM transceiver_equivalences e
|
|
WHERE e.flexoptix_id = fx.id AND 'opn' = ANY(e.match_basis)
|
|
)
|
|
`;
|
|
|
|
// ── Main export ───────────────────────────────────────────────────────────────
|
|
|
|
export async function runSpecMatcher(): Promise<SpecMatcherResult> {
|
|
const ts = () => new Date().toISOString();
|
|
console.log(`[${ts()}] Spec Matcher starting`);
|
|
|
|
const fxRes = await pool.query<{ cnt: string }>(COUNT_FX_WITHOUT_OPN);
|
|
const fxProductsScanned = parseInt(fxRes.rows[0].cnt, 10);
|
|
|
|
const candRes = await pool.query<{ cnt: string }>(COUNT_SPEC_CANDIDATES);
|
|
const candidatePairs = parseInt(candRes.rows[0].cnt, 10);
|
|
|
|
console.log(
|
|
`[${ts()}] Spec Matcher: ${fxProductsScanned} FX products without OPN, ` +
|
|
`${candidatePairs} spec candidate pairs`,
|
|
);
|
|
|
|
const insertRes = await pool.query(INSERT_SPEC_MATCHES);
|
|
const inserted = insertRes.rowCount ?? 0;
|
|
const skippedExisting = candidatePairs - inserted;
|
|
|
|
console.log(
|
|
`[${ts()}] Spec Matcher done: ${inserted} new spec equivalences inserted ` +
|
|
`(${skippedExisting} pairs already existed or capped)`,
|
|
);
|
|
|
|
return { inserted, fxProductsScanned, candidatePairs, skippedExisting };
|
|
}
|