BOM & Quote Normalizer avatar

BOM & Quote Normalizer

Pricing

from $10.00 / 1,000 document processeds

Go to Apify Store
BOM & Quote Normalizer

BOM & Quote Normalizer

Normalize KiCad, Altium, EAGLE, JLCPCB and IPC-2581 BOMs plus supplier quotes (CSV, XLSX, PDF) into one clean schema: expanded designators, DNP, normalized values, price breaks and lead times, with source provenance.

Pricing

from $10.00 / 1,000 document processeds

Rating

0.0

(0)

Developer

Leon Yu

Leon Yu

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

8 days ago

Last modified

Security

Limited permissions

Share

Turn messy BOMs and supplier quotes into one clean, comparable schema, with a trace back to the source cell for every value.

Supported inputs: KiCad / Altium / EAGLE-Fusion / JLCPCB CSV & XLSX, Octopart-style BOM exports, IPC-2581 XML, supplier quotes in CSV/XLSX, and PDF BOMs and quotes that contain tables.

How to provide files

  • Public link: paste a direct URL to the CSV/XLSX/PDF/XML file into Files (URLs).
  • Your own file: in Apify Console open Storage → Key-value stores, create a store (or open any existing one), click Add record / Upload file, upload the BOM or quote, then copy the record's URL (https://api.apify.com/v2/key-value-stores/<storeId>/records/<key>) and paste it into Files (URLs). Never put your API token in a URL. If the store is private, make that one record or store publicly readable (or use a signed/shareable link). Query strings and credentials are removed from every URL the Actor writes to logs and outputs.
  • Only public http/https URLs are fetched; private-network and cloud-metadata addresses are refused.
  • Sites that block automated downloads: download the file yourself and upload it as above.

What it does

  • Detects encoding (UTF-8, GB18030, Shift-JIS, …), delimiter and the real header row (it skips title and metadata rows).
  • Maps columns with a multilingual alias table (English, 中文, Deutsch, 日本語), e.g. 位号, Menge, LCSC Part #, Price @100.
  • Expands designators (R1-R4, R7 becomes R1 R2 R3 R4 R7) and flags rows where the designator count doesn't match the quantity.
  • Detects DNP/DNI, normalizes values (4k7, 0.1u, 470uF, 2R2), parses price breaks, MOQ/SPQ and lead times (4 weeks, 10-12 wks, 现货, 3 Wochen).
  • Reads quote header fields: supplier company, quote number, date, validity, currency, Incoterms and payment terms.
  • Privacy: contact names, emails and phone numbers are removed during parsing and never written to the output.
  • PDF: tables are extracted with pdfplumber. Pages without a table can optionally use an AI fallback (Gemini Flash-Lite). LLM values must appear verbatim in their quoted source line (source_text, stored on the line as provenance.raw). A value that is not in that quote is dropped, and a quote longer than 3 lines, 400 characters, or half the page is rejected.

PDF pages without tables

Pages where pdfplumber finds no table (free-text quotes, forms) can only be read with the AI fallback (use_llm). The AI fallback is currently not active on this Actor (no AI key configured), so such pages are skipped with a warning, and a file with no extracted lines is not charged. Scanned or image-only PDFs need OCR, which isn't supported yet.

Limits

  • maxPdfPages (default 200, range 1–2000): only the first N pages of each PDF are processed. A JSON null uses the default.
  • maxLlmPages (default 50, range 0–1000): upper limit on AI-read pages per run. Each AI call times out after 30 seconds. Before every AI page the run checks that one more llm-page event still fits in Max total charge, and skips the rest when the count is 0.
  • The run stops charging, and stops processing further files, once your Max total charge for the run is reached.
  • The run stops starting new files and new AI pages once about 90% of the run timeout has elapsed (ACTOR_STARTED_AT … ACTOR_TIMEOUT_AT).
  • Set the run timeout to about 900 seconds in the run options (timeoutSecs). Actor specification 1 has no default-timeout field in actor.json. Default memory is 1024 MB.

Example input

{
"files": [
{"url": "https://example.com/board-bom.csv"}
],
"use_llm": true,
"maxLlmPages": 50,
"maxPdfPages": 200
}

Output

Dataset records with record_type = document (one per file) or line_item. Each line item has confidence (1.0 for exact column matches, 0.7–0.9 for fuzzy matches, ≤0.8 for AI values) and provenance (sheet/row/page, raw source text, method). For AI lines, provenance.raw is the quoted source line, and every kept value appears verbatim in that quote. The key-value store also gets OUTPUT.csv and OUTPUT.xlsx with all line items.

Example records for a one-line CSV (Designator,Qty,Value,MPN,Manufacturer / R1,1,10k,RC0402FR-0710KL,Yageo):

{
"record_type": "document",
"document_id": "6fb7a755167d5a60",
"source_file": "board-bom.csv",
"doc_type": "bom",
"detected_format": "csv/generic",
"supplier_name": null,
"quote_number": null,
"quote_date": null,
"valid_until": null,
"currency_default": null,
"incoterms": null,
"payment_terms": null,
"line_count": 1,
"total_amount": null,
"parse_method": "deterministic",
"llm_pages": 0,
"column_mapping": {
"Designator": {"field": "reference_designators", "confidence": 1.0},
"Qty": {"field": "quantity", "confidence": 1.0},
"Value": {"field": "value", "confidence": 1.0},
"MPN": {"field": "mpn", "confidence": 1.0},
"Manufacturer": {"field": "manufacturer", "confidence": 1.0}
},
"warnings": ["encoding=utf-8 delimiter=','"],
"pii_redacted": false
}
{
"record_type": "line_item",
"mpn": "RC0402FR-0710KL",
"manufacturer": "Yageo",
"alternates": [],
"supplier_part_number": null,
"supplier": null,
"description": null,
"value": "10k",
"value_normalized": {"number": 10000.0, "unit": "ohm"},
"footprint": null,
"reference_designators": ["R1"],
"designator_count_mismatch": false,
"quantity": 1,
"unit": null,
"dnp": false,
"unit_price": null,
"currency": null,
"price_breaks": [],
"extended_price": null,
"moq": null,
"spq": null,
"lead_time_days": null,
"lead_time_raw": null,
"packaging": null,
"confidence": {
"overall": 1.0,
"fields": {
"reference_designators": 1.0,
"quantity": 1.0,
"value": 1.0,
"mpn": 1.0,
"manufacturer": 1.0
}
},
"provenance": {
"sheet": null,
"row": 2,
"page": null,
"bbox": null,
"columns": {
"reference_designators": "Designator",
"quantity": "Qty",
"value": "Value",
"mpn": "MPN",
"manufacturer": "Manufacturer"
},
"raw": "R1,1,10k,RC0402FR-0710KL,Yageo",
"method": "deterministic"
},
"document_id": "6fb7a755167d5a60",
"line_no": 1
}

Pricing (pay per event)

  • document-processed: $0.01 per file that yields line items
  • line-item: $0.002 per line
  • llm-page: $0.01 per PDF page read by the AI fallback
  • apify-actor-start: $0.00005 (Apify platform default)
  • Capped at $5 per document. Charging stops at the cap, and you still get every line.
  • Files that fail or yield no line items are not charged. Identical lines repeated across sheets of one workbook are charged once.

Not included (yet)

No price or stock lookups on Octopart, Digi-Key, Mouser or LCSC; no OCR for scanned PDFs; no ERP integration.

Development

pip install -r requirements.txt pytest && pytest -q. Test fixtures: tests/fixtures (public samples, see ATTRIBUTION.md) and tests/fixtures/synthetic (fictional data).