-- sql/125-switch-quality.sql -- Switch / compatibility data-quality remediation + monitoring layer. -- -- Audit 2026-07-06 (read-only) found systematic errors in the switch database: -- * 5.686+ physically-impossible compatibility rows (transceiver faster than the -- switch's fastest port). Root cause: the form-factor fallback in -- packages/scraper/src/scrapers/flexoptix-compat.ts matched by form factor ONLY, -- ignoring speed -> a 400G-OSFP port matched every OSFP transceiver incl. 1.6T. -- * 420/687 switches with NULL max_speed_gbps (ports_config present -> derivable). -- * 528 transceivers with speed_gbps <= 0 (blocks compat validation; they silently -- pass as "compatible" with everything). -- * coding-pattern coverage as low as ~13% at 100G (vendor_compat = [] for most). -- -- DESIGN (why this is safe at the "no errors" bar): -- The naive ceiling (ports_config-derived max) is UNRELIABLE because some switches' -- ports_config is understated (e.g. Cisco C9500-32QC lists 40G but does 100G; NCS 540 -- "Z" ports list 10G but are 25G-capable; A9K-20HG-FLEX lists 100G but does 400G). -- Flagging by that ceiling would create FALSE "incompatible" flags on curated rows. -- So the effective ceiling is evidence-based: -- effective_max = GREATEST(stored max_speed_gbps, -- ports_config-derived max, -- fastest curated-compatible transceiver for the switch) -- and ONLY algorithmic 'spec_match' fallback rows are ever auto-flagged; curated -- ('vendor_matrix'/'vendor_compat') rows are never touched (they instead RAISE the -- ceiling, protecting understated switches). Result: 0 curated false positives. -- -- Idempotent + safe to re-run. Applied with psql --single-transaction. -- ═══════════════════════════════════════════════════════════════════════════ -- 0) Ports-config -> max port speed (pure helper) + evidence-based switch ceiling -- ═══════════════════════════════════════════════════════════════════════════ create or replace function fn_ports_max_gbps(cfg jsonb) returns numeric language sql immutable as $fn$ select case when jsonb_typeof(cfg) = 'object' then (select max(substring(k from '([0-9]+)G')::numeric) from jsonb_object_keys(cfg) k) else null end $fn$; comment on function fn_ports_max_gbps(jsonb) is 'Max port speed (Gbps) parsed from a switch ports_config JSONB object, e.g. {"400G_QSFP-DD":4,"100G_QSFP28":2} -> 400. NULL if config is empty/non-object.'; create or replace view v_switch_effective_max as select s.id as switch_id, s.max_speed_gbps as stored_max_gbps, fn_ports_max_gbps(s.ports_config) as ports_max_gbps, (select max(t.speed_gbps) from compatibility c join transceivers t on t.id = c.transceiver_id where c.switch_id = s.id and c.status = 'compatible' and c.verification_method in ('vendor_matrix','vendor_compat') and t.speed_gbps > 0) as curated_max_gbps, greatest( coalesce(s.max_speed_gbps, 0), coalesce(fn_ports_max_gbps(s.ports_config), 0), coalesce((select max(t.speed_gbps) from compatibility c join transceivers t on t.id = c.transceiver_id where c.switch_id = s.id and c.status = 'compatible' and c.verification_method in ('vendor_matrix','vendor_compat') and t.speed_gbps > 0), 0) ) as effective_max_gbps from switches s; comment on view v_switch_effective_max is 'Evidence-based max port speed per switch (greatest of stored max_speed_gbps, ports_config-derived, and fastest curated-compatible transceiver). Used to validate compatibility without false-flagging switches whose ports_config is understated.'; -- ═══════════════════════════════════════════════════════════════════════════ -- REMEDIATION A) Backfill max_speed_gbps from ports_config (additive, nulls only). -- Highest leverage: unblocks validation for switches that currently pass no check. -- Only fills NULLs; never overwrites a curated stored value (61/267 stored values -- disagree with ports-derived in both directions -> curated values are preserved). -- Switches with an empty {} ports_config stay NULL (need a datasheet -> see alerts). -- ═══════════════════════════════════════════════════════════════════════════ update switches s set max_speed_gbps = fn_ports_max_gbps(s.ports_config) where s.max_speed_gbps is null and fn_ports_max_gbps(s.ports_config) is not null and fn_ports_max_gbps(s.ports_config) > 0; -- ═══════════════════════════════════════════════════════════════════════════ -- REMEDIATION C) Re-derive speed_gbps for transceivers with speed <= 0, HIGH -- confidence only. Speed comes from the manufacturer's standardized speed token -- in the part number (e.g. SFP-10G-SR -> 10, QDD-2X400G -> 800, QSFP-4SFP25G -> 100), -- and is applied ONLY when it is <= the form factor's physical maximum. Parts whose -- token exceeds the form-factor cap (usually a wrong form_factor, e.g. QSFP-400G-AOC -- mislabelled 'SFP') or that have no speed token are LEFT flagged for datasheet work. -- No blind update: FastEthernet "100FX"/"100BASE" (no 'G') never matches. -- ═══════════════════════════════════════════════════════════════════════════ with cand as ( select id, case when part_number ~* '([0-9]+)X([0-9]+)G' then (regexp_match(part_number,'([0-9]+)X([0-9]+)G','i'))[1]::numeric * (regexp_match(part_number,'([0-9]+)X([0-9]+)G','i'))[2]::numeric when part_number ~* '([0-9]+)SFP([0-9]+)G' then (regexp_match(part_number,'([0-9]+)SFP([0-9]+)G','i'))[1]::numeric * (regexp_match(part_number,'([0-9]+)SFP([0-9]+)G','i'))[2]::numeric when part_number ~* '([0-9]+)G' then (regexp_match(part_number,'([0-9]+)G','i'))[1]::numeric end as derived, case upper(coalesce(form_factor,'')) when 'SFP' then 1 when 'SFP+' then 16 when 'SFP28' then 32 when 'SFP56' then 56 when 'XFP' then 10 when 'QSFP+' then 40 when 'QSFP28' then 100 when 'QSFP56' then 200 when 'QSFP-DD' then 800 when 'QSFP-DD800' then 800 when 'OSFP' then 1600 when 'OSFP224' then 1600 when 'CFP' then 100 when 'CFP2' then 400 when 'CFP4' then 100 when 'CPAK' then 100 else 99999 end as ff_cap from transceivers where speed_gbps is null or speed_gbps <= 0) update transceivers t set speed_gbps = c.derived from cand c where t.id = c.id and c.derived is not null and c.derived > 0 and c.derived <= c.ff_cap; -- ═══════════════════════════════════════════════════════════════════════════ -- REMEDIATION B) Flag physically-impossible compatibility rows. -- Only 'spec_match' (algorithmic form-factor fallback) rows are flagged; curated -- rows are never touched. status='incompatible' (constraint-allowed value) removes -- them from every consumer that filters status='compatible' (e.g. queries.ts:302). -- Runs AFTER A + C so it uses backfilled ceilings and corrected transceiver speeds. -- ═══════════════════════════════════════════════════════════════════════════ with em as materialized ( select switch_id, effective_max_gbps from v_switch_effective_max) update compatibility c set status = 'incompatible', notes = case when c.notes is null or c.notes = '' then 'auto(125): transceiver speed exceeds switch max port speed' else c.notes || ' | auto(125): transceiver speed exceeds switch max port speed' end from em e, transceivers t where e.switch_id = c.switch_id and t.id = c.transceiver_id and c.status = 'compatible' and c.verification_method = 'spec_match' and e.effective_max_gbps > 0 and t.speed_gbps is not null and t.speed_gbps > e.effective_max_gbps; -- ═══════════════════════════════════════════════════════════════════════════ -- MONITORING VIEWS (read-only). v_switch_quality_alerts is the task list. -- ═══════════════════════════════════════════════════════════════════════════ -- 1) Impossible rows still CLAIMING compatibility (drains to 0 after remediation B; -- lights up again if a future scrape inserts a new impossible spec_match row). create or replace view v_compat_speed_errors as select c.id as compat_id, s.model as switch, e.effective_max_gbps as switch_max_gbps, t.part_number as transceiver, t.speed_gbps as transceiver_gbps, round(t.speed_gbps / nullif(e.effective_max_gbps,0), 1) as over_ratio from compatibility c join v_switch_effective_max e on e.switch_id = c.switch_id join switches s on s.id = c.switch_id join transceivers t on t.id = c.transceiver_id where c.status = 'compatible' and c.verification_method = 'spec_match' and e.effective_max_gbps > 0 and t.speed_gbps is not null and t.speed_gbps > e.effective_max_gbps; -- 2) Understated switch port data: a CURATED source pairs a transceiver faster than the -- switch's stored/ports max. These are NOT errors to auto-fix -- they flag switches -- whose ports_config needs datasheet enrichment (or a wrong curated claim to review). create or replace view v_switch_understated_ports as select c.id as compat_id, v.name as manufacturer, s.model as switch, s.max_speed_gbps as stored_max_gbps, fn_ports_max_gbps(s.ports_config) as ports_max_gbps, t.part_number as transceiver, t.speed_gbps as transceiver_gbps, c.verification_method from compatibility c join switches s on s.id = c.switch_id join vendors v on v.id = s.vendor_id join transceivers t on t.id = c.transceiver_id where c.status = 'compatible' and c.verification_method in ('vendor_matrix','vendor_compat') and t.speed_gbps is not null and (coalesce(s.max_speed_gbps,0) > 0 or coalesce(fn_ports_max_gbps(s.ports_config),0) > 0) and t.speed_gbps > greatest(coalesce(s.max_speed_gbps,0), coalesce(fn_ports_max_gbps(s.ports_config),0)); -- 3) Coding-pattern coverage per speed tier (module only) -- data-SOURCE gap, pipeline task. create or replace view v_coding_coverage as select t.speed_gbps::numeric as tier, count(*)::int as modules, count(*) filter (where jsonb_array_length(coalesce(t.vendor_compat,'[]'::jsonb)) > 0)::int as with_coding, round(100.0 * count(*) filter (where jsonb_array_length(coalesce(t.vendor_compat,'[]'::jsonb)) > 0) / count(*), 1) as coverage_pct from transceivers t where t.product_type = 'module' and t.speed_gbps >= 100 group by 1 order by 1; -- 4) Transceivers with an impossible speed (<=0). 100 Mbps (0.1) and FC/SONET rates -- (0.155/0.622/2.488/8/8.5/14/28/32) are legitimate and intentionally NOT flagged. create or replace view v_transceiver_speed_errors as select id, part_number, speed_gbps, form_factor from transceivers where speed_gbps is null or speed_gbps <= 0; -- 5) Switches that cannot validate compatibility (missing max_speed / total_ports). create or replace view v_switch_completeness as select s.id, v.name as manufacturer, s.model, s.max_speed_gbps, s.total_ports, (jsonb_typeof(s.ports_config) = 'object' and s.ports_config <> '{}'::jsonb) as has_ports_config from switches s join vendors v on v.id = s.vendor_id where s.max_speed_gbps is null or s.total_ports is null; -- 6) One-line alerts = the switch/coding data-quality task list. create or replace view v_switch_quality_alerts as select 'compatibility: ' || count(*) || ' physically-impossible spec_match rows (transceiver faster than switch)' as alert, 'critical' as severity from v_compat_speed_errors union all select 'coding matrix: ' || tier || 'G only ' || coverage_pct || '% populated (' || (modules - with_coding) || ' modules without coding patterns)', case when coverage_pct < 25 then 'high' else 'warning' end from v_coding_coverage where coverage_pct < 70 union all select 'transceivers: ' || count(*) || ' with speed_gbps <= 0 (need form_factor/datasheet)', 'high' from v_transceiver_speed_errors union all select 'switches: ' || count(*) || ' missing max_speed_gbps (empty ports_config -> needs datasheet)', 'warning' from v_switch_completeness where max_speed_gbps is null union all select 'switches: ' || count(*) || ' with understated port data (curated compat faster than ports_config)', 'warning' from v_switch_understated_ports; -- Usage: select * from v_switch_quality_alerts order by severity; -- select * from v_compat_speed_errors order by over_ratio desc limit 50; -- select * from v_switch_understated_ports order by transceiver_gbps desc; insert into _migrations (filename, applied_at) select '125-switch-quality.sql', now() where not exists (select 1 from _migrations where filename = '125-switch-quality.sql');