Rene Fichtmueller 681da54523 feat: Procurement Intelligence Engine (WS0c)
- 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
2026-04-01 22:04:33 +02:00

136 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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,
},
};