<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Larry</title>
    <description>The latest articles on DEV Community by Larry (@larry_d5d17a458878a2edc21).</description>
    <link>https://dev.to/larry_d5d17a458878a2edc21</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4196274%2Fdb0a81ca-6dc5-4322-90d7-2720c150581e.png</url>
      <title>DEV Community: Larry</title>
      <link>https://dev.to/larry_d5d17a458878a2edc21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/larry_d5d17a458878a2edc21"/>
    <language>en</language>
    <item>
      <title>Turning a bank statement PDF into a spreadsheet with a vision model</title>
      <dc:creator>Larry</dc:creator>
      <pubDate>Tue, 20 Oct 2026 14:16:33 +0000</pubDate>
      <link>https://dev.to/larry_d5d17a458878a2edc21/turning-a-bank-statement-pdf-into-a-spreadsheet-with-a-vision-model-3fcp</link>
      <guid>https://dev.to/larry_d5d17a458878a2edc21/turning-a-bank-statement-pdf-into-a-spreadsheet-with-a-vision-model-3fcp</guid>
      <description>&lt;p&gt;Bank statement PDFs are deceptively hard to parse. You'd think it's a&lt;br&gt;
table, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single transaction "description" frequently wraps across several
lines, so you can't just split on newlines.&lt;/li&gt;
&lt;li&gt;Debits and credits flip sign depending on the bank.&lt;/li&gt;
&lt;li&gt;Scanned statements have no text layer at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I didn't try to parse the PDF structure directly. Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Render each page to an image with mupdf compiled to WASM.&lt;/li&gt;
&lt;li&gt;Send the image to a vision model with a strict JSON schema:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;{"bankName": ..., "accountNumber": ..., "summary": [...],&lt;br&gt;
    "transactions": [{"date": "...", "description": "...",&lt;br&gt;
    "amount": 0}]}&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask for negative amounts on debits and positive on credits.&lt;/li&gt;
&lt;li&gt;Stream rows back over SSE so the UI fills in live.&lt;/li&gt;
&lt;li&gt;Reassemble and export (xlsx, CSV, and plain-text OFX/QBO/QIF for
QuickBooks and Xero).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The things that actually moved the needle on accuracy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separate prompts: a "header + summary + transactions" prompt for
page one, a transactions-only prompt for later pages.&lt;/li&gt;
&lt;li&gt;Telling the model "return only JSON, no markdown".&lt;/li&gt;
&lt;li&gt;Explicit sign conventions instead of relying on the model's guess.&lt;/li&gt;
&lt;li&gt;Generating a deterministic FITID for QBO/OFC so re-imports don't
duplicate entries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's live and free at statementexcelconverter.com. The parts that&lt;br&gt;
still need work: hand-annotated adjustments (paper statements with&lt;br&gt;
notes), and non-Latin statement layouts.&lt;/p&gt;

&lt;p&gt;Happy to share more detail or the prompt structure if useful.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>data</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
