{"id":101873,"date":"2026-07-04T18:50:17","date_gmt":"2026-07-04T18:50:17","guid":{"rendered":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/"},"modified":"2026-07-04T18:50:17","modified_gmt":"2026-07-04T18:50:17","slug":"designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation","status":"publish","type":"post","link":"https:\/\/youzum.net\/fr\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/","title":{"rendered":"Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation"},"content":{"rendered":"<p class=\"wp-block-paragraph\">In this <a href=\"https:\/\/drive.google.com\/file\/d\/1AHOPT6PnXwHE1i1J8HzyuizMGKP_Lq2v\/view?usp=sharing\" target=\"_blank\" rel=\"noreferrer noopener\">tutorial<\/a>, we build an end-to-end accounts-payable extraction pipeline with<a href=\"https:\/\/pxllnk.co\/rc5yap\" target=\"_blank\" rel=\"noreferrer noopener\"> <strong>lift-pdf<\/strong><\/a>, using synthetic invoice PDFs as controlled test documents and a structured JSON schema as the target output format. Instead of treating invoice parsing as a simple OCR task, we frame it as schema-guided document understanding: we generate realistic invoices, define fields such as vendor identity, billing party, PO number, line items, tax, total amount, balance due, and payment status, and then ask the model to extract those values directly from the rendered PDF layout. We also include practical extraction traps that appear in real finance workflows, such as distinguishing bill-to from ship-to, separating subtotal from after-tax total, returning null for absent values, and correctly marking partially paid invoices as unpaid when a balance remains. Through <a href=\"https:\/\/pxllnk.co\/rc5yap\">GPU-aware model loading, optional 4-bit quantization, PDF generation and extraction, scoring, and ledger construction<\/a>, we turn this tutorial into a compact yet realistic demonstration of document intelligence for invoice mining.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">N_DOCS               = 3       \nFORCE_FULL_PRECISION = False   \nFORCE_4BIT           = False   \nSHOW_FIRST_PAGE      = True    \nRUN_ON_REAL_PDF      = False   \nREAL_PDF_URL         = \"\"      \nREAL_PDF_PAGES       = \"0-1\"   \nPIN_PILLOW           = True    \nPILLOW_VERSION       = \"11.3.0\"\nimport os, sys, subprocess, json, re, time, warnings\nwarnings.filterwarnings(\"ignore\")\nos.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"\ndef pip(*pkgs, upgrade=False):\n   \"\"\"Install without invoking a shell (so '[hf]' is never glob-expanded).\"\"\"\n   args = [sys.executable, \"-m\", \"pip\", \"install\", \"-q\"] + ([\"-U\"] if upgrade else []) + list(pkgs)\n   print(\"  pip install\", *pkgs)\n   subprocess.run(args, check=False)\nprint(\"STEP 1\/7 \u00b7 Installing lift + light dependencies (first run is the slow one)\u2026\")\npip(\"reportlab\", \"pypdfium2\", \"pandas\", \"matplotlib\")  \npip(\"lift-pdf[hf]\")                                     \npip(\"bitsandbytes\", \"accelerate\", upgrade=True)         \nif PIN_PILLOW:\n   pip(f\"pillow=={PILLOW_VERSION}\")\n   if \"PIL\" in sys.modules:                      \n       import PIL\n       if getattr(PIL, \"__version__\", \"\") != PILLOW_VERSION:\n           print(f\"     Pinned Pillow {PILLOW_VERSION} on disk, but a stale \"\n                 f\"{getattr(PIL, '__version__', '?')} is loaded in memory \u2014 restarting runtime.\")\n           print(\"     Just re-run the cell(s) after Colab reconnects.\")\n           os.kill(os.getpid(), 9)\nprint(\"     \u2026install finished.n\")\nimport torch\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We begin by defining the runtime controls that decide how many invoices we process, whether we use 4-bit loading, whether we preview the generated PDF, and whether we later test a real invoice. We install the core dependencies for PDF generation, rendering, tabular analysis, plotting, and <a href=\"https:\/\/pxllnk.co\/rc5yap\">lift-pdf inference<\/a>. We also pin Pillow to a stable version because the tutorial addresses a known Colab compatibility issue among Pillow, torchvision, and Transformers. This setup gives us a reproducible environment before we load any model or generate any document.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">def detect_gpu():\n   if not torch.cuda.is_available():\n       raise SystemExit(\n           \"n\u2717 No CUDA GPU found. In Colab: Runtime \u25b8 Change runtime type \u25b8 GPU \"\n           \"(A100 is best; L4\/T4 also work).n\"\n       )\n   p  = torch.cuda.get_device_properties(0)\n   cc = torch.cuda.get_device_capability(0)\n   return p.name, p.total_memory \/ 1e9, cc\ndef enable_4bit(compute_dtype):\n   \"\"\"Load lift's weights in 4-bit NF4 whatever transformers Auto* class it uses internally.\"\"\"\n   import inspect, functools, transformers\n   from transformers import BitsAndBytesConfig\n   bnb = BitsAndBytesConfig(\n       load_in_4bit=True,\n       bnb_4bit_quant_type=\"nf4\",\n       bnb_4bit_use_double_quant=True,\n       bnb_4bit_compute_dtype=compute_dtype,\n   )\n   def patch(cls):\n       try:\n           cm   = inspect.getattr_static(cls, \"from_pretrained\")\n           orig = cm.__func__ if isinstance(cm, (classmethod, staticmethod)) else cm\n       except Exception:\n           return\n       @functools.wraps(orig)\n       def inner(cls_, *args, **kwargs):\n           kwargs.setdefault(\"quantization_config\", bnb)\n           kwargs.setdefault(\"device_map\", {\"\": 0})\n           model = orig(cls_, *args, **kwargs)\n           try:                                  \n               model.to   = lambda *a, **k: model\n               model.cuda = lambda *a, **k: model\n           except Exception:\n               pass\n           return model\n       cls.from_pretrained = classmethod(inner)\n   for name in [\"AutoModelForImageTextToText\", \"AutoModelForMultimodalLM\",\n                \"AutoModelForVision2Seq\", \"AutoModelForCausalLM\", \"AutoModel\"]:\n       c = getattr(transformers, name, None)\n       if c is not None:\n           patch(c)\n   try:\n       from transformers.modeling_utils import PreTrainedModel\n       patch(PreTrainedModel)\n   except Exception:\n       pass\nprint(\"STEP 2\/7 \u00b7 Preparing the model backend\u2026\")\ngpu_name, vram, cc = detect_gpu()\nuse_4bit      = FORCE_4BIT or (vram &lt; 34 and not FORCE_FULL_PRECISION)\ncompute_dtype = torch.bfloat16 if cc[0] &gt;= 8 else torch.float16  \nprint(f\"     GPU: {gpu_name} | ~{vram:.0f} GB | compute capability {cc[0]}.{cc[1]}\")\nprint(f\"     Load mode: {'4-bit NF4' if use_4bit else 'full bf16'} (compute dtype {compute_dtype})\")\nos.environ.setdefault(\"TORCH_DEVICE\", \"cuda:0\")\nos.environ.setdefault(\"MODEL_CHECKPOINT\", \"datalab-to\/lift\")\nif use_4bit:\n   enable_4bit(compute_dtype)\nfrom lift import extract\nfrom lift.model import InferenceManager\nprint(\"     Loading lift weights (\u224820 GB download on first run)\u2026\")\n_t = time.time()\nMODEL = InferenceManager(method=\"hf\")         \nprint(f\"     \u2713 model ready in {time.time() - _t:.0f}sn\")\ndef run_lift(pdf_path, schema, page_range=None):\n   kw = {\"model\": MODEL}\n   if page_range:\n       kw[\"page_range\"] = page_range\n   result = extract(pdf_path, schema, **kw)\n   return getattr(result, \"extraction\", None)\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We prepare the GPU-aware inference backend and decide whether the model should run in full precision or 4-bit NF4 quantization based on available VRAM. We patch the Hugging Face model-loading path so lift can transparently load the checkpoint with a BitsAndBytes quantization configuration when needed. We initialize the InferenceManager once and reuse it across all invoices, avoiding repeated model-loading overhead. Finally, we wrap lift.extract() inside a small helper so each PDF can be mined with the same schema and optional page range.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">DOCS = [\n   dict( \n       invoice_number=\"INV-2026-0412\",\n       invoice_date=\"2026-05-04\", due_date=\"2026-06-03\",\n       vendor_name=\"Cloudworks Inc.\",\n       vendor_address=\"500 Market St, Suite 900, San Francisco, CA 94105, USA\",\n       bill_to_name=\"Acme Robotics LLC\",\n       bill_to_address=\"12 Foundry Rd, Pittsburgh, PA 15222, USA\",\n       ship_to_name=\"Acme Robotics \u2014 Warehouse 4\",          \n       ship_to_address=\"88 Dockside Blvd, Newark, NJ 07114, USA\",\n       po_number=None,                                       \n       discount_amount=None,                                 \n       currency_code=\"USD\", currency_symbol=\"$\",\n       tax_rate=0.085,\n       amount_paid=0.00,                                     \n       line_items=[\n           (\"Cloud Compute \u2014 Standard tier (monthly)\", 3, 240.00),\n           (\"Object Storage \u2014 2 TB\", 1, 46.00),\n           (\"Priority Support add-on\", 1, 99.00),\n       ],\n       notes=\"Payment due within 30 days. Late payments accrue 1.5% monthly interest.\",\n   ),\n   dict( \n       invoice_number=\"INV-ND-2026-118\",\n       invoice_date=\"2026-04-18\", due_date=\"2026-05-18\",\n       vendor_name=\"Nordic Design Studio Oy\",\n       vendor_address=\"Etel\u00e4ranta 12, 00130 Helsinki, Finland\",\n       bill_to_name=\"Helsinki Media Oy\",\n       bill_to_address=\"Mannerheimintie 4, 00100 Helsinki, Finland\",\n       ship_to_name=None, ship_to_address=None,              \n       po_number=\"PO-HM-5589\",\n       discount_amount=785.00,                               \n       currency_code=\"EUR\", currency_symbol=\"\u20ac\",\n       tax_rate=0.24,                                        \n       amount_paid=8760.60,                                  \n       line_items=[\n           (\"Brand identity design package\", 1, 4200.00),\n           (\"Web UI design \u2014 12 screens\", 12, 180.00),\n           (\"Custom illustration set\", 1, 850.00),\n           (\"Design-system documentation\", 1, 640.00),\n       ],\n       notes=\"Paid in full \u2014 thank you. All amounts in EUR.\",\n   ),\n   dict( \n       invoice_number=\"INV-BR-4471\",\n       invoice_date=\"2026-06-01\", due_date=\"2026-07-15\",\n       vendor_name=\"BuildRight Contractors Inc.\",\n       vendor_address=\"740 Industrial Way, Austin, TX 78744, USA\",\n       bill_to_name=\"Sunrise Property Group\",\n       bill_to_address=\"9 Lakeview Terrace, Austin, TX 78703, USA\",\n       ship_to_name=\"Sunrise Property Group \u2014 Lot 14 site office\",  \n       ship_to_address=\"Parcel 14, Mesa Ridge Development, Austin, TX 78737, USA\",\n       po_number=\"PO-SPG-2211\",\n       discount_amount=None,\n       currency_code=\"USD\", currency_symbol=\"$\",\n       tax_rate=0.07,\n       amount_paid=15000.00,                                 \n       line_items=[\n           (\"Site preparation and grading\", 1, 18500.00),\n           (\"Foundation concrete pour (Phase 1)\", 1, 27400.00),\n       ],\n       notes=\"A 15,000 USD deposit has been received. Remaining balance due by the date above.\",\n   ),\n][:N_DOCS]\ndef compute(d):\n   \"\"\"Derive every money figure once, so PDF text and ground truth are guaranteed identical.\"\"\"\n   items = [(desc, q, up, round(q * up, 2)) for (desc, q, up) in d[\"line_items\"]]\n   subtotal = round(sum(t for *_, t in items), 2)\n   disc     = d.get(\"discount_amount\")\n   taxable  = round(subtotal - (disc or 0.0), 2)\n   tax      = round(taxable * d[\"tax_rate\"], 2)\n   total    = round(taxable + tax, 2)\n   paid     = round(d.get(\"amount_paid\", 0.0), 2)\n   balance  = round(total - paid, 2)\n   return dict(items=items, subtotal=subtotal, discount=disc, tax=tax,\n               total=total, amount_paid=paid, balance=balance, is_paid=(balance &lt;= 0.005))\ndef ground_truth(d):\n   \"\"\"Reshape raw inputs + computed totals into the exact JSON shape our schema asks for.\"\"\"\n   c = compute(d)\n   return {\n       \"invoice_number\": d[\"invoice_number\"],\n       \"invoice_date\": d[\"invoice_date\"],\n       \"due_date\": d[\"due_date\"],\n       \"vendor\": {\"name\": d[\"vendor_name\"], \"address\": d[\"vendor_address\"]},\n       \"customer_name\": d[\"bill_to_name\"],                   \n       \"purchase_order_number\": d.get(\"po_number\"),          \n       \"currency\": d[\"currency_code\"],\n       \"line_items\": [{\"description\": desc, \"quantity\": q,\n                       \"unit_price\": up, \"line_total\": t} for (desc, q, up, t) in c[\"items\"]],\n       \"subtotal\": c[\"subtotal\"],\n       \"discount_amount\": c[\"discount\"],                     \n       \"tax_amount\": c[\"tax\"],\n       \"total_amount\": c[\"total\"],                           \n       \"amount_paid\": c[\"amount_paid\"],\n       \"balance_due\": c[\"balance\"],\n       \"is_paid\": c[\"is_paid\"],                              \n   }\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We define a controlled synthetic invoice corpus that mimics realistic accounts-payable documents across different vendors, currencies, payment states, and invoice layouts. Each invoice includes raw business fields such as vendor details, bill-to and ship-to parties, PO numbers, discounts, taxes, deposits, and line items. We then compute derived financial values such as subtotal, tax, total, balance due, and paid status from the raw invoice data. This ensures the rendered PDF and the ground-truth JSON remain mathematically consistent.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">def render_pdf(d, path):\n   &quot;&quot;&quot;Draw a realistic one-page invoice: header, meta, bill\/ship, line items, totals, payment.&quot;&quot;&quot;\n   from reportlab.lib.pagesizes import LETTER\n   from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle\n   from reportlab.lib.units import inch\n   from reportlab.lib import colors\n   from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer,\n                                   Table, TableStyle)\n   c   = compute(d)\n   sym = d[&quot;currency_symbol&quot;]\n   def money(x): return f&quot;{sym}{x:,.2f}&quot;\n   ss   = getSampleStyleSheet()\n   H1   = ParagraphStyle(&quot;H1&quot;,   parent=ss[&quot;Title&quot;],    fontSize=18, leading=22, spaceAfter=2)\n   SMALL= ParagraphStyle(&quot;SM&quot;,   parent=ss[&quot;Normal&quot;],   fontSize=8.5, textColor=colors.grey, leading=11)\n   LBL  = ParagraphStyle(&quot;LBL&quot;,  parent=ss[&quot;Normal&quot;],   fontSize=8.5, textColor=colors.HexColor(&quot;#2b3a67&quot;),\n                         spaceAfter=1, fontName=&quot;Helvetica-Bold&quot;)\n   BODY = ParagraphStyle(&quot;BODY&quot;, parent=ss[&quot;Normal&quot;],   fontSize=9.5, leading=13)\n   RIGHT= ParagraphStyle(&quot;R&quot;,    parent=ss[&quot;Normal&quot;],   fontSize=16, leading=18, alignment=2,\n                         textColor=colors.HexColor(&quot;#2b3a67&quot;), fontName=&quot;Helvetica-Bold&quot;)\n   story = []\n  \n   head = Table([[\n       [Paragraph(d[&quot;vendor_name&quot;], H1), Paragraph(d[&quot;vendor_address&quot;], SMALL)],\n       [Paragraph(&quot;INVOICE&quot;, RIGHT),\n        Paragraph(f&quot;{d[&#039;invoice_number&#039;]}&quot;, ParagraphStyle(&#039;n&#039;, parent=SMALL, alignment=2, fontSize=9.5))],\n   ]], colWidths=[4.2 * inch, 2.8 * inch])\n   head.setStyle(TableStyle([(&quot;VALIGN&quot;, (0, 0), (-1, -1), &quot;TOP&quot;)]))\n   story += [head, Spacer(1, 10)]\n  \n   meta_rows = [[&quot;Invoice date&quot;, d[&quot;invoice_date&quot;], &quot;Due date&quot;, d[&quot;due_date&quot;]]]\n   if d.get(&quot;po_number&quot;):\n       meta_rows.append([&quot;PO number&quot;, d[&quot;po_number&quot;], &quot;Currency&quot;, d[&quot;currency_code&quot;]])\n   else:\n       meta_rows.append([&quot;Currency&quot;, d[&quot;currency_code&quot;], &quot;&quot;, &quot;&quot;])\n   meta = Table(meta_rows, colWidths=[1.3 * inch, 2.2 * inch, 1.3 * inch, 2.2 * inch])\n   meta.setStyle(TableStyle([\n       (&quot;FONTSIZE&quot;, (0, 0), (-1, -1), 9),\n       (&quot;TEXTCOLOR&quot;, (0, 0), (0, -1), colors.HexColor(&quot;#2b3a67&quot;)),\n       (&quot;TEXTCOLOR&quot;, (2, 0), (2, -1), colors.HexColor(&quot;#2b3a67&quot;)),\n       (&quot;FONTNAME&quot;, (0, 0), (0, -1), &quot;Helvetica-Bold&quot;),\n       (&quot;FONTNAME&quot;, (2, 0), (2, -1), &quot;Helvetica-Bold&quot;),\n       (&quot;BOTTOMPADDING&quot;, (0, 0), (-1, -1), 3), (&quot;TOPPADDING&quot;, (0, 0), (-1, -1), 3)]))\n   story += [meta, Spacer(1, 12)]\n  \n   bill = [Paragraph(&quot;BILL TO&quot;, LBL), Paragraph(d[&quot;bill_to_name&quot;], BODY),\n           Paragraph(d[&quot;bill_to_address&quot;], SMALL)]\n   if d.get(&quot;ship_to_name&quot;):\n       ship = [Paragraph(&quot;SHIP TO&quot;, LBL), Paragraph(d[&quot;ship_to_name&quot;], BODY),\n               Paragraph(d[&quot;ship_to_address&quot;], SMALL)]\n   else:\n       ship = [Paragraph(&quot;SHIP TO&quot;, LBL), Paragraph(&quot;Same as billing address&quot;, SMALL)]\n   parties = Table([[bill, ship]], colWidths=[3.5 * inch, 3.5 * inch])\n   parties.setStyle(TableStyle([(&quot;VALIGN&quot;, (0, 0), (-1, -1), &quot;TOP&quot;),\n                                (&quot;LEFTPADDING&quot;, (0, 0), (-1, -1), 0)]))\n   story += [parties, Spacer(1, 14)]\n  \n   rows = [[&quot;Description&quot;, &quot;Qty&quot;, &quot;Unit price&quot;, &quot;Amount&quot;]]\n   for (desc, q, up, t) in c[&quot;items&quot;]:\n       rows.append([desc, str(q), money(up), money(t)])\n   items_tbl = Table(rows, colWidths=[3.5 * inch, 0.7 * inch, 1.4 * inch, 1.4 * inch])\n   items_tbl.setStyle(TableStyle([\n       (&quot;BACKGROUND&quot;, (0, 0), (-1, 0), colors.HexColor(&quot;#2b3a67&quot;)),\n       (&quot;TEXTCOLOR&quot;, (0, 0), (-1, 0), colors.white),\n       (&quot;FONTSIZE&quot;, (0, 0), (-1, -1), 9.5),\n       (&quot;ALIGN&quot;, (1, 0), (-1, -1), &quot;RIGHT&quot;),\n       (&quot;GRID&quot;, (0, 0), (-1, -1), 0.4, colors.HexColor(&quot;#cdd3e6&quot;)),\n       (&quot;ROWBACKGROUNDS&quot;, (0, 1), (-1, -1), [colors.white, colors.HexColor(&quot;#eef1f8&quot;)]),\n       (&quot;LEFTPADDING&quot;, (0, 0), (-1, -1), 8), (&quot;TOPPADDING&quot;, (0, 0), (-1, -1), 5),\n       (&quot;BOTTOMPADDING&quot;, (0, 0), (-1, -1), 5)]))\n   story += [items_tbl, Spacer(1, 10)]\n  \n   tot_rows = [[&quot;Subtotal&quot;, money(c[&quot;subtotal&quot;])]]\n   if c[&quot;discount&quot;]:\n       tot_rows.append([&quot;Discount&quot;, &quot;-&quot; + money(c[&quot;discount&quot;])])\n   tot_rows.append([f&quot;Tax ({d[&#039;tax_rate&#039;]*100:.1f}%)&quot;, money(c[&quot;tax&quot;])])\n   tot_rows.append([&quot;TOTAL&quot;, money(c[&quot;total&quot;])])\n   totals = Table(tot_rows, colWidths=[1.6 * inch, 1.4 * inch], hAlign=&quot;RIGHT&quot;)\n   totals.setStyle(TableStyle([\n       (&quot;FONTSIZE&quot;, (0, 0), (-1, -1), 10),\n       (&quot;ALIGN&quot;, (0, 0), (-1, -1), &quot;RIGHT&quot;),\n       (&quot;LINEABOVE&quot;, (0, -1), (-1, -1), 1.0, colors.HexColor(&quot;#2b3a67&quot;)),\n       (&quot;FONTNAME&quot;, (0, -1), (-1, -1), &quot;Helvetica-Bold&quot;),\n       (&quot;TEXTCOLOR&quot;, (0, -1), (-1, -1), colors.HexColor(&quot;#2b3a67&quot;)),\n       (&quot;TOPPADDING&quot;, (0, 0), (-1, -1), 3), (&quot;BOTTOMPADDING&quot;, (0, 0), (-1, -1), 3)]))\n   story += [totals, Spacer(1, 8)]\n  \n   pay_rows = [[&quot;Amount paid&quot;, money(c[&quot;amount_paid&quot;])],\n               [&quot;Balance due&quot;, money(c[&quot;balance&quot;])]]\n   pay = Table(pay_rows, colWidths=[1.6 * inch, 1.4 * inch], hAlign=&quot;RIGHT&quot;)\n   due_color = colors.HexColor(&quot;#1b7a3d&quot;) if c[&quot;is_paid&quot;] else colors.HexColor(&quot;#7a2e2e&quot;)\n   pay.setStyle(TableStyle([\n       (&quot;FONTSIZE&quot;, (0, 0), (-1, -1), 10),\n       (&quot;ALIGN&quot;, (0, 0), (-1, -1), &quot;RIGHT&quot;),\n       (&quot;FONTNAME&quot;, (0, 1), (-1, 1), &quot;Helvetica-Bold&quot;),\n       (&quot;TEXTCOLOR&quot;, (0, 1), (-1, 1), due_color),\n       (&quot;TOPPADDING&quot;, (0, 0), (-1, -1), 2), (&quot;BOTTOMPADDING&quot;, (0, 0), (-1, -1), 2)]))\n   status = &quot;PAID IN FULL&quot; if c[&quot;is_paid&quot;] else &quot;BALANCE DUE&quot;\n   story += [pay, Spacer(1, 6),\n             Paragraph(f&quot;&lt;b&gt;Status:&lt;\/b&gt; {status}&quot;, BODY), Spacer(1, 16),\n             Paragraph(&quot;Notes&quot;, LBL), Paragraph(d[&quot;notes&quot;], BODY)]\n   SimpleDocTemplate(path, pagesize=LETTER,\n                     topMargin=0.7 * inch, bottomMargin=0.7 * inch,\n                     leftMargin=0.8 * inch, rightMargin=0.8 * inch).build(story)\nprint(&quot;STEP 3\/7 &middot; Generating synthetic invoice PDFs&hellip;&quot;)\nCORPUS = []\nfor i, d in enumerate(DOCS):\n   path = f&quot;\/content\/invoice_{i}.pdf&quot; if os.path.isdir(&quot;\/content&quot;) else f&quot;invoice_{i}.pdf&quot;\n   render_pdf(d, path)\n   CORPUS.append((d, ground_truth(d), path))\n   print(f&quot;     \u2713 {os.path.basename(path)}  &mdash;  {d[&#039;vendor_name&#039;]} &rarr; {d[&#039;bill_to_name&#039;]}&quot;)\nprint()\nif SHOW_FIRST_PAGE:\n   try:\n       import pypdfium2 as pdfium, matplotlib.pyplot as plt\n       pg  = pdfium.PdfDocument(CORPUS[0][2])[0]\n       img = pg.render(scale=2.0).to_pil()\n       plt.figure(figsize=(6.4, 8.3)); plt.imshow(img); plt.axis(&quot;off&quot;)\n       plt.title(&quot;What lift reads &mdash; page 1 of invoice_0.pdf&quot;, fontsize=10); plt.show()\n   except Exception as e:\n       print(&quot;     page preview skipped:&quot;, e, &quot;n&quot;)\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We render each synthetic invoice into a realistic one-page PDF using ReportLab, including headers, invoice metadata, billing and shipping blocks, line-item tables, totals, payment status, and notes. We intentionally preserve layout elements that make invoice extraction difficult, such as separate bill-to and ship-to sections and subtotal versus total fields. We then generate the PDF corpus and optionally preview the first page using pypdfium2 and Matplotlib. This step creates the actual visual documents that lift reads during extraction.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">SCHEMA = {\n   \"type\": \"object\",\n   \"properties\": {\n       \"invoice_number\": {\"type\": \"string\", \"description\": \"The invoice's unique identifier \/ number\"},\n       \"invoice_date\":   {\"type\": \"string\", \"description\": \"Date the invoice was issued (as printed)\"},\n       \"due_date\":       {\"type\": \"string\", \"description\": \"Date payment is due\"},\n       \"vendor\": {\n           \"type\": \"object\",\n           \"description\": \"The party that ISSUED the invoice (the seller \/ supplier)\",\n           \"properties\": {\n               \"name\":    {\"type\": \"string\"},\n               \"address\": {\"type\": \"string\"},\n           }},\n       \"customer_name\": {\"type\": \"string\",\n                         \"description\": \"The party the invoice is billed TO (the 'Bill To' party) \u2014 \"\n                                        \"not the vendor, and not the 'Ship To' party if it differs\"},\n       \"purchase_order_number\": {\"type\": \"string\",\n                                 \"description\": \"The PO number referenced on the invoice. \"\n                                                \"Return null if no purchase-order number appears\"},\n       \"currency\": {\"type\": \"string\",\n                    \"description\": \"ISO 4217 currency code of the amounts, e.g. USD or EUR\"},\n       \"line_items\": {\n           \"type\": \"array\",\n           \"description\": \"Every billed line item, in order\",\n           \"items\": {\"type\": \"object\", \"properties\": {\n               \"description\": {\"type\": \"string\"},\n               \"quantity\":    {\"type\": \"number\"},\n               \"unit_price\":  {\"type\": \"number\"},\n               \"line_total\":  {\"type\": \"number\", \"description\": \"quantity \u00d7 unit_price for this line\"},\n           }}},\n       \"subtotal\":        {\"type\": \"number\", \"description\": \"Sum of line totals BEFORE tax and discount\"},\n       \"discount_amount\": {\"type\": \"number\",\n                           \"description\": \"Total discount applied. Return null if no discount is shown\"},\n       \"tax_amount\":      {\"type\": \"number\", \"description\": \"Total tax \/ VAT charged\"},\n       \"total_amount\":    {\"type\": \"number\",\n                           \"description\": \"The grand total the customer owes, AFTER tax and any discount \u2014 \"\n                                          \"NOT the pre-tax subtotal and NOT the tax line\"},\n       \"amount_paid\":     {\"type\": \"number\", \"description\": \"Amount already paid deposits included\"},\n       \"balance_due\":     {\"type\": \"number\", \"description\": \"Outstanding balance still owed\"},\n       \"is_paid\":         {\"type\": \"boolean\",\n                           \"description\": \"true ONLY if the balance due is zero. A partial payment or \"\n                                          \"deposit with a remaining balance does NOT count as paid\"},\n   },\n   \"required\": [\"invoice_number\", \"total_amount\", \"vendor\"],\n}\ndef _norm(s):\n   return re.sub(r\"s+\", \" \", str(s).strip().lower()).strip(\" .,:;\/\")\ndef _num(x):\n   try:    return float(str(x).replace(\"%\", \"\").replace(\",\", \"\").replace(\"$\", \"\").replace(\"\u20ac\", \"\").strip())\n   except Exception: return None\ndef leaf_equal(gt, pr):\n   if gt is None and pr is None:                       return True\n   if gt is None or pr is None:                        return False\n   if isinstance(gt, bool) or isinstance(pr, bool):    return bool(gt) == bool(pr)\n   a, b = _num(gt), _num(pr)\n   if a is not None and b is not None:                \n       return abs(a - b) &lt; 1e-6 if b == 0 else abs(a - b) \/ max(abs(a), abs(b)) &lt; 5e-3\n   return _norm(gt) == _norm(pr)                      \ndef flatten(o, prefix=\"\"):\n   out = {}\n   if isinstance(o, dict):\n       for k, v in o.items():\n           out.update(flatten(v, f\"{prefix}.{k}\" if prefix else k))\n   elif isinstance(o, list):\n       for i, v in enumerate(o):\n           out.update(flatten(v, f\"{prefix}[{i}]\"))\n   else:\n       out[prefix] = o\n   return out\ndef score(gt, pred):\n   fg, fp = flatten(gt), flatten(pred or {})\n   rows, correct = [], 0\n   for key, gv in fg.items():\n       present = key in fp\n       pv = fp.get(key)\n       ok = (gv is None and (not present or pv is None)) or (present and leaf_equal(gv, pv))\n       correct += int(ok)\n       rows.append((key, gv, (pv if present else None), ok))\n   return (correct \/ len(fg) if fg else 0.0), rows\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We define the JSON extraction schema that tells lift exactly which invoice fields to recover and how to interpret ambiguous values. The schema uses field descriptions to guide the model toward the bill-to customer, the after-tax total amount, nullable PO and discount fields, and the correct payment-status logic. We also implement normalization, numeric parsing, recursive flattening, and field-level comparison utilities. These scoring functions let us compare lift\u2019s predicted JSON against the known ground truth, with tolerance for differences in numeric formatting.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">print(\"STEP 4\/7 \u00b7 Extracting with lift and scoring against ground truth\u2026n\")\nresults = []\nfor i, (src, gt, path) in enumerate(CORPUS):\n   t0 = time.time()\n   pred = run_lift(path, SCHEMA)\n   dt = time.time() - t0\n   acc, rows = score(gt, pred)\n   results.append(dict(src=src, gt=gt, pred=pred, acc=acc, rows=rows, seconds=dt))\n   print(f\"     invoice {i} \u00b7 {src['vendor_name']:&lt;28} field accuracy {acc*100:5.1f}%   ({dt:.1f}s)\")\nr0 = results[0]\nprint(\"n\" + \"=\" * 90)\nprint(f\"DETAILED VIEW \u00b7 invoice 0 \u00b7 {r0['src']['vendor_name']} \u2192 {r0['src']['bill_to_name']}\")\nprint(\"=\" * 90)\nprint(\"Raw JSON lift returned guaranteed to match the schema shape:n\")\nprint(json.dumps(r0[\"pred\"], indent=2, ensure_ascii=False))\nimport pandas as pd\npd.set_option(\"display.max_colwidth\", 46)\npd.set_option(\"display.width\", 120)\ngrade = pd.DataFrame([{\"field\": k,\n                      \"ground_truth\": (\"\u2205 null\" if g is None else g),\n                      \"lift_predicted\": (\"\u2205 null\" if p is None else p),\n                      \"\u2713\": \"\u2713\" if ok else \"\u2717\"}\n                     for (k, g, p, ok) in r0[\"rows\"]])\nprint(\"nField-by-field grade:n\")\nprint(grade.to_string(index=False))\nprint(\"nWhat to look for:\")\nprint(\"  \u2022 total_amount should be the AFTER-TAX grand total, not the subtotal \u2014 the distractor test.\")\nprint(\"  \u2022 customer_name should be the BILL-TO party, not the different SHIP-TO warehouse.\")\nprint(\"  \u2022 purchase_order_number and discount_amount should be \u2205 null: invoice 0 has neither.\")\nprint(\"  \u2022 on invoice 2, is_paid must be False \u2014 a $15,000 deposit is shown but a balance remains.\")\nprint(\"n\" + \"=\" * 90)\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We run lift across every generated invoice, collect the extracted JSON, measure runtime, and calculate field-level accuracy against the ground truth. We then inspect the first invoice in detail by printing the raw model output and a field-by-field grading table. This diagnostic view helps us verify whether the model handles the most important extraction traps correctly, including null fields, bill-to versus ship-to selection, and total amount disambiguation. We use this section as the main evaluation checkpoint for the tutorial.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">print(\"STEP 5\/7 \u00b7 Assembling the extractions into a queryable accounts-payable ledger\")\nprint(\"=\" * 90)\ndef g(d, path, default=None):\n   cur = d\n   for key in path.split(\".\"):\n       if isinstance(cur, dict) and cur.get(key) is not None:\n           cur = cur[key]\n       else:\n           return default\n   return cur\nkb = pd.DataFrame([{\n   \"invoice\":    g(r[\"pred\"], \"invoice_number\"),\n   \"vendor\":     g(r[\"pred\"], \"vendor.name\"),\n   \"customer\":   g(r[\"pred\"], \"customer_name\"),\n   \"ccy\":        g(r[\"pred\"], \"currency\"),\n   \"total\":      g(r[\"pred\"], \"total_amount\"),\n   \"paid\":       g(r[\"pred\"], \"amount_paid\"),\n   \"balance\":    g(r[\"pred\"], \"balance_due\"),\n   \"is_paid\":    g(r[\"pred\"], \"is_paid\"),\n   \"items\":      len(g(r[\"pred\"], \"line_items\", []) or []),\n   \"po\":         g(r[\"pred\"], \"purchase_order_number\"),\n   \"field_acc\":  round(r[\"acc\"], 3),\n} for r in results])\nprint(\"nAccounts-payable ledger one row per mined invoice:n\")\nprint(kb.to_string(index=False))\nprint(\"nExample query \u2014 OUTSTANDING invoices not fully paid, largest balance first:n\")\nowed = kb[kb[\"is_paid\"] != True].sort_values(\"balance\", ascending=False)\nprint(owed.to_string(index=False) if len(owed) else \"  everything is paid <img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png\" alt=\"\ud83c\udf89\" class=\"wp-smiley\" \/>\")\ntry:\n   total_owed = sum((r or 0) for r in kb.loc[kb[\"is_paid\"] != True, \"balance\"])\n   print(f\"nTotal outstanding across the batch: {total_owed:,.2f} mixed currencies \u2014 group by ccy in practice\")\nexcept Exception:\n   pass\noverall = sum(r[\"acc\"] for r in results) \/ len(results)\nprint(f\"nSTEP 6\/7 \u00b7 Overall field accuracy across {len(results)} invoices: {overall*100:.1f}%\")\nprint(\"     Datalab report lift at ~90.2% field accuracy on their 225-doc benchmark.\")\ntry:\n   import matplotlib.pyplot as plt\n   labels = [r[\"src\"][\"vendor_name\"].split()[0] for r in results]\n   accs   = [r[\"acc\"] * 100 for r in results]\n   plt.figure(figsize=(7, 3.6))\n   bars = plt.bar(labels, accs, color=\"#2b3a67\")\n   plt.axhline(90.2, ls=\"--\", color=\"#7a2e2e\", lw=1.4, label=\"lift benchmark 90.2%\")\n   for b, a in zip(bars, accs):\n       plt.text(b.get_x() + b.get_width()\/2, a + 1, f\"{a:.0f}%\", ha=\"center\", fontsize=9)\n   plt.ylim(0, 108); plt.ylabel(\"Field accuracy %\")\n   plt.title(\"Per-invoice extraction accuracy on the synthetic corpus\")\n   plt.legend(fontsize=8); plt.tight_layout(); plt.show()\nexcept Exception as e:\n   print(\"     chart skipped:\", e, \")\")\nif RUN_ON_REAL_PDF and REAL_PDF_URL:\n   print(\"n\" + \"=\" * 90)\n   print(f\"STEP 7\/7 \u00b7 Bonus \u2014 extracting from a REAL invoice: {REAL_PDF_URL}\")\n   print(\"=\" * 90)\n   try:\n       import urllib.request\n       real_path = \"\/content\/real_invoice.pdf\" if os.path.isdir(\"\/content\") else \"real_invoice.pdf\"\n       urllib.request.urlretrieve(REAL_PDF_URL, real_path)\n       pred_real = run_lift(real_path, SCHEMA, page_range=REAL_PDF_PAGES)\n       print(\"nExtraction no ground truth \u2014 real invoices vary wildly in layout:n\")\n       print(json.dumps(pred_real, indent=2, ensure_ascii=False))\n       print(\"nTip: real invoices differ hugely by vendor. Tighten the field `description`s and use \"\n             \"page_range to point lift at the page that carries the totals block.\")\n   except Exception as e:\n       print(\"     real-PDF pass failed:\", e)\nelse:\n   print(\"nSTEP 7\/7 \u00b7 skipped set RUN_ON_REAL_PDF = True and REAL_PDF_URL to mine your own invoice.\")\nprint(\"n<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/> Done. You now have: schema-valid invoice extractions, a scored grade, and an AP ledger.\")\nprint(\"   Next: swap in your own invoice PDFs + tweak SCHEMA, or reuse MODEL across thousands of files.\")\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">We convert the extracted invoice records into a compact accounts-payable ledger using pandas, with one row per mined invoice. We include operational fields such as invoice number, vendor, customer, currency, total, amount paid, balance due, payment status, item count, PO number, and extraction accuracy. We then query the ledger for outstanding invoices and calculate the total unpaid balance across the batch. Finally, we visualize per-invoice accuracy and optionally apply the same schema to a real invoice PDF when a URL is provided.<\/p>\n<p class=\"wp-block-paragraph\">In conclusion, we completed the tutorial by converting unstructured <a href=\"https:\/\/pxllnk.co\/rc5yap\">invoice PDFs into schema-valid JSON records, validating each extracted field against known ground truth<\/a>, and assembling the results into a queryable accounts-payable ledger. This gives us more than a basic extraction demo: we evaluated how well the model handles numerical fields, nested vendor objects, arrays of line items, nullable attributes, boolean payment logic, and layout-level distractors that often break brittle parsers. We also reused a single loaded inference manager across the batch, which reflects how we would scale this workflow across many invoices without repeatedly reinitializing the model. By the end, we have a reproducible pipeline that generates test invoices, extracts structured financial data, scores the output, visualizes accuracy, and optionally extends to real invoice PDFs with the same schema-driven approach.<\/p>\n<p class=\"wp-block-paragraph\">\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n<\/p><p class=\"wp-block-paragraph\">Check out the<strong>\u00a0<a href=\"https:\/\/drive.google.com\/file\/d\/1AHOPT6PnXwHE1i1J8HzyuizMGKP_Lq2v\/view?usp=sharing\" target=\"_blank\" rel=\"noreferrer noopener\">Full Colab Notebook here<\/a><\/strong>.<strong>\u00a0<\/strong>Also,\u00a0feel free to follow us on\u00a0<strong><a href=\"https:\/\/x.com\/intent\/follow?screen_name=marktechpost\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Twitter<\/mark><\/a><\/strong>\u00a0and don\u2019t forget to join our\u00a0<strong><a href=\"https:\/\/www.reddit.com\/r\/machinelearningnews\/\" target=\"_blank\" rel=\"noreferrer noopener\">150k+ML SubReddit<\/a><\/strong>\u00a0and Subscribe to\u00a0<strong><a href=\"https:\/\/www.aidevsignals.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">our Newsletter<\/a><\/strong>. Wait! are you on telegram?\u00a0<strong><a href=\"https:\/\/t.me\/machinelearningresearchnews\" target=\"_blank\" rel=\"noreferrer noopener\">now you can join us on telegram as well.<\/a><\/strong><\/p>\n<p class=\"wp-block-paragraph\">Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.?\u00a0<strong><a href=\"https:\/\/forms.gle\/wbash1wF6efRj8G58\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Connect with us<\/mark><\/a><\/strong><\/p>\n<p>The post <a href=\"https:\/\/www.marktechpost.com\/2026\/07\/03\/schema-guided-invoice-intelligence-pipeline-with-lift-pdf\/\">Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation<\/a> appeared first on <a href=\"https:\/\/www.marktechpost.com\/\">MarkTechPost<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>In this tutorial, we build an end-to-end accounts-payable extraction pipeline with lift-pdf, using synthetic invoice PDFs as controlled test documents and a structured JSON schema as the target output format. Instead of treating invoice parsing as a simple OCR task, we frame it as schema-guided document understanding: we generate realistic invoices, define fields such as vendor identity, billing party, PO number, line items, tax, total amount, balance due, and payment status, and then ask the model to extract those values directly from the rendered PDF layout. We also include practical extraction traps that appear in real finance workflows, such as distinguishing bill-to from ship-to, separating subtotal from after-tax total, returning null for absent values, and correctly marking partially paid invoices as unpaid when a balance remains. Through GPU-aware model loading, optional 4-bit quantization, PDF generation and extraction, scoring, and ledger construction, we turn this tutorial into a compact yet realistic demonstration of document intelligence for invoice mining. Copy CodeCopiedUse a different Browser N_DOCS = 3 FORCE_FULL_PRECISION = False FORCE_4BIT = False SHOW_FIRST_PAGE = True RUN_ON_REAL_PDF = False REAL_PDF_URL = &#8220;&#8221; REAL_PDF_PAGES = &#8220;0-1&#8221; PIN_PILLOW = True PILLOW_VERSION = &#8220;11.3.0&#8221; import os, sys, subprocess, json, re, time, warnings warnings.filterwarnings(&#8220;ignore&#8221;) os.environ[&#8220;TOKENIZERS_PARALLELISM&#8221;] = &#8220;false&#8221; def pip(*pkgs, upgrade=False): &#8220;&#8221;&#8221;Install without invoking a shell (so &#8216;[hf]&#8217; is never glob-expanded).&#8221;&#8221;&#8221; args = [sys.executable, &#8220;-m&#8221;, &#8220;pip&#8221;, &#8220;install&#8221;, &#8220;-q&#8221;] + ([&#8220;-U&#8221;] if upgrade else []) + list(pkgs) print(&#8221; pip install&#8221;, *pkgs) subprocess.run(args, check=False) print(&#8220;STEP 1\/7 \u00b7 Installing lift + light dependencies (first run is the slow one)\u2026&#8221;) pip(&#8220;reportlab&#8221;, &#8220;pypdfium2&#8221;, &#8220;pandas&#8221;, &#8220;matplotlib&#8221;) pip(&#8220;lift-pdf[hf]&#8221;) pip(&#8220;bitsandbytes&#8221;, &#8220;accelerate&#8221;, upgrade=True) if PIN_PILLOW: pip(f&#8221;pillow=={PILLOW_VERSION}&#8221;) if &#8220;PIL&#8221; in sys.modules: import PIL if getattr(PIL, &#8220;__version__&#8221;, &#8220;&#8221;) != PILLOW_VERSION: print(f&#8221; Pinned Pillow {PILLOW_VERSION} on disk, but a stale &#8221; f&#8221;{getattr(PIL, &#8216;__version__&#8217;, &#8216;?&#8217;)} is loaded in memory \u2014 restarting runtime.&#8221;) print(&#8221; Just re-run the cell(s) after Colab reconnects.&#8221;) os.kill(os.getpid(), 9) print(&#8221; \u2026install finished.n&#8221;) import torch We begin by defining the runtime controls that decide how many invoices we process, whether we use 4-bit loading, whether we preview the generated PDF, and whether we later test a real invoice. We install the core dependencies for PDF generation, rendering, tabular analysis, plotting, and lift-pdf inference. We also pin Pillow to a stable version because the tutorial addresses a known Colab compatibility issue among Pillow, torchvision, and Transformers. This setup gives us a reproducible environment before we load any model or generate any document. Copy CodeCopiedUse a different Browser def detect_gpu(): if not torch.cuda.is_available(): raise SystemExit( &#8220;n\u2717 No CUDA GPU found. In Colab: Runtime \u25b8 Change runtime type \u25b8 GPU &#8221; &#8220;(A100 is best; L4\/T4 also work).n&#8221; ) p = torch.cuda.get_device_properties(0) cc = torch.cuda.get_device_capability(0) return p.name, p.total_memory \/ 1e9, cc def enable_4bit(compute_dtype): &#8220;&#8221;&#8221;Load lift&#8217;s weights in 4-bit NF4 whatever transformers Auto* class it uses internally.&#8221;&#8221;&#8221; import inspect, functools, transformers from transformers import BitsAndBytesConfig bnb = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type=&#8221;nf4&#8243;, bnb_4bit_use_double_quant=True, bnb_4bit_compute_dtype=compute_dtype, ) def patch(cls): try: cm = inspect.getattr_static(cls, &#8220;from_pretrained&#8221;) orig = cm.__func__ if isinstance(cm, (classmethod, staticmethod)) else cm except Exception: return @functools.wraps(orig) def inner(cls_, *args, **kwargs): kwargs.setdefault(&#8220;quantization_config&#8221;, bnb) kwargs.setdefault(&#8220;device_map&#8221;, {&#8220;&#8221;: 0}) model = orig(cls_, *args, **kwargs) try: model.to = lambda *a, **k: model model.cuda = lambda *a, **k: model except Exception: pass return model cls.from_pretrained = classmethod(inner) for name in [&#8220;AutoModelForImageTextToText&#8221;, &#8220;AutoModelForMultimodalLM&#8221;, &#8220;AutoModelForVision2Seq&#8221;, &#8220;AutoModelForCausalLM&#8221;, &#8220;AutoModel&#8221;]: c = getattr(transformers, name, None) if c is not None: patch(c) try: from transformers.modeling_utils import PreTrainedModel patch(PreTrainedModel) except Exception: pass print(&#8220;STEP 2\/7 \u00b7 Preparing the model backend\u2026&#8221;) gpu_name, vram, cc = detect_gpu() use_4bit = FORCE_4BIT or (vram &lt; 34 and not FORCE_FULL_PRECISION) compute_dtype = torch.bfloat16 if cc[0] &gt;= 8 else torch.float16 print(f&#8221; GPU: {gpu_name} | ~{vram:.0f} GB | compute capability {cc[0]}.{cc[1]}&#8221;) print(f&#8221; Load mode: {&#8216;4-bit NF4&#8217; if use_4bit else &#8216;full bf16&#8217;} (compute dtype {compute_dtype})&#8221;) os.environ.setdefault(&#8220;TORCH_DEVICE&#8221;, &#8220;cuda:0&#8221;) os.environ.setdefault(&#8220;MODEL_CHECKPOINT&#8221;, &#8220;datalab-to\/lift&#8221;) if use_4bit: enable_4bit(compute_dtype) from lift import extract from lift.model import InferenceManager print(&#8221; Loading lift weights (\u224820 GB download on first run)\u2026&#8221;) _t = time.time() MODEL = InferenceManager(method=&#8221;hf&#8221;) print(f&#8221; \u2713 model ready in {time.time() &#8211; _t:.0f}sn&#8221;) def run_lift(pdf_path, schema, page_range=None): kw = {&#8220;model&#8221;: MODEL} if page_range: kw[&#8220;page_range&#8221;] = page_range result = extract(pdf_path, schema, **kw) return getattr(result, &#8220;extraction&#8221;, None) We prepare the GPU-aware inference backend and decide whether the model should run in full precision or 4-bit NF4 quantization based on available VRAM. We patch the Hugging Face model-loading path so lift can transparently load the checkpoint with a BitsAndBytes quantization configuration when needed. We initialize the InferenceManager once and reuse it across all invoices, avoiding repeated model-loading overhead. Finally, we wrap lift.extract() inside a small helper so each PDF can be mined with the same schema and optional page range. Copy CodeCopiedUse a different Browser DOCS = [ dict( invoice_number=&#8221;INV-2026-0412&#8243;, invoice_date=&#8221;2026-05-04&#8243;, due_date=&#8221;2026-06-03&#8243;, vendor_name=&#8221;Cloudworks Inc.&#8221;, vendor_address=&#8221;500 Market St, Suite 900, San Francisco, CA 94105, USA&#8221;, bill_to_name=&#8221;Acme Robotics LLC&#8221;, bill_to_address=&#8221;12 Foundry Rd, Pittsburgh, PA 15222, USA&#8221;, ship_to_name=&#8221;Acme Robotics \u2014 Warehouse 4&#8243;, ship_to_address=&#8221;88 Dockside Blvd, Newark, NJ 07114, USA&#8221;, po_number=None, discount_amount=None, currency_code=&#8221;USD&#8221;, currency_symbol=&#8221;$&#8221;, tax_rate=0.085, amount_paid=0.00, line_items=[ (&#8220;Cloud Compute \u2014 Standard tier (monthly)&#8221;, 3, 240.00), (&#8220;Object Storage \u2014 2 TB&#8221;, 1, 46.00), (&#8220;Priority Support add-on&#8221;, 1, 99.00), ], notes=&#8221;Payment due within 30 days. Late payments accrue 1.5% monthly interest.&#8221;, ), dict( invoice_number=&#8221;INV-ND-2026-118&#8243;, invoice_date=&#8221;2026-04-18&#8243;, due_date=&#8221;2026-05-18&#8243;, vendor_name=&#8221;Nordic Design Studio Oy&#8221;, vendor_address=&#8221;Etel\u00e4ranta 12, 00130 Helsinki, Finland&#8221;, bill_to_name=&#8221;Helsinki Media Oy&#8221;, bill_to_address=&#8221;Mannerheimintie 4, 00100 Helsinki, Finland&#8221;, ship_to_name=None, ship_to_address=None, po_number=&#8221;PO-HM-5589&#8243;, discount_amount=785.00, currency_code=&#8221;EUR&#8221;, currency_symbol=&#8221;\u20ac&#8221;, tax_rate=0.24, amount_paid=8760.60, line_items=[ (&#8220;Brand identity design package&#8221;, 1, 4200.00), (&#8220;Web UI design \u2014 12 screens&#8221;, 12, 180.00), (&#8220;Custom illustration set&#8221;, 1, 850.00), (&#8220;Design-system documentation&#8221;, 1, 640.00), ], notes=&#8221;Paid in full \u2014 thank you. All amounts in EUR.&#8221;, ), dict( invoice_number=&#8221;INV-BR-4471&#8243;, invoice_date=&#8221;2026-06-01&#8243;, due_date=&#8221;2026-07-15&#8243;, vendor_name=&#8221;BuildRight Contractors Inc.&#8221;, vendor_address=&#8221;740 Industrial Way, Austin, TX 78744, USA&#8221;, bill_to_name=&#8221;Sunrise Property Group&#8221;, bill_to_address=&#8221;9 Lakeview Terrace, Austin, TX 78703, USA&#8221;, ship_to_name=&#8221;Sunrise Property Group \u2014 Lot 14 site office&#8221;, ship_to_address=&#8221;Parcel 14, Mesa Ridge Development, Austin, TX 78737, USA&#8221;, po_number=&#8221;PO-SPG-2211&#8243;, discount_amount=None, currency_code=&#8221;USD&#8221;, currency_symbol=&#8221;$&#8221;, tax_rate=0.07, amount_paid=15000.00, line_items=[ (&#8220;Site preparation and grading&#8221;, 1, 18500.00), (&#8220;Foundation concrete pour (Phase 1)&#8221;, 1, 27400.00), ], notes=&#8221;A 15,000 USD deposit has been received. Remaining balance due by the date above.&#8221;, ), ][:N_DOCS] def compute(d): &#8220;&#8221;&#8221;Derive every money figure once, so PDF text and ground truth are guaranteed identical.&#8221;&#8221;&#8221; items = [(desc, q,<\/p>","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pmpro_default_level":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_pvb_checkbox_block_on_post":false,"footnotes":""},"categories":[52,5,7,1],"tags":[],"class_list":["post-101873","post","type-post","status-publish","format-standard","hentry","category-ai-club","category-committee","category-news","category-uncategorized","pmpro-has-access"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation - YouZum<\/title>\n<meta name=\"description\" content=\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/youzum.net\/fr\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation - YouZum\" \/>\n<meta property=\"og:description\" content=\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\" \/>\n<meta property=\"og:url\" content=\"https:\/\/youzum.net\/fr\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/\" \/>\n<meta property=\"og:site_name\" content=\"YouZum\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/DroneAssociationTH\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-07-04T18:50:17+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png\" \/>\n<meta name=\"author\" content=\"admin NU\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u00c9crit par\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin NU\" \/>\n\t<meta name=\"twitter:label2\" content=\"Dur\u00e9e de lecture estim\u00e9e\" \/>\n\t<meta name=\"twitter:data2\" content=\"20 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/\"},\"author\":{\"name\":\"admin NU\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c\"},\"headline\":\"Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation\",\"datePublished\":\"2026-07-04T18:50:17+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/\"},\"wordCount\":1004,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\"},\"image\":{\"@id\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png\",\"articleSection\":[\"AI\",\"Committee\",\"News\",\"Uncategorized\"],\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/\",\"url\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/\",\"name\":\"Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation - YouZum\",\"isPartOf\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png\",\"datePublished\":\"2026-07-04T18:50:17+00:00\",\"description\":\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\",\"breadcrumb\":{\"@id\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#primaryimage\",\"url\":\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png\",\"contentUrl\":\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/youzum.net\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/yousum.gpucore.co\/#website\",\"url\":\"https:\/\/yousum.gpucore.co\/\",\"name\":\"YouSum\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/yousum.gpucore.co\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"fr-FR\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\",\"name\":\"Drone Association Thailand\",\"url\":\"https:\/\/yousum.gpucore.co\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png\",\"contentUrl\":\"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png\",\"width\":300,\"height\":300,\"caption\":\"Drone Association Thailand\"},\"image\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/DroneAssociationTH\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c\",\"name\":\"admin NU\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png\",\"contentUrl\":\"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png\",\"caption\":\"admin NU\"},\"url\":\"https:\/\/youzum.net\/fr\/members\/adminnu\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation - YouZum","description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/youzum.net\/fr\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/","og_locale":"fr_FR","og_type":"article","og_title":"Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation - YouZum","og_description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","og_url":"https:\/\/youzum.net\/fr\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/","og_site_name":"YouZum","article_publisher":"https:\/\/www.facebook.com\/DroneAssociationTH\/","article_published_time":"2026-07-04T18:50:17+00:00","og_image":[{"url":"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png","type":"","width":"","height":""}],"author":"admin NU","twitter_card":"summary_large_image","twitter_misc":{"\u00c9crit par":"admin NU","Dur\u00e9e de lecture estim\u00e9e":"20 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#article","isPartOf":{"@id":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/"},"author":{"name":"admin NU","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c"},"headline":"Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation","datePublished":"2026-07-04T18:50:17+00:00","mainEntityOfPage":{"@id":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/"},"wordCount":1004,"commentCount":0,"publisher":{"@id":"https:\/\/yousum.gpucore.co\/#organization"},"image":{"@id":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#primaryimage"},"thumbnailUrl":"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png","articleSection":["AI","Committee","News","Uncategorized"],"inLanguage":"fr-FR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/","url":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/","name":"Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation - YouZum","isPartOf":{"@id":"https:\/\/yousum.gpucore.co\/#website"},"primaryImageOfPage":{"@id":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#primaryimage"},"image":{"@id":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#primaryimage"},"thumbnailUrl":"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png","datePublished":"2026-07-04T18:50:17+00:00","description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","breadcrumb":{"@id":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/"]}]},{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#primaryimage","url":"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png","contentUrl":"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f389.png"},{"@type":"BreadcrumbList","@id":"https:\/\/youzum.net\/designing-a-schema-guided-invoice-intelligence-pipeline-with-lift-pdf-for-accounts-payable-extraction-validation-and-ledger-generation\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/youzum.net\/"},{"@type":"ListItem","position":2,"name":"Designing a Schema-Guided Invoice Intelligence Pipeline with lift-pdf for Accounts-Payable Extraction, Validation, and Ledger Generation"}]},{"@type":"WebSite","@id":"https:\/\/yousum.gpucore.co\/#website","url":"https:\/\/yousum.gpucore.co\/","name":"YouSum","description":"","publisher":{"@id":"https:\/\/yousum.gpucore.co\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/yousum.gpucore.co\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"fr-FR"},{"@type":"Organization","@id":"https:\/\/yousum.gpucore.co\/#organization","name":"Drone Association Thailand","url":"https:\/\/yousum.gpucore.co\/","logo":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/","url":"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png","contentUrl":"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png","width":300,"height":300,"caption":"Drone Association Thailand"},"image":{"@id":"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/DroneAssociationTH\/"]},{"@type":"Person","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c","name":"admin NU","image":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/image\/","url":"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png","contentUrl":"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png","caption":"admin NU"},"url":"https:\/\/youzum.net\/fr\/members\/adminnu\/"}]}},"rttpg_featured_image_url":null,"rttpg_author":{"display_name":"admin NU","author_link":"https:\/\/youzum.net\/fr\/members\/adminnu\/"},"rttpg_comment":0,"rttpg_category":"<a href=\"https:\/\/youzum.net\/fr\/category\/ai-club\/\" rel=\"category tag\">AI<\/a> <a href=\"https:\/\/youzum.net\/fr\/category\/committee\/\" rel=\"category tag\">Committee<\/a> <a href=\"https:\/\/youzum.net\/fr\/category\/news\/\" rel=\"category tag\">News<\/a> <a href=\"https:\/\/youzum.net\/fr\/category\/uncategorized\/\" rel=\"category tag\">Uncategorized<\/a>","rttpg_excerpt":"In this tutorial, we build an end-to-end accounts-payable extraction pipeline with lift-pdf, using synthetic invoice PDFs as controlled test documents and a structured JSON schema as the target output format. Instead of treating invoice parsing as a simple OCR task, we frame it as schema-guided document understanding: we generate realistic invoices, define fields such as\u2026","_links":{"self":[{"href":"https:\/\/youzum.net\/fr\/wp-json\/wp\/v2\/posts\/101873","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/youzum.net\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/youzum.net\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/youzum.net\/fr\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/youzum.net\/fr\/wp-json\/wp\/v2\/comments?post=101873"}],"version-history":[{"count":0,"href":"https:\/\/youzum.net\/fr\/wp-json\/wp\/v2\/posts\/101873\/revisions"}],"wp:attachment":[{"href":"https:\/\/youzum.net\/fr\/wp-json\/wp\/v2\/media?parent=101873"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/youzum.net\/fr\/wp-json\/wp\/v2\/categories?post=101873"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/youzum.net\/fr\/wp-json\/wp\/v2\/tags?post=101873"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}