fix(equivalences): spec-matcher no longer bypasses review

spec-matcher.ts inserted every JOIN match straight into status=auto_approved
at a flat confidence=0.85 -- no standard_name check, no fiber_type check, no
recent-competitor-price check, none of the discrimination catalog-reconcile.ts
uses before auto-approving. That is real ground live: of the 1,455 currently
auto_approved spec-basis equivalences, ~19% would fail a recent-price check
and ~8% have a conflicting standard_name if re-scored against the rigorous
bar (see sql/140 for the one-time reclassification of the existing rows).

Fix: spec matches now insert as pending with re_research_due_at=NOW(), so
they flow through the same maintenance:re-research-equivalences job (fixed
2026-07-17, sql/139) that already scores catalog-reconcile.ts pending rows
-- real evidence-based confidence before anything is served as approved, not
a blanket 0.85. opn-matcher.ts is untouched: OPN matches are
manufacturer-confirmed ground truth from fx_compatibilities, not a heuristic
guess, so blanket auto_approve there remains correct.
This commit is contained in:
root 2026-07-17 20:17:02 +00:00
parent 723dfe7bdc
commit e7299ac779

View File

@ -14,9 +14,18 @@
* - Max 30 competitor matches per FX product (safety cap) * - Max 30 competitor matches per FX product (safety cap)
* *
* Match quality: * Match quality:
* confidence = 0.85 * confidence = 0.85 (starting prior only)
* match_basis = '{spec}' * match_basis = '{spec}'
* status = 'auto_approved' * status = 'pending' unlike OPN matches (manufacturer-confirmed ground
* truth), spec matches are a coarse JOIN with no standard_name / fiber_type /
* recent-price discrimination, so they route into the same
* maintenance:re-research-equivalences pipeline as catalog-reconcile.ts's
* pending rows (evaluateEquivalenceResearch(), scheduler.ts) for real
* confidence scoring before ever being served as an approved equivalence.
* Found live 2026-07-17: of the equivalences this matcher had previously put
* straight into 'auto_approved', ~19% failed a recent-competitor-price check
* and ~8% had a conflicting standard_name i.e. real false-positive risk
* was going live unreviewed.
*/ */
import { pool } from "../utils/db"; import { pool } from "../utils/db";
@ -39,13 +48,14 @@ const INSERT_SPEC_MATCHES = `
match_basis, match_basis,
match_notes, match_notes,
created_at, created_at,
updated_at updated_at,
re_research_due_at
) )
SELECT DISTINCT SELECT DISTINCT
fx.id AS flexoptix_id, fx.id AS flexoptix_id,
comp.id AS competitor_id, comp.id AS competitor_id,
0.85 AS confidence, 0.85 AS confidence,
'auto_approved' AS status, 'pending' AS status,
ARRAY['spec'] AS match_basis, ARRAY['spec'] AS match_basis,
'Spec match: ' || fx.form_factor || ' ' || fx.speed_gbps || 'G ' || 'Spec match: ' || fx.form_factor || ' ' || fx.speed_gbps || 'G ' ||
CASE WHEN fx.reach_meters <= 300 THEN 'SR' CASE WHEN fx.reach_meters <= 300 THEN 'SR'
@ -57,7 +67,8 @@ const INSERT_SPEC_MATCHES = `
THEN ' @' || tip_extract_wavelength_nm(fx.wavelengths) || 'nm' THEN ' @' || tip_extract_wavelength_nm(fx.wavelengths) || 'nm'
ELSE '' END AS match_notes, ELSE '' END AS match_notes,
NOW() AS created_at, NOW() AS created_at,
NOW() AS updated_at NOW() AS updated_at,
NOW() AS re_research_due_at
FROM transceivers fx FROM transceivers fx
JOIN vendors vfx ON vfx.id = fx.vendor_id AND UPPER(vfx.name) LIKE '%FLEXOPTIX%' JOIN vendors vfx ON vfx.id = fx.vendor_id AND UPPER(vfx.name) LIKE '%FLEXOPTIX%'
JOIN transceivers comp JOIN transceivers comp