fix(equivalences): reclassify 1,455 blanket-auto_approved spec matches
One-time companion to commit e7299ac7 (spec-matcher.ts now inserts pending, not auto_approved). Re-scores the pre-existing spec-basis auto_approved rows against the rigorous evaluateEquivalenceResearch() criteria: 972 (67%) genuinely hold up and stay auto_approved, 483 (33%) get demoted -- some permanently (technical mismatch / no recent competitor price), some flagged for re-research in 21 days (missing wavelength/fiber/reach evidence, not a wrong match). Backup in spec_matcher_reclassify_backup_20260717.
This commit is contained in:
parent
e7299ac779
commit
668911756c
99
sql/140-spec-matcher-reclassify.sql
Normal file
99
sql/140-spec-matcher-reclassify.sql
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
-- sql/140-spec-matcher-reclassify.sql
|
||||||
|
-- One-time reclassification of the 1,455 equivalences that spec-matcher.ts had
|
||||||
|
-- inserted straight into status='auto_approved' at a flat confidence=0.85,
|
||||||
|
-- bypassing the discrimination (standard_name, fiber_type, recent-competitor-
|
||||||
|
-- price) that catalog-reconcile.ts's calcConfidence() applies before ever
|
||||||
|
-- auto-approving a pending row. Same set-based scoring port as sql/139, applied
|
||||||
|
-- here to the pre-existing 'spec'-basis auto_approved rows instead of pending
|
||||||
|
-- ones. Paired with a code fix (commit e7299ac7) so spec-matcher.ts inserts
|
||||||
|
-- 'pending' going forward and routes through the same reviewed pipeline.
|
||||||
|
--
|
||||||
|
-- OPN-basis rows (manufacturer-confirmed, fx_compatibilities ground truth) are
|
||||||
|
-- NOT touched -- this only reclassifies 'spec'-basis rows.
|
||||||
|
|
||||||
|
create table if not exists spec_matcher_reclassify_backup_20260717 as
|
||||||
|
select *, now() as backed_up_at
|
||||||
|
from transceiver_equivalences
|
||||||
|
where 'spec' = any(match_basis) and status = 'auto_approved';
|
||||||
|
|
||||||
|
with normalized as (
|
||||||
|
select eq.id,
|
||||||
|
nullif(upper(trim(fx.form_factor)),'') as fx_form, nullif(upper(trim(cp.form_factor)),'') as cp_form,
|
||||||
|
fx.speed_gbps as fx_speed, cp.speed_gbps as cp_speed,
|
||||||
|
nullif(upper(trim(fx.standard_name)),'') as fx_std, nullif(upper(trim(cp.standard_name)),'') as cp_std,
|
||||||
|
nullif(upper(trim(fx.fiber_type)),'') as fx_fiber, nullif(upper(trim(cp.fiber_type)),'') as cp_fiber,
|
||||||
|
fx.reach_meters as fx_reach, cp.reach_meters as cp_reach,
|
||||||
|
(regexp_match(fx.wavelengths, '(\d{3,4})'))[1]::numeric as fx_nm,
|
||||||
|
(regexp_match(cp.wavelengths, '(\d{3,4})'))[1]::numeric as cp_nm,
|
||||||
|
exists(select 1 from price_observations po where po.transceiver_id=eq.competitor_id and po.time > now() - interval '45 days') as has_recent_price
|
||||||
|
from transceiver_equivalences eq
|
||||||
|
join transceivers fx on fx.id = eq.flexoptix_id
|
||||||
|
join transceivers cp on cp.id = eq.competitor_id
|
||||||
|
where 'spec' = any(eq.match_basis) and eq.status = 'auto_approved'
|
||||||
|
),
|
||||||
|
scored as (
|
||||||
|
select *,
|
||||||
|
(case when fx_form is not null and cp_form is not null and fx_form=cp_form then 25 else 0 end
|
||||||
|
+ case when fx_speed is not null and cp_speed is not null and fx_speed=cp_speed then 20 else 0 end
|
||||||
|
+ case when fx_std is not null and cp_std is not null and fx_std=cp_std then 30 else 0 end
|
||||||
|
+ case when fx_nm is not null and cp_nm is not null then
|
||||||
|
case when abs(fx_nm-cp_nm)<=15 then 20 else -20 end
|
||||||
|
else 0 end
|
||||||
|
+ case when fx_fiber is not null and cp_fiber is not null then
|
||||||
|
case when fx_fiber=cp_fiber then 10 else -15 end
|
||||||
|
else 0 end
|
||||||
|
+ case when fx_reach is not null and cp_reach is not null and fx_reach>0 and cp_reach>0 then
|
||||||
|
case when least(fx_reach,cp_reach)/greatest(fx_reach,cp_reach) >= 0.85 then 10 else -15 end
|
||||||
|
else 0 end) as raw_score,
|
||||||
|
(fx_nm is null or cp_nm is null) as wl_missing,
|
||||||
|
(fx_fiber is null or cp_fiber is null) as fiber_missing,
|
||||||
|
(fx_reach is null or cp_reach is null or fx_reach<=0 or cp_reach<=0) as reach_missing,
|
||||||
|
(fx_nm is not null and cp_nm is not null and abs(fx_nm-cp_nm)>15) as wl_mismatch,
|
||||||
|
(fx_fiber is not null and cp_fiber is not null and fx_fiber<>cp_fiber) as fiber_mismatch,
|
||||||
|
(fx_reach is not null and cp_reach is not null and fx_reach>0 and cp_reach>0 and least(fx_reach,cp_reach)/greatest(fx_reach,cp_reach) < 0.85) as reach_mismatch,
|
||||||
|
(fx_form is null or cp_form is null or fx_form<>cp_form) as form_bad,
|
||||||
|
(fx_speed is null or cp_speed is null or fx_speed<>cp_speed) as speed_bad
|
||||||
|
from normalized
|
||||||
|
),
|
||||||
|
decided as (
|
||||||
|
select *,
|
||||||
|
greatest(0, least(1, raw_score::numeric/115)) as confidence,
|
||||||
|
(not has_recent_price) as no_price,
|
||||||
|
(wl_mismatch or fiber_mismatch or reach_mismatch or form_bad or speed_bad) as critical_mismatch,
|
||||||
|
(wl_missing or fiber_missing or reach_missing) as missing_evidence
|
||||||
|
from scored
|
||||||
|
)
|
||||||
|
update transceiver_equivalences eq
|
||||||
|
set status = case
|
||||||
|
when d.no_price then 'rejected'
|
||||||
|
when d.critical_mismatch then 'rejected'
|
||||||
|
when d.missing_evidence then 'rejected'
|
||||||
|
when d.confidence >= 0.73 then 'auto_approved'
|
||||||
|
else 'rejected'
|
||||||
|
end,
|
||||||
|
confidence = case when d.no_price then 0 else d.confidence end,
|
||||||
|
reviewed_by = 'automated-research-bulk-140',
|
||||||
|
reviewed_at = now(),
|
||||||
|
reject_reason = case
|
||||||
|
when d.no_price then 'automated research: competitor has no recent price observation'
|
||||||
|
when d.critical_mismatch then 'automated research: technical mismatch'
|
||||||
|
when d.missing_evidence then 'automated research: insufficient technical evidence'
|
||||||
|
when d.confidence >= 0.73 then null
|
||||||
|
else 'automated research: confidence below approval threshold'
|
||||||
|
end,
|
||||||
|
re_research_due_at = case
|
||||||
|
when (not d.no_price) and (not d.critical_mismatch) and d.missing_evidence and d.confidence < 0.73
|
||||||
|
then now() + interval '21 days'
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
re_researched_at = now(),
|
||||||
|
match_notes = case when eq.match_notes is null or eq.match_notes = ''
|
||||||
|
then 'bulk-reclassify(140) ' || now()::date
|
||||||
|
else eq.match_notes || ' | bulk-reclassify(140) ' || now()::date end,
|
||||||
|
updated_at = now()
|
||||||
|
from decided d
|
||||||
|
where eq.id = d.id;
|
||||||
|
|
||||||
|
insert into _migrations (filename, applied_at)
|
||||||
|
select '140-spec-matcher-reclassify.sql', now()
|
||||||
|
where not exists (select 1 from _migrations where filename = '140-spec-matcher-reclassify.sql');
|
||||||
Loading…
x
Reference in New Issue
Block a user