The problem I kept hitting: an international invoice with a line total that’s off by a cent, or an HS code that appears in the commercial invoice but is absent from the packing list. Customs holds are rarely dramatic. They’re usually paperwork.
I wanted a tool that would catch these before the documents left the company. The constraint I set: nothing uploaded to a server. Every byte stays in the browser.
What it does
Upload an invoice and a packing list (Excel or PDF). The tool flags:
- Sum mismatches — line where qty × unit_price ≠ line total; grand total ≠ sum of lines
- HS code conflicts — codes present in one document but not the other
- Quantity discrepancies — total quantities that don’t match across documents
The parsing challenge
PDF trade documents are the worst case. They’re not structured data. The text extraction gives you a stream of strings with no inherent table structure.
The approach I landed on: extract all text, pattern-match for trailing numeric triples (qty, unit price, line total as the last three numbers on a line), and separately extract HS codes using the \d{4}[\.\-]?\d{2}[\.\-]?\d{0,4} pattern. It’s imperfect — complex PDFs with merged cells or rotated text break it — but it catches the common case.
Excel is cleaner. SheetJS gives you a proper row/column grid. The trickier part is header detection: the column labeled “HS” in one document might be “Tariff Code” in another. I match a set of synonyms against lowercased, whitespace-stripped header strings.
Stack
- Next.js 14, static export, deployed on Cloudflare Workers
-
xlsxfor Excel/CSV -
pdfjs-distwith CDN worker (no bundling headache) - All validation logic runs in the browser —
parseDocument(),validateSums(),crossValidate()
Free tier
Three document sets per month tracked in localStorage (monthly reset). Paid unlocks unlimited checks and the CSV report download.
Live at: https://compare-lab.xyz/trade-doc-validator/
The source for the validation logic is what I’d most want feedback on. If you deal with trade documents regularly and there’s a class of errors this misses, I want to know about it.
답글 남기기