{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "Tae Kim",
  "title_listed": "I built a client-side trade document validator — no upload, no server, just the browser",
  "keywords": "",
  "domain": "文本处理",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://dev.to/hannune/i-built-a-client-side-trade-document-validator-no-upload-no-server-just-the-browser-3c6o",
  "publish_date": "2026-07-27",
  "corpus_stage": "",
  "notes": "",
  "id": "db9cb049c175",
  "collected_at": "2026-10-02T14:04:01",
  "title": "I built a client-side trade document validator — no upload, no server, just the browser - DEV Community",
  "body_markdown": "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.\nI 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.\nWhat it does\nUpload an invoice and a packing list (Excel or PDF). The tool flags:\nSum mismatches\n— line where qty × unit_price ≠ line total; grand total ≠ sum of lines\nHS code conflicts\n— codes present in one document but not the other\nQuantity discrepancies\n— total quantities that don't match across documents\nThe parsing challenge\nPDF 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.\nThe 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\n\\d{4}[\\.\\-]?\\d{2}[\\.\\-]?\\d{0,4}\npattern. It's imperfect — complex PDFs with merged cells or rotated text break it — but it catches the common case.\nExcel 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.\nStack\nNext.js 14, static export, deployed on Cloudflare Workers\nxlsx\nfor Excel/CSV\npdfjs-dist\nwith CDN worker (no bundling headache)\nAll validation logic runs in the browser —\nparseDocument()\n,\nvalidateSums()\n,\ncrossValidate()\nFree tier\nThree document sets per month tracked in localStorage (monthly reset). Paid unlocks unlimited checks and the CSV report download.\nLive at:\nhttps://compare-lab.xyz/trade-doc-validator/\nThe 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.",
  "metadata": {
    "title": "I built a client-side trade document validator — no upload, no server, just the browser - DEV Community",
    "description": "The problem I kept hitting: an international invoice with a line total that's off by a cent, or an HS... Tagged with javascript, webdev, pdf, shipping.",
    "keywords": "javascript, webdev, pdf, shipping, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/hannune/i-built-a-client-side-trade-document-validator-no-upload-no-server-just-the-browser-3c6o",
    "og:title": "I built a client-side trade document validator — no upload, no server, just the browser",
    "og:description": "The problem I kept hitting: an international invoice with a line total that's off by a cent, or an HS...",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1450101499163-c8848c66ca85%3Fw%3D1200%26h%3D630%26fit%3Dcrop",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@HannuneTech",
    "twitter:title": "I built a client-side trade document validator — no upload, no server, just the browser",
    "twitter:description": "The problem I kept hitting: an international invoice with a line total that's off by a cent, or an HS...",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1450101499163-c8848c66ca85%3Fw%3D1200%26h%3D630%26fit%3Dcrop"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [],
  "images": [],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://compare-lab.xyz/trade-doc-validator/",
      "label": "https://compare-lab.xyz/trade-doc-validator/",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:49:41Z",
  "source_url": "https://dev.to/hannune/i-built-a-client-side-trade-document-validator-no-upload-no-server-just-the-browser-3c6o",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 2099,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/db9cb049c175/pages/source.html",
  "local_html": "attachments/db9cb049c175/pages/article.html",
  "body_base_path": "attachments/db9cb049c175/pages/",
  "body_markdown_path": "attachments/db9cb049c175/pages/article.md"
}