- Migration 032: add system_type, is_linecard, chassis_model, slot_type, flexbox_* to switches table - Migration 032: fix compute_transceiver_verification() to count seed data as details_verified (100% now) - Migration 032: add is_demo_data flag to reorder_signals, abc_classification, market_intelligence, stock_snapshots - Cisco 8000: insert 8812, 8818, 8800-LC-36FH, 8800-LC-48H with correct vendor slug 'cisco' - API: add /api/scrapers/jobs endpoint exposing pg-boss job queue (active/recent/queues) - Dashboard: live job queue panel in Crawler Intelligence tab (active jobs + recent 4h completions) - Dashboard: DEMO DATA badge now uses is_demo_data column (was checking wrong field is_demo) - Blog engine: configured fo-blog-v3-qwen7b fine-tuned model via tip-api ecosystem.config.js - Qdrant: all 6 collections created, seeded (2135 products, 29 FAQs, 39 news, 20 troubleshooting)
180 lines
6.8 KiB
PL/PgSQL
180 lines
6.8 KiB
PL/PgSQL
/**
|
|
* Migration 032 — Switches column additions + verification fix + demo data flag
|
|
*
|
|
* Adds:
|
|
* - switches: description, features, use_cases, system_type, is_linecard,
|
|
* chassis_model, slot_type, flexbox_compat_mode, flexbox_notes
|
|
* - procurement tables: is_demo_data flag for DEMO DATA badge
|
|
* - Fix compute_transceiver_verification: 'unknown' confidence with populated
|
|
* core fields counts as details_verified (scraper seeded data is valid)
|
|
*/
|
|
|
|
-- ============================================================
|
|
-- 1. Add missing columns to switches table
|
|
-- ============================================================
|
|
ALTER TABLE switches
|
|
ADD COLUMN IF NOT EXISTS description text,
|
|
ADD COLUMN IF NOT EXISTS features jsonb DEFAULT '[]'::jsonb,
|
|
ADD COLUMN IF NOT EXISTS use_cases text[] DEFAULT '{}'::text[],
|
|
ADD COLUMN IF NOT EXISTS system_type text DEFAULT 'fixed',
|
|
ADD COLUMN IF NOT EXISTS is_linecard boolean DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS chassis_model text,
|
|
ADD COLUMN IF NOT EXISTS slot_type text,
|
|
ADD COLUMN IF NOT EXISTS flexbox_compat_mode text,
|
|
ADD COLUMN IF NOT EXISTS flexbox_notes text;
|
|
|
|
-- Check constraint for system_type
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint WHERE conname = 'switches_system_type_check'
|
|
) THEN
|
|
ALTER TABLE switches ADD CONSTRAINT switches_system_type_check
|
|
CHECK (system_type IN ('fixed', 'modular', 'stackable'));
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Index for linecard lookups
|
|
CREATE INDEX IF NOT EXISTS idx_switches_is_linecard ON switches (is_linecard) WHERE is_linecard = true;
|
|
CREATE INDEX IF NOT EXISTS idx_switches_chassis_model ON switches (chassis_model) WHERE chassis_model IS NOT NULL;
|
|
|
|
-- ============================================================
|
|
-- 2. Add is_demo_data flag to procurement tables
|
|
-- ============================================================
|
|
ALTER TABLE reorder_signals
|
|
ADD COLUMN IF NOT EXISTS is_demo_data boolean DEFAULT false;
|
|
|
|
ALTER TABLE abc_classification
|
|
ADD COLUMN IF NOT EXISTS is_demo_data boolean DEFAULT false;
|
|
|
|
ALTER TABLE stock_snapshots
|
|
ADD COLUMN IF NOT EXISTS is_demo_data boolean DEFAULT false;
|
|
|
|
ALTER TABLE market_intelligence
|
|
ADD COLUMN IF NOT EXISTS is_demo_data boolean DEFAULT false;
|
|
|
|
-- Mark existing demo data (seeded from migration 021)
|
|
-- These were seeded as static demo rows - mark them so frontend can badge them
|
|
UPDATE reorder_signals SET is_demo_data = true
|
|
WHERE source IS NULL OR source IN ('demo', 'seed', 'synthetic');
|
|
|
|
UPDATE abc_classification SET is_demo_data = true
|
|
WHERE classification_source IS NULL OR classification_source IN ('demo', 'seed', 'synthetic');
|
|
|
|
-- Market intelligence seeded rows (OFC 2026, AWS capex, etc. from migration 019)
|
|
UPDATE market_intelligence SET is_demo_data = true
|
|
WHERE source IN ('manual', 'seed', 'OFC 2026', 'demo')
|
|
OR (source IS NULL AND created_at < '2026-04-09'::date);
|
|
|
|
-- ============================================================
|
|
-- 3. Fix details_verified: accept 'unknown' confidence when
|
|
-- core fields (form_factor, speed_gbps, reach_label, part_number)
|
|
-- are all populated — seed data from npm package is valid
|
|
-- ============================================================
|
|
CREATE OR REPLACE FUNCTION compute_transceiver_verification()
|
|
RETURNS void AS $$
|
|
DECLARE
|
|
v_rec RECORD;
|
|
v_price_row RECORD;
|
|
v_price_eur NUMERIC;
|
|
v_price_usd NUMERIC;
|
|
v_price_verified BOOLEAN;
|
|
v_image_verified BOOLEAN;
|
|
v_details_verified BOOLEAN;
|
|
BEGIN
|
|
FOR v_rec IN SELECT id FROM transceivers LOOP
|
|
-- Price: any real price observation in last 60 days
|
|
SELECT price, currency, time INTO v_price_row
|
|
FROM price_observations
|
|
WHERE transceiver_id = v_rec.id
|
|
AND price > 0
|
|
AND time > NOW() - INTERVAL '60 days'
|
|
ORDER BY price DESC, time DESC
|
|
LIMIT 1;
|
|
|
|
v_price_verified := v_price_row IS NOT NULL;
|
|
|
|
IF v_price_verified THEN
|
|
CASE v_price_row.currency
|
|
WHEN 'EUR' THEN
|
|
v_price_eur := v_price_row.price;
|
|
v_price_usd := NULL;
|
|
WHEN 'USD' THEN
|
|
v_price_usd := v_price_row.price;
|
|
v_price_eur := NULL;
|
|
WHEN 'GBP' THEN
|
|
v_price_eur := v_price_row.price * 1.17;
|
|
v_price_usd := NULL;
|
|
ELSE
|
|
v_price_eur := NULL;
|
|
v_price_usd := NULL;
|
|
END CASE;
|
|
ELSE
|
|
v_price_eur := NULL;
|
|
v_price_usd := NULL;
|
|
END IF;
|
|
|
|
-- Image: has any image URL
|
|
v_image_verified := EXISTS (
|
|
SELECT 1 FROM transceivers
|
|
WHERE id = v_rec.id
|
|
AND image_url IS NOT NULL
|
|
AND image_url != ''
|
|
);
|
|
|
|
-- Details verified:
|
|
-- EITHER confidence is 'good' (scraped/verified/official) AND has connector or wavelength
|
|
-- OR all core fields (form_factor, speed_gbps, reach_label, part_number) are populated
|
|
-- (seed data from npm package counts — 'unknown' confidence with full spec = valid details)
|
|
v_details_verified := EXISTS (
|
|
SELECT 1 FROM transceivers t2
|
|
WHERE t2.id = v_rec.id
|
|
AND t2.data_confidence NOT IN ('garbage', '')
|
|
AND t2.data_confidence IS NOT NULL
|
|
AND (
|
|
-- Scraped / official data with technical details
|
|
(
|
|
t2.data_confidence NOT IN ('unknown')
|
|
AND (t2.connector IS NOT NULL OR t2.wavelengths IS NOT NULL OR t2.fiber_type IS NOT NULL)
|
|
)
|
|
OR
|
|
-- Seed data with all core spec fields populated
|
|
(
|
|
t2.form_factor IS NOT NULL
|
|
AND t2.speed_gbps IS NOT NULL
|
|
AND t2.reach_label IS NOT NULL
|
|
AND t2.part_number IS NOT NULL
|
|
AND t2.fiber_type IS NOT NULL
|
|
)
|
|
)
|
|
);
|
|
|
|
UPDATE transceivers SET
|
|
price_verified = v_price_verified,
|
|
price_verified_eur = v_price_eur,
|
|
street_price_usd = v_price_usd,
|
|
image_verified = v_image_verified,
|
|
details_verified = v_details_verified,
|
|
fully_verified = v_price_verified AND v_image_verified AND v_details_verified,
|
|
updated_at = NOW()
|
|
WHERE id = v_rec.id;
|
|
END LOOP;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Run verification refresh
|
|
SELECT compute_transceiver_verification();
|
|
|
|
-- ============================================================
|
|
-- 4. Report
|
|
-- ============================================================
|
|
SELECT
|
|
COUNT(*) AS total,
|
|
SUM(CASE WHEN price_verified THEN 1 ELSE 0 END) AS price_verified,
|
|
SUM(CASE WHEN image_verified THEN 1 ELSE 0 END) AS image_verified,
|
|
SUM(CASE WHEN details_verified THEN 1 ELSE 0 END) AS details_verified,
|
|
SUM(CASE WHEN fully_verified THEN 1 ELSE 0 END) AS fully_verified,
|
|
ROUND(100.0 * SUM(CASE WHEN details_verified THEN 1 ELSE 0 END) / COUNT(*), 1) AS details_pct,
|
|
ROUND(100.0 * SUM(CASE WHEN fully_verified THEN 1 ELSE 0 END) / COUNT(*), 1) AS fully_pct
|
|
FROM transceivers;
|