fix(equivalences): resolve wavelength column mismatch + reach-tier substitution policy
Root cause of the equivalence review-queue backlog: spec-matcher read transceivers.wavelengths (text), but the enricher wrote wavelength_tx_nm (int) — 27,908 rows had the wavelength in the wrong column, and tx_nm itself defaulted incorrectly to 1310nm in ~22% of disagreements with the authoritative part-derived value. sql/131: backfill + sync trigger from wavelength_tx_nm into wavelengths. sql/132: correct authority (bare-number wavelengths, not tx_nm) + format normalization + valid promotion on the full spec-matcher gate. sql/133: one-directional reach-tier substitution (higher reach may satisfy a lower-reach requirement, never the reverse; ZR/coherent excluded — real link-budget/receiver-overload risk, not a simple tier comparison). Tagged as its own status (compatible_superset), not merged into auto_approved.
This commit is contained in:
parent
3ebb552647
commit
91d19e152d
60
sql/131-wavelength-column-sync.sql
Normal file
60
sql/131-wavelength-column-sync.sql
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
-- sql/131 — Wavelength column sync (root-cause fix for equivalence review backlog)
|
||||||
|
--
|
||||||
|
-- Root cause: the enricher populates the structured integer column
|
||||||
|
-- transceivers.wavelength_tx_nm, but the equivalence spec-matcher reads the TEXT
|
||||||
|
-- column transceivers.wavelengths via tip_extract_wavelength_nm(). 27,908 rows had
|
||||||
|
-- wavelength_tx_nm set while wavelengths was NULL, so the matcher could not confirm
|
||||||
|
-- wavelength on either side and dumped ~3,155 genuine equivalences into manual review.
|
||||||
|
--
|
||||||
|
-- Fix (no fabrication — copies existing structured data into the format the matcher
|
||||||
|
-- expects, and keeps it in sync going forward):
|
||||||
|
-- A) one-off backfill of transceivers.wavelengths from wavelength_tx_nm
|
||||||
|
-- B) durable BEFORE-trigger so future ingests stay in sync
|
||||||
|
-- C) promote pending -> auto_approved ONLY where the FULL spec-matcher criteria now
|
||||||
|
-- agree (form_factor + speed + fiber_type + reach-tier + wavelength +/-10nm).
|
||||||
|
-- Everything with a genuine reach-tier / fiber / wavelength difference stays pending.
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- A) one-off backfill (existing structured data -> text column the matcher reads)
|
||||||
|
UPDATE transceivers
|
||||||
|
SET wavelengths = wavelength_tx_nm || 'nm'
|
||||||
|
WHERE (wavelengths IS NULL OR wavelengths = '')
|
||||||
|
AND wavelength_tx_nm IS NOT NULL;
|
||||||
|
|
||||||
|
-- B) durable sync trigger for future inserts/updates
|
||||||
|
CREATE OR REPLACE FUNCTION tip_sync_wavelengths_from_tx() RETURNS trigger AS $fn$
|
||||||
|
BEGIN
|
||||||
|
IF (NEW.wavelengths IS NULL OR NEW.wavelengths = '')
|
||||||
|
AND NEW.wavelength_tx_nm IS NOT NULL THEN
|
||||||
|
NEW.wavelengths := NEW.wavelength_tx_nm || 'nm';
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$fn$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_sync_wavelengths ON transceivers;
|
||||||
|
CREATE TRIGGER trg_sync_wavelengths
|
||||||
|
BEFORE INSERT OR UPDATE ON transceivers
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION tip_sync_wavelengths_from_tx();
|
||||||
|
|
||||||
|
-- C) valid promotion: pending -> auto_approved where full spec now confirmed
|
||||||
|
UPDATE transceiver_equivalences e
|
||||||
|
SET status = 'auto_approved',
|
||||||
|
confidence = GREATEST(e.confidence, 0.85),
|
||||||
|
match_notes = COALESCE(e.match_notes,'') || ' | sql/131: full spec confirmed after wavelength column sync',
|
||||||
|
updated_at = now()
|
||||||
|
FROM transceivers fx, transceivers cx
|
||||||
|
WHERE e.flexoptix_id = fx.id
|
||||||
|
AND e.competitor_id = cx.id
|
||||||
|
AND e.status = 'pending'
|
||||||
|
AND fx.form_factor = cx.form_factor
|
||||||
|
AND fx.speed_gbps = cx.speed_gbps
|
||||||
|
AND fx.fiber_type IS NOT DISTINCT FROM cx.fiber_type
|
||||||
|
AND fx.reach_meters >= 10 AND cx.reach_meters >= 10
|
||||||
|
AND tip_reach_tier(fx.reach_meters) = tip_reach_tier(cx.reach_meters)
|
||||||
|
AND tip_extract_wavelength_nm(fx.wavelengths) IS NOT NULL
|
||||||
|
AND tip_extract_wavelength_nm(cx.wavelengths) IS NOT NULL
|
||||||
|
AND abs(tip_extract_wavelength_nm(fx.wavelengths) - tip_extract_wavelength_nm(cx.wavelengths)) <= 10;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
74
sql/132-wavelength-authority-normalize.sql
Normal file
74
sql/132-wavelength-authority-normalize.sql
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
-- sql/132 — Wavelength authority correction + format normalization
|
||||||
|
--
|
||||||
|
-- Follow-up to sql/131. Discovery: transceivers.wavelengths holds the part-derived
|
||||||
|
-- wavelength as a BARE number ("1470") for ~16.8k rows, which tip_extract_wavelength_nm()
|
||||||
|
-- cannot read (it requires the "nm" suffix). Meanwhile wavelength_tx_nm disagrees with
|
||||||
|
-- that authoritative bare number 33% of the time, and 64% of those disagreements are a
|
||||||
|
-- systematic tx_nm=1310 enricher default. So the bare-number column is authoritative;
|
||||||
|
-- tx_nm is not. This migration prefers the authoritative source.
|
||||||
|
--
|
||||||
|
-- 1) normalize authoritative bare-number wavelengths -> "NNNnm" (format only, no value change)
|
||||||
|
-- 2) clean "N/A" text -> NULL
|
||||||
|
-- 3) correct the few CWDM rows sql/131 backfilled with a wrong tx_nm default, using the
|
||||||
|
-- deterministic CWDM wavelength in the part number
|
||||||
|
-- 4) re-promote pending -> auto_approved where the FULL spec gate now agrees on
|
||||||
|
-- authoritative data (gate already protects against tx_nm noise: both sides must
|
||||||
|
-- agree within +/-10nm, so a wrong 1310 default can never auto-approve a real 1470).
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- 1) normalize authoritative bare-number wavelengths (preserve value, add unit)
|
||||||
|
UPDATE transceivers
|
||||||
|
SET wavelengths = wavelengths || 'nm'
|
||||||
|
WHERE wavelengths ~ '^\d{3,4}$';
|
||||||
|
|
||||||
|
-- 2) non-values -> NULL (N/A, -, empty, etc.)
|
||||||
|
UPDATE transceivers
|
||||||
|
SET wavelengths = NULL
|
||||||
|
WHERE wavelengths IS NOT NULL
|
||||||
|
AND tip_extract_wavelength_nm(wavelengths) IS NULL
|
||||||
|
AND wavelengths !~ '\d';
|
||||||
|
|
||||||
|
-- 3) correct CWDM rows that sql/131 filled from a wrong tx_nm default:
|
||||||
|
-- use the deterministic CWDM wavelength encoded in the part number
|
||||||
|
UPDATE transceivers
|
||||||
|
SET wavelengths = (regexp_match(part_number,'(12[6-9]0|13[0-9]0|14[0-9]0|15[0-9]0|16[01]0)'))[1] || 'nm'
|
||||||
|
WHERE wavelengths = wavelength_tx_nm || 'nm'
|
||||||
|
AND part_number ~ '(12[6-9]|13[0-9]|14[0-9]|15[0-9]|16[01])0'
|
||||||
|
AND (regexp_match(part_number,'(12[6-9]0|13[0-9]0|14[0-9]0|15[0-9]0|16[01]0)'))[1]::int <> wavelength_tx_nm;
|
||||||
|
|
||||||
|
-- 3b) upgrade the sync trigger so future ingests self-normalize:
|
||||||
|
-- prefer an authoritative bare-number wavelength (add "nm"), fall back to tx_nm only
|
||||||
|
-- when no textual wavelength is present at all.
|
||||||
|
CREATE OR REPLACE FUNCTION tip_sync_wavelengths_from_tx() RETURNS trigger AS $fn$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.wavelengths ~ '^\d{3,4}$' THEN
|
||||||
|
NEW.wavelengths := NEW.wavelengths || 'nm';
|
||||||
|
ELSIF (NEW.wavelengths IS NULL OR NEW.wavelengths = '' OR tip_extract_wavelength_nm(NEW.wavelengths) IS NULL)
|
||||||
|
AND NEW.wavelength_tx_nm IS NOT NULL THEN
|
||||||
|
NEW.wavelengths := NEW.wavelength_tx_nm || 'nm';
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$fn$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- 4) valid promotion on corrected/authoritative data (same full spec gate as spec-matcher)
|
||||||
|
UPDATE transceiver_equivalences e
|
||||||
|
SET status = 'auto_approved',
|
||||||
|
confidence = GREATEST(e.confidence, 0.85),
|
||||||
|
match_notes = COALESCE(e.match_notes,'') || ' | sql/132: full spec confirmed on authoritative wavelength',
|
||||||
|
updated_at = now()
|
||||||
|
FROM transceivers fx, transceivers cx
|
||||||
|
WHERE e.flexoptix_id = fx.id
|
||||||
|
AND e.competitor_id = cx.id
|
||||||
|
AND e.status = 'pending'
|
||||||
|
AND fx.form_factor = cx.form_factor
|
||||||
|
AND fx.speed_gbps = cx.speed_gbps
|
||||||
|
AND fx.fiber_type IS NOT DISTINCT FROM cx.fiber_type
|
||||||
|
AND fx.reach_meters >= 10 AND cx.reach_meters >= 10
|
||||||
|
AND tip_reach_tier(fx.reach_meters) = tip_reach_tier(cx.reach_meters)
|
||||||
|
AND tip_extract_wavelength_nm(fx.wavelengths) IS NOT NULL
|
||||||
|
AND tip_extract_wavelength_nm(cx.wavelengths) IS NOT NULL
|
||||||
|
AND abs(tip_extract_wavelength_nm(fx.wavelengths) - tip_extract_wavelength_nm(cx.wavelengths)) <= 10;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
46
sql/133-reach-substitution-policy.sql
Normal file
46
sql/133-reach-substitution-policy.sql
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
-- sql/133 — One-directional reach-tier substitution policy
|
||||||
|
--
|
||||||
|
-- Decision (Rene, 2026-07-07): a higher-reach optic may satisfy a lower-reach
|
||||||
|
-- requirement (e.g. a 40km ER can serve where a 10km LR is asked for), never the
|
||||||
|
-- reverse — the shorter-reach part cannot close the link budget on a longer run.
|
||||||
|
-- ZR/coherent/amplified tiers are explicitly EXCLUDED from this automatic rule:
|
||||||
|
-- those can overload a short-haul receiver without an attenuator, which is a real
|
||||||
|
-- power-budget calculation, not a simple tier comparison. Those stay in manual review.
|
||||||
|
--
|
||||||
|
-- Result is tagged as its own status (compatible_superset), never merged into
|
||||||
|
-- auto_approved/exact-spec matches, so downstream consumers (Fossy/Beo/Pulso, pricing)
|
||||||
|
-- can distinguish "identical spec" from "safe higher-spec substitute" — they usually
|
||||||
|
-- differ in price and are not a like-for-like swap.
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- widen the status check (additive — existing values still valid)
|
||||||
|
ALTER TABLE transceiver_equivalences DROP CONSTRAINT transceiver_equivalences_status_check;
|
||||||
|
ALTER TABLE transceiver_equivalences ADD CONSTRAINT transceiver_equivalences_status_check
|
||||||
|
CHECK (status::text = ANY (ARRAY['pending','approved','rejected','auto_approved','compatible_superset']::text[]));
|
||||||
|
|
||||||
|
UPDATE transceiver_equivalences e
|
||||||
|
SET status = 'compatible_superset',
|
||||||
|
confidence = GREATEST(e.confidence, 0.75),
|
||||||
|
match_notes = COALESCE(e.match_notes,'') || ' | sql/133: ' ||
|
||||||
|
CASE WHEN fx.reach_meters > cx.reach_meters
|
||||||
|
THEN 'flexoptix ('||tip_reach_tier(fx.reach_meters)||') covers competitor requirement ('||tip_reach_tier(cx.reach_meters)||')'
|
||||||
|
ELSE 'competitor ('||tip_reach_tier(cx.reach_meters)||') covers flexoptix requirement ('||tip_reach_tier(fx.reach_meters)||')'
|
||||||
|
END || ' — reach-tier substitute, not identical spec',
|
||||||
|
updated_at = now()
|
||||||
|
FROM transceivers fx, transceivers cx
|
||||||
|
WHERE e.flexoptix_id = fx.id
|
||||||
|
AND e.competitor_id = cx.id
|
||||||
|
AND e.status = 'pending'
|
||||||
|
AND fx.form_factor = cx.form_factor
|
||||||
|
AND fx.speed_gbps = cx.speed_gbps
|
||||||
|
AND fx.fiber_type IS NOT DISTINCT FROM cx.fiber_type
|
||||||
|
AND fx.reach_meters >= 10 AND cx.reach_meters >= 10
|
||||||
|
AND tip_extract_wavelength_nm(fx.wavelengths) IS NOT NULL
|
||||||
|
AND tip_extract_wavelength_nm(cx.wavelengths) IS NOT NULL
|
||||||
|
AND abs(tip_extract_wavelength_nm(fx.wavelengths) - tip_extract_wavelength_nm(cx.wavelengths)) <= 10
|
||||||
|
AND tip_reach_tier(fx.reach_meters) <> tip_reach_tier(cx.reach_meters)
|
||||||
|
AND tip_reach_tier(fx.reach_meters) <> 'ZR'
|
||||||
|
AND tip_reach_tier(cx.reach_meters) <> 'ZR';
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
Loading…
x
Reference in New Issue
Block a user