Scraper changes:
- fs-com.ts v2: Playwright stealth patches + www.fs.com/de/ URL fix (de.fs.com DNS NXDOMAIN).
Extracts DE-Lager, Global-Lager, Nachlieferung, units_sold, compatible_brands, price_net.
Mac-side runner (run-fs-scraper-mac.sh) via SSH tunnel for residential IP access.
Fast-fail connectivity check on datacenter IPs that are blocked by Cloudflare.
- smartoptics.ts v2: WooCommerce REST API fallback + 8 catalog categories + relative URL fix.
Was finding only 8 products, now discovers 18+ with multi-category crawl.
DB layer:
- db.ts: add upsertStockObservation() — writes 10 new stock_observations columns
(warehouse_de_qty, warehouse_global_qty, backorder_qty, units_sold, compatible_brands,
price_net, product_url, delivery dates) with dedup check.
API:
- routes/stock.ts: GET /api/stock, /api/stock/summary, /api/stock/:id
Warehouse breakdowns per transceiver/vendor with top-sellers and vendor summary.
- routes/review.ts: equivalence review queue (approve/reject/bulk-approve).
- index.ts: register /api/stock and /api/review routes.
Dashboard:
- index.html: 🏭 Stock tab with stat cards (DE-Lager, Global-Lager, Nachlieferung totals),
top-sellers table, vendor breakdown, recently-restocked events, part-number lookup.
SQL migrations:
- 034: blog-review-tag, 035: price-observations is_anomalous, 036: transceiver-equivalences.
45 lines
2.1 KiB
PL/PgSQL
45 lines
2.1 KiB
PL/PgSQL
-- Migration 036: Transceiver equivalences for competitor_verified matching
|
|
-- Stores semantic equivalences between Flexoptix SKUs and competitor products
|
|
-- matched by technical specs (form_factor + speed + reach + standard + fiber_type)
|
|
|
|
CREATE TABLE IF NOT EXISTS transceiver_equivalences (
|
|
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
flexoptix_id UUID NOT NULL REFERENCES transceivers(id) ON DELETE CASCADE,
|
|
competitor_id UUID NOT NULL REFERENCES transceivers(id) ON DELETE CASCADE,
|
|
confidence DECIMAL(4,3) NOT NULL CHECK (confidence BETWEEN 0 AND 1),
|
|
match_basis TEXT[] NOT NULL DEFAULT '{}', -- ['standard_name','form_factor','speed_gbps','fiber_type','reach']
|
|
match_notes TEXT,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending'
|
|
CHECK (status IN ('pending','approved','rejected','auto_approved')),
|
|
reviewed_by VARCHAR(200),
|
|
reviewed_at TIMESTAMPTZ,
|
|
reject_reason TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE (flexoptix_id, competitor_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_eq_flexoptix ON transceiver_equivalences (flexoptix_id);
|
|
CREATE INDEX IF NOT EXISTS idx_eq_competitor ON transceiver_equivalences (competitor_id);
|
|
CREATE INDEX IF NOT EXISTS idx_eq_status ON transceiver_equivalences (status, confidence DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_eq_pending ON transceiver_equivalences (flexoptix_id) WHERE status = 'pending';
|
|
|
|
-- Auto-update updated_at
|
|
CREATE OR REPLACE FUNCTION update_equivalences_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_eq_updated_at ON transceiver_equivalences;
|
|
CREATE TRIGGER trg_eq_updated_at
|
|
BEFORE UPDATE ON transceiver_equivalences
|
|
FOR EACH ROW EXECUTE FUNCTION update_equivalences_updated_at();
|
|
|
|
COMMENT ON TABLE transceiver_equivalences IS
|
|
'Semantic equivalences between Flexoptix SKUs and competitor products, '
|
|
'matched by technical specification overlap. Used to set competitor_verified=true '
|
|
'on Flexoptix transceivers that have no exact SKU match at competitors.';
|