- Migration 019: stock_snapshots, abc_classification, reorder_signals, product_lifecycle_events, market_intelligence, crawler_llm_log tables - Seeded 7 market intel events (OFC 2026, AWS/Azure CapEx, Coherent lead times, EU TED tenders, ECOC 2026, IEEE 802.3df) - Seeded 4 lifecycle events (Cisco SFP-10G-LR EOL, Juniper EOL, 400ZR ratified, 800G MSA draft) - Crawler LLM: core.ts (Ollama-based extractor), stock-schema.ts (typed schemas + vendor profiles for Flexoptix/FS.com/10Gtek/ATGBICS/ProLabs/Farnell/Mouser), validator.ts (rule-based sanity checks + cross-validation) - market-intelligence.ts scraper: OFC/ECOC, LightReading, IEEE 802.3, EU TED, Farnell/Mouser lead times, FierceTelecom — weekly via pg-boss - computeAbcClassification(): dynamic A/B/C classification from price obs + compat count + vendor breadth - computeReorderSignals(): buy_now/wait/hold/monitor with reasons + signal strength - API: GET /api/procurement/overview|signals|signals/:id|abc|market-intel| stock-trends/:id|lifecycle - Dashboard: Procurement Intel tab with Reorder Signals, ABC table, Market Intel cards, Lifecycle Events
136 lines
5.5 KiB
TypeScript
136 lines
5.5 KiB
TypeScript
/**
|
||
* Crawler LLM — Schema definitions for structured product extraction.
|
||
*
|
||
* Every schema includes a `confidence` and `source_evidence` field so the LLM
|
||
* is forced to cite its work. This enables validation and debugging.
|
||
*/
|
||
|
||
export interface StockExtractionResult {
|
||
is_product_page: boolean; // false = category/listing page → discard
|
||
confidence: number; // 0.0 – 1.0 — LLM self-assessment
|
||
source_evidence: string; // which text passage the LLM used
|
||
|
||
// Pricing
|
||
price: number | null;
|
||
currency: "USD" | "EUR" | "GBP" | "CNY" | null;
|
||
price_breaks: PriceBreak[]; // volume discount tiers
|
||
|
||
// Stock
|
||
stock_level: "in_stock" | "out_of_stock" | "limited" | "unknown";
|
||
stock_quantity: number | null; // exact qty if shown
|
||
incoming_quantity: number | null; // "18 im Zulauf"
|
||
incoming_eta: string | null; // ISO date string "2026-04-15"
|
||
lead_time_days: number | null;
|
||
moq: number | null; // minimum order quantity
|
||
|
||
// Product identity (for cross-validation)
|
||
part_number: string | null;
|
||
standard_name: string | null; // manufacturer's exact product name
|
||
form_factor: string | null;
|
||
speed_gbps: number | null;
|
||
}
|
||
|
||
export interface PriceBreak {
|
||
qty: number;
|
||
price: number;
|
||
}
|
||
|
||
export interface MarketIntelExtractionResult {
|
||
is_relevant: boolean; // false = skip
|
||
confidence: number;
|
||
source_evidence: string;
|
||
|
||
intel_type: "capex_cycle" | "trade_show" | "standard_ratified" | "standard_draft" | "distributor_lead_time" | "supply_chain" | "tender";
|
||
title: string;
|
||
summary: string;
|
||
technologies: string[]; // ['400G', 'QSFP-DD', ...]
|
||
buy_signal_implication: "buy_now" | "wait" | "hold" | "monitor" | "none";
|
||
impact_horizon_months: number;
|
||
published_at: string | null; // ISO date
|
||
}
|
||
|
||
/** Vendor-specific hints to improve LLM extraction accuracy */
|
||
export interface VendorProfile {
|
||
slug: string;
|
||
name: string;
|
||
currency: "USD" | "EUR" | "GBP" | "CNY";
|
||
product_page_signals: string[]; // text patterns that indicate a product page
|
||
category_page_signals: string[]; // text patterns that indicate a category page
|
||
price_hint: string | null; // natural language hint for the LLM
|
||
stock_hint: string | null;
|
||
known_moq: number | null;
|
||
}
|
||
|
||
export const VENDOR_PROFILES: Record<string, VendorProfile> = {
|
||
"flexoptix": {
|
||
slug: "flexoptix",
|
||
name: "Flexoptix",
|
||
currency: "EUR",
|
||
product_page_signals: ["In den Warenkorb", "Add to Cart", "part number", "SKU:", "P/N:"],
|
||
category_page_signals: ["Alle Produkte", "Filter", "Ergebnisse", "products found"],
|
||
price_hint: "Price is shown in EUR, usually near 'In den Warenkorb' button. May show 'auf Anfrage' if not listed.",
|
||
stock_hint: "Look for 'auf Lager', 'Lieferzeit', 'sofort lieferbar', or stock badge near price.",
|
||
known_moq: 1,
|
||
},
|
||
"fs-com": {
|
||
slug: "fs-com",
|
||
name: "FS.com",
|
||
currency: "USD",
|
||
product_page_signals: ["Add to Cart", "Part No.", "SKU", "In Stock", "Reviews"],
|
||
category_page_signals: ["Products", "Filter by", "Sort by", "items found", "Category"],
|
||
price_hint: "Price is in USD, shown prominently near 'Add to Cart'. May show qty pricing table.",
|
||
stock_hint: "Look for 'In Stock', exact number like '847 In Stock', or 'Out of Stock'.",
|
||
known_moq: 1,
|
||
},
|
||
"10gtek": {
|
||
slug: "10gtek",
|
||
name: "10Gtek",
|
||
currency: "USD",
|
||
product_page_signals: ["Add to Cart", "Product Code:", "In Stock", "Ships from"],
|
||
category_page_signals: ["Shop All", "Filter", "Category", "Sort By"],
|
||
price_hint: "Price in USD near Add to Cart button. Volume pricing sometimes shown as table.",
|
||
stock_hint: "Stock level shown as text: 'In Stock', 'Low Stock', 'Out of Stock'.",
|
||
known_moq: 1,
|
||
},
|
||
"atgbics": {
|
||
slug: "atgbics",
|
||
name: "ATGBICS",
|
||
currency: "GBP",
|
||
product_page_signals: ["Add to Basket", "Part Number:", "Stock:", "Delivery"],
|
||
category_page_signals: ["Products", "Browse by", "Refine by"],
|
||
price_hint: "Price in GBP. ATGBICS uses Shopify, price is in a span with class 'price'.",
|
||
stock_hint: "Stock shown as 'In Stock', 'Limited Stock', or 'Out of Stock' near price.",
|
||
known_moq: 1,
|
||
},
|
||
"prolabs": {
|
||
slug: "prolabs",
|
||
name: "ProLabs",
|
||
currency: "USD",
|
||
product_page_signals: ["Add to Cart", "Part Number", "In Stock", "Specs"],
|
||
category_page_signals: ["Results", "Filter", "Category", "Sort"],
|
||
price_hint: "Price in USD. ProLabs may require login for prices — if so, mark price as null.",
|
||
stock_hint: "Stock availability shown near product title.",
|
||
known_moq: 1,
|
||
},
|
||
"farnell": {
|
||
slug: "farnell",
|
||
name: "Farnell",
|
||
currency: "EUR",
|
||
product_page_signals: ["Add to Basket", "Order Code:", "Stock:", "Lead Time:"],
|
||
category_page_signals: ["Products", "Refine Search", "Category", "results for"],
|
||
price_hint: "Price in EUR or GBP. Farnell shows break prices in a table with columns Qty/Price.",
|
||
stock_hint: "Stock shown as number, e.g. '47 In Stock'. Lead time shown in business days.",
|
||
known_moq: 1,
|
||
},
|
||
"mouser": {
|
||
slug: "mouser",
|
||
name: "Mouser Electronics",
|
||
currency: "EUR",
|
||
product_page_signals: ["Add to Cart", "Mouser Part No.", "Mfr. Part No.", "In Stock:"],
|
||
category_page_signals: ["Search Results", "Filter Results", "Products (", "Sort By"],
|
||
price_hint: "Mouser shows price per unit and break quantities. USD or EUR depending on locale.",
|
||
stock_hint: "Stock shown as exact number: 'In Stock: 124'. Lead time shown for out-of-stock items.",
|
||
known_moq: null,
|
||
},
|
||
};
|