<?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: cleanstmt</title>
    <description>The latest articles on DEV Community by cleanstmt (@cleanstmt).</description>
    <link>https://dev.to/cleanstmt</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%2F4017093%2Fb32c539b-bb5f-4203-a329-1fef3691775d.png</url>
      <title>DEV Community: cleanstmt</title>
      <link>https://dev.to/cleanstmt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cleanstmt"/>
    <language>en</language>
    <item>
      <title>What AI tool can I use to make videos like the Tiptoeing in My Jordans meme?</title>
      <dc:creator>cleanstmt</dc:creator>
      <pubDate>Fri, 23 Oct 2026 09:55:05 +0000</pubDate>
      <link>https://dev.to/cleanstmt/what-ai-tool-can-i-use-to-make-videos-like-the-tiptoeing-in-my-jordans-meme-1hkp</link>
      <guid>https://dev.to/cleanstmt/what-ai-tool-can-i-use-to-make-videos-like-the-tiptoeing-in-my-jordans-meme-1hkp</guid>
      <description>&lt;p&gt;If your feed has been full of a creepy little man in a barn tiptoeing to a 2014 Riff Raff song, you are not alone. The "Tiptoeing in My Jordans" clip has escaped the usual meme cycle and turned into a small industry: edits, Halloween costume suggestions, an NFL touchdown celebration, and a wave of people asking how to put themselves or their friends into the scene.&lt;/p&gt;

&lt;p&gt;Short answer: yes, you can make one with AI. The longer answer is that &lt;em&gt;how&lt;/em&gt; you make it decides whether it takes ten minutes or ten failed prompt attempts. Here is what the meme actually is, what makes the clip work shot by shot, and three practical ways to make your own version.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I build Zombie Video Lab, one of the tools linked below. I have tried to keep the comparison honest, including what the tool does &lt;em&gt;not&lt;/em&gt; do.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is the "Tiptoeing in My Jordans" meme?
&lt;/h2&gt;

&lt;p&gt;It is not a lost scene from an 1980s fantasy movie, even though a lot of people were convinced it was. The clip is AI-generated.&lt;/p&gt;

&lt;p&gt;The original video was posted in late August 2026 by the Instagram accounts &lt;strong&gt;Stroinaya&lt;/strong&gt;, &lt;strong&gt;Neuroferma&lt;/strong&gt;, and &lt;strong&gt;Anna Glinskaya&lt;/strong&gt; as a demo of what AI video could do. It shows a Rumpelstiltskin-like imp in a straw-filled wooden barn: he shushes a crying woman, does an absurd tiptoe dance, spins straw into gold, and is eventually transformed into a prince by her kiss. The vintage film grain and fairy-tale lighting made it feel like a real VHS discovery, which is exactly why so many viewers experienced a Mandela-effect moment.&lt;/p&gt;

&lt;p&gt;The meme ignition happened on &lt;strong&gt;September 29, 2026&lt;/strong&gt;, when TikToker &lt;strong&gt;@6aimbot9&lt;/strong&gt; paired the clip with Riff Raff's 2014 track &lt;strong&gt;"Tip Toe Wing In My Jawwdinz"&lt;/strong&gt; and the caption &lt;em&gt;"The type of dreams I be having after taking dollar store melatonin."&lt;/em&gt; The song's &lt;em&gt;"Jawwdinz"&lt;/em&gt; got heard as &lt;em&gt;"Jordans,"&lt;/em&gt; and the name stuck.&lt;/p&gt;

&lt;p&gt;From there it spread across TikTok, X, and Instagram Reels. People replaced the imp with Wednesday Addams, Ronald McDonald, Sam and Dean from &lt;em&gt;Supernatural&lt;/em&gt;, streamers, and mascots. Jacksonville Jaguars running back &lt;strong&gt;Bhayshul Tuten&lt;/strong&gt; celebrated a touchdown by recreating the dance. The character is now less a specific meme than a template for "someone doing something ridiculous while everyone else watches in disbelief."&lt;/p&gt;

&lt;p&gt;Sources if you want the full lore: &lt;a href="https://knowyourmeme.com/memes/ai-rumpelstiltskin-tip-toein-in-my-jordans" rel="noopener noreferrer"&gt;Know Your Meme's explainer&lt;/a&gt; and &lt;a href="https://tech.yahoo.com/social-media/article/the-internets-favorite-dancing-rumpelstiltskin-meme-has-a-surprising-origin-193811856.html" rel="noopener noreferrer"&gt;Yahoo's origin write-up&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes the clip work (and what a generator has to get right)
&lt;/h2&gt;

&lt;p&gt;The meme is not just "a weird dance." It is a tiny, tightly edited comedy scene. If you look at the structure, it reads like a shot list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Beat&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0–2s&lt;/td&gt;
&lt;td&gt;The dancer shushes the seated observer, finger to lips, mischievous eye contact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2–4s&lt;/td&gt;
&lt;td&gt;A knowing grin; the observer starts to look surprised&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4–6s&lt;/td&gt;
&lt;td&gt;The dancer bends the knees, extends both arms like wings, feet visible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6–9s&lt;/td&gt;
&lt;td&gt;The signature tiptoe: heels lift, weight shifts left-right on the forefeet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9–12s&lt;/td&gt;
&lt;td&gt;Low-angle close-up of the footwork on the hay-covered floor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12–14s&lt;/td&gt;
&lt;td&gt;Back to a full shot: same dance, same outfit, same expression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14–15s&lt;/td&gt;
&lt;td&gt;The observer's exaggerated reaction — eyes wide, hand to mouth&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three things make it land:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The footwork is specific.&lt;/strong&gt; It has to read as alternating heel lifts and quick forefoot steps, not jumping, marching, or a generic dance loop. This is the hardest part for AI models; feet are where most generations fall apart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The reaction sells the joke.&lt;/strong&gt; The dancer's confidence only works if the observer reacts. Two characters, two roles, no swapping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The visual language is doing half the work.&lt;/strong&gt; Warm amber light, weathered wood, hay, dust, gentle film grain. It looks like a lost artifact, not a TikTok studio.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That structure is also what any prompt needs: role assignment, an ordered storyboard, a dedicated footwork shot, and a consistent identity across cuts. "Make a funny dance video" will not get you there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to make your own version
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Prompt a general-purpose video model
&lt;/h3&gt;

&lt;p&gt;If you already have a paid AI video tool, you can feed it a reference clip plus two portraits and write a detailed prompt covering choreography, shot order, camera angles, and duration.&lt;/p&gt;

&lt;p&gt;The catch is that you are now a prompt engineer and a video editor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need a reference video you are allowed to use.&lt;/li&gt;
&lt;li&gt;You need to describe timing, framing, and motion precisely enough that the model does not turn the dance into walking.&lt;/li&gt;
&lt;li&gt;Face identity often drifts between shots, and feet are a common failure point.&lt;/li&gt;
&lt;li&gt;You still need to handle music/add a track separately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the right path if you want maximum control and enjoy iterating. It is the wrong path if you just want a clip for this weekend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: CapCut template or green-screen overlay
&lt;/h3&gt;

&lt;p&gt;There are CapCut templates that drop your face onto the original dance clip. It is fast and free-ish, but the result is an overlay, not a new scene. Your person is pasted into existing footage, so lighting, angle, and motion rarely match. It also puts you on the wrong side of the meme's own "is this real?" charm: everyone can tell it is a template.&lt;/p&gt;

&lt;p&gt;Good for a quick joke, not for a personalized version of the scene.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Use a prepared photo-to-video template
&lt;/h3&gt;

&lt;p&gt;This is the middle path: someone has already turned the shot list above into a fixed template, and you supply only the two people. That is exactly what we built at Zombie Video Lab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.zombievideolab.com/video/tiptoeing-in-my-jordans" rel="noopener noreferrer"&gt;&lt;strong&gt;Tiptoeing In My Jordans AI Video Generator →&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open the generator and watch both previews.&lt;/strong&gt; The page shows the original reference (muted by default) and a separately labeled real AI result, so you can see the actual output rather than a mockup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload the dancer first.&lt;/strong&gt; Use one clear, well-lit, consenting adult portrait. This person performs the footwork, regardless of gender.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload the observer second.&lt;/strong&gt; This person stays seated and reacts. Upload order defines the roles, not a gender setting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose 480p or 720p, then 9:16 or 16:9.&lt;/strong&gt; Portrait is the natural fit for TikTok and Reels; landscape gives both characters more horizontal space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay once.&lt;/strong&gt; At the time of writing, the live prices are &lt;strong&gt;$5.99 for 480p&lt;/strong&gt; and &lt;strong&gt;$8.99 for 720p&lt;/strong&gt; per 15-second video. There is no subscription, no credit bundle, and no account registration. Check the checkout total before paying; prices can change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wait for the render, then download the MP4.&lt;/strong&gt; Generation is not instant because it depends on the provider queue. Keep the video code shown by the tool. When the video is ready, play it and save the MP4 to your device within three days.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What you get is a new 15-second AI-generated scene using your two people, with a prepared storyboard covering the shush, the grin, the tiptoe footwork, the foot close-up, and the observer reaction. New generations also request AI-created instrumental music guided by the reference's tempo and mood, so the steps and cuts have something to move to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this tool does not do
&lt;/h2&gt;

&lt;p&gt;This matters more than the feature list, because most disappointment with AI video comes from mismatched expectations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is not a frame-for-frame copy.&lt;/strong&gt; It is a trend-inspired recreation. Faces, clothing details, footwear, and exact choreography can differ from both your photos and the original clip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not include the original Riff Raff song.&lt;/strong&gt; New generations get AI-created instrumental music inspired by the groove, not a licensed soundtrack. Exact melodies and beat matching are not guaranteed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;480p and 720p only.&lt;/strong&gt; No 2K/4K option, and no free personalized generation. The published examples are free to watch; your own video is paid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporary delivery.&lt;/strong&gt; Download within three days of completion. The browser remembers your order references, but this is not permanent cloud storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI artifacts happen.&lt;/strong&gt; Extra fingers, sliding feet, or identity drift are still possible. Clear, well-lit photos reduce the risk but do not eliminate it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need frame-accurate editing, a specific song, or perfect beat synchronization, a fixed-template generator is not the right tool. Use an editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for a version that actually lands
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pick photos that show the shoes.&lt;/strong&gt; The footwork close-up is a core beat. A cropped selfie gives the model less to work with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One adult per photo, clear face, no group shots.&lt;/strong&gt; The template needs to assign each face to one role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the observer's reaction.&lt;/strong&gt; A friend who will pull a genuinely surprised face makes the final second funnier than any prompt tweak.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go 9:16 for TikTok.&lt;/strong&gt; Vertical framing keeps the dancer's head and feet in shot on a phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Label it as AI.&lt;/strong&gt; Follow the destination platform's AI-disclosure rules, and do not present the clip as a real event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get consent before uploading anyone.&lt;/strong&gt; Use only photos you have permission to process.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need to write a prompt?&lt;/strong&gt; No. The template supplies the storyboard and motion reference. You upload two photos and pick a resolution and aspect ratio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it free?&lt;/strong&gt; Watching the examples is free. Creating your own video is a one-time paid generation. There is no subscription trial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I post the result on TikTok or Reels?&lt;/strong&gt; Yes, you can download the MP4 and share it from your own account, provided you have the necessary photo and likeness rights and follow the platform's AI and music rules. Reach and approval are not guaranteed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which photo dances?&lt;/strong&gt; The first upload is always the dancer; the second is the seated observer. Swap the order before checkout to reverse the roles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it copy the original audio?&lt;/strong&gt; No. It requests new instrumental music in a similar tempo and mood. The original track is not included or licensed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long is the video?&lt;/strong&gt; 15 seconds, in either resolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if generation fails after payment?&lt;/strong&gt; A confirmed failure may qualify for a retry on the same paid order, subject to the retry allowance. Do not pay again while the status is uncertain — contact support with your video code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If the goal is "I want my own version of this meme today, without writing a prompt or learning a video editor," the prepared template is the shortest path:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.zombievideolab.com/video/tiptoeing-in-my-jordans" rel="noopener noreferrer"&gt;Make a Tiptoeing In My Jordans video from two photos →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Watch the reference and the real result first, decide who dances and who reacts, then check the current one-time price. No account, no subscription, no prompt. And if you build your own version with a general video model instead, the shot list above is the part worth stealing.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Reliable Pipeline for Extracting Transaction Data from Financial PDFs</title>
      <dc:creator>cleanstmt</dc:creator>
      <pubDate>Mon, 31 Aug 2026 08:45:49 +0000</pubDate>
      <link>https://dev.to/cleanstmt/building-a-reliable-pipeline-for-extracting-transaction-data-from-financial-pdfs-d0f</link>
      <guid>https://dev.to/cleanstmt/building-a-reliable-pipeline-for-extracting-transaction-data-from-financial-pdfs-d0f</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqac57772ccy8znsg9wp3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqac57772ccy8znsg9wp3.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;br&gt;
Financial documents look structured to humans, but they are often surprisingly difficult for software to process.&lt;/p&gt;

&lt;p&gt;A bank statement may appear to contain a simple table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Debit&lt;/th&gt;
&lt;th&gt;Credit&lt;/th&gt;
&lt;th&gt;Balance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01/15/2024&lt;/td&gt;
&lt;td&gt;Office supplies&lt;/td&gt;
&lt;td&gt;89.99&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;4,120.50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;But internally, the PDF may contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text positioned independently on a page&lt;/li&gt;
&lt;li&gt;Separate text fragments for each character&lt;/li&gt;
&lt;li&gt;Wrapped descriptions across multiple lines&lt;/li&gt;
&lt;li&gt;Different layouts for each bank&lt;/li&gt;
&lt;li&gt;Headers repeated on every page&lt;/li&gt;
&lt;li&gt;Summary blocks mixed with transaction rows&lt;/li&gt;
&lt;li&gt;Scanned pages with no text layer at all&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the goal is to export the data into Excel or accounting software, simply extracting visible text is not enough.&lt;/p&gt;

&lt;p&gt;This article explains the main engineering challenges and a practical architecture for building a more reliable financial document extraction pipeline.&lt;/p&gt;
&lt;h2&gt;
  
  
  The real problem is structure, not text
&lt;/h2&gt;

&lt;p&gt;A basic PDF text extractor can often return something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;01/15/2024 Office supplies 89.99 4,120.50
01/16/2024 Wire transfer 5,000.00 9,120.50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That output is readable, but it does not tell us with certainty:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which value belongs to which column&lt;/li&gt;
&lt;li&gt;Whether &lt;code&gt;89.99&lt;/code&gt; is a debit or a credit&lt;/li&gt;
&lt;li&gt;Whether the balance belongs to the same transaction&lt;/li&gt;
&lt;li&gt;Whether a line is a transaction or a summary value&lt;/li&gt;
&lt;li&gt;Whether a wrapped line is a new transaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The extraction problem is therefore better represented as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document
  -&amp;gt; page structure
  -&amp;gt; table boundaries
  -&amp;gt; transaction rows
  -&amp;gt; normalized fields
  -&amp;gt; validated output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important design decision is to separate these stages instead of asking one parser to solve everything at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical extraction pipeline
&lt;/h2&gt;

&lt;p&gt;A production-oriented pipeline can use the following stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Detect document type
2. Extract the text layer when available
3. Fall back to OCR for scanned pages
4. Identify the main transaction table
5. Reconstruct rows and columns
6. Normalize dates and amounts
7. Validate balances and totals
8. Export to the target format
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage has a different failure mode, which makes debugging and testing much easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Detect whether the PDF has a text layer
&lt;/h2&gt;

&lt;p&gt;Not every PDF needs OCR.&lt;/p&gt;

&lt;p&gt;There are two common types:&lt;/p&gt;

&lt;h3&gt;
  
  
  Native PDF
&lt;/h3&gt;

&lt;p&gt;The document was generated digitally. It usually contains selectable text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bank statement generated by online banking
Invoice exported from accounting software
Digital receipt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For these files, direct text extraction is usually faster and cheaper than OCR.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scanned PDF
&lt;/h3&gt;

&lt;p&gt;The document is effectively a collection of images. Selecting text does not work because there is no usable text layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanned paper statement
Photographed receipt
Faxed document
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These files require OCR before table extraction can begin.&lt;/p&gt;

&lt;p&gt;A simplified TypeScript decision function might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PdfInspection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;pageCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;textLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;hasTextLayer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shouldUseOcr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inspection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PdfInspection&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;inspection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasTextLayer&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;inspection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textLength&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact threshold depends on the document type. The important point is to make the decision explicitly rather than assuming every PDF can be handled the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Use different prompts for different input types
&lt;/h2&gt;

&lt;p&gt;A scanned image and a native PDF should not necessarily use the same extraction instructions.&lt;/p&gt;

&lt;p&gt;For an image-based document, the model must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the visual layout&lt;/li&gt;
&lt;li&gt;Locate table boundaries&lt;/li&gt;
&lt;li&gt;Interpret column alignment&lt;/li&gt;
&lt;li&gt;Recognize low-quality characters&lt;/li&gt;
&lt;li&gt;Reconstruct multi-line rows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a native PDF converted to text, the model should focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identifying the transaction table&lt;/li&gt;
&lt;li&gt;Ignoring page headers and footers&lt;/li&gt;
&lt;li&gt;Ignoring account summaries&lt;/li&gt;
&lt;li&gt;Merging continuation pages&lt;/li&gt;
&lt;li&gt;Preserving the original values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful text-extraction instruction is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract only the main transaction table.

Ignore:
- account holder information
- branch addresses
- account activity summaries
- daily balance summaries
- check listings
- loan information
- legal notices
- repeated page headers and footers

If the table continues on another page, merge the rows into one array.
Do not infer missing values.
Return an empty string when a value cannot be read confidently.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is more reliable than asking for “all text on the page,” because financial statements often contain several different tables with unrelated totals.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use a stable intermediate schema
&lt;/h2&gt;

&lt;p&gt;Before exporting to Excel, QBO, or another accounting format, normalize the extraction result into a stable internal schema.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtractedDocument&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[][];&lt;/span&gt;
  &lt;span class="nl"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original columns should be preserved initially. Normalization should happen in a separate step.&lt;/p&gt;

&lt;p&gt;This is useful because different banks use different labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Withdrawal
Debit
Money Out
Payment
Outflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These may all represent a similar concept, but they should not be blindly merged before the original data has been preserved.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Reconstruct wrapped descriptions carefully
&lt;/h2&gt;

&lt;p&gt;Descriptions frequently wrap to a second line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;07/18/2024  ACME RETAIL SERVICES
            MONTHLY BUSINESS SUBSCRIPTION     49.00  2,811.42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A naïve line-based parser may treat this as two transactions.&lt;/p&gt;

&lt;p&gt;A better approach is to detect whether a line begins with a date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dateAtStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{1,2}[/&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;]\d{1,2}[/&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;]\d{2,4}\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isNewTransactionLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dateAtStart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a line does not begin with a date, it may be a continuation of the previous description.&lt;/p&gt;

&lt;p&gt;However, this is only a heuristic. Some statements use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Posting dates without transaction dates&lt;/li&gt;
&lt;li&gt;Transaction IDs at the beginning&lt;/li&gt;
&lt;li&gt;Dates in separate columns&lt;/li&gt;
&lt;li&gt;Non-date transaction identifiers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For that reason, the parser should preserve the original extracted rows and allow the user to review the result before export.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Normalize amounts without changing their meaning
&lt;/h2&gt;

&lt;p&gt;Money values may appear as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$1,234.56
(89.99)
-89.99
89.99 CR
89.99 DR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A normalization function should remove presentation characters while preserving the sign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isParenthesized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isCredit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;CR&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDebit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;DR&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numeric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;raw&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;,$£€¥&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;()&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(&lt;/span&gt;&lt;span class="sr"&gt;CR|DR&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isParenthesized&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;isDebit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isCredit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser should not “correct” suspicious amounts automatically.&lt;/p&gt;

&lt;p&gt;For example, if the source contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$1O0.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where the character could be either &lt;code&gt;O&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt;, silently converting it to &lt;code&gt;$100.00&lt;/code&gt; creates false confidence. It is safer to leave the value empty or flag it for review.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Validate running balances
&lt;/h2&gt;

&lt;p&gt;A running balance provides a useful consistency check.&lt;/p&gt;

&lt;p&gt;If a statement has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Opening balance: 1,000.00
Credit:           250.00
Debit:             80.00
Expected ending: 1,170.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the final extracted balance should be approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,170.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A basic validation function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;expectedEndingBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;opening&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;credits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;debits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;opening&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;credits&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;debits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;amountsMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tolerance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This check does not prove that the extraction is correct. It only identifies cases that deserve review.&lt;/p&gt;

&lt;p&gt;There are several reasons why a summary total may not match the visible transaction table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The summary covers a different period&lt;/li&gt;
&lt;li&gt;Check transactions are listed in a separate section&lt;/li&gt;
&lt;li&gt;The statement contains multiple account sections&lt;/li&gt;
&lt;li&gt;Some pages were excluded intentionally&lt;/li&gt;
&lt;li&gt;The PDF contains beginning and ending balance adjustments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, a mismatch should normally produce a review note, not an automatic rejection.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Keep validation warnings close to the risky action
&lt;/h2&gt;

&lt;p&gt;Displaying every warning immediately after extraction can confuse users.&lt;/p&gt;

&lt;p&gt;A user exporting to Excel may not need a warning that only matters for a Xero import.&lt;/p&gt;

&lt;p&gt;A better user experience is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;General extraction result:
- Show the extracted rows
- Let the user review and edit them
- Avoid unrelated warnings

Xero export:
- Show date-format and accounting import notes
- Ask for confirmation if needed

Sage export:
- Show Sage-specific import notes
- Keep the CSV itself clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an important product design principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Show a warning at the point where the user can act on it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Warnings should also be kept out of machine-readable CSV files. Adding explanatory text before the CSV header may cause an accounting application to interpret the note as a transaction row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export should be a separate layer
&lt;/h2&gt;

&lt;p&gt;The normalized transaction data should not be coupled directly to one output format.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeExtractedRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;excelFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exportToExcel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csvFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exportToCsv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qboFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exportToQbo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xeroFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exportToXero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each serializer can then enforce its own rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Excel:
- Preserve readable formatting
- Keep numeric values as numbers

CSV:
- Use a stable header
- Escape commas and quotes

Xero:
- Normalize dates
- Map signed amounts correctly
- Use the expected columns

Sage 50 UK:
- Normalize DD/MM/YYYY dates
- Sanitize descriptions
- Respect field length limits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design makes it possible to improve one export format without changing the extraction engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built with CleanStmt
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://www.cleanstmt.com" rel="noopener noreferrer"&gt;CleanStmt&lt;/a&gt; around this type of workflow.&lt;/p&gt;

&lt;p&gt;It is a web-based tool for converting bank statements, credit card statements, invoices, receipts, and scanned financial documents into structured data.&lt;/p&gt;

&lt;p&gt;The parts I consider most important are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A separate path for native PDFs and scanned documents&lt;/li&gt;
&lt;li&gt;OCR instructions focused on financial tables&lt;/li&gt;
&lt;li&gt;One transaction per output row&lt;/li&gt;
&lt;li&gt;No merged cells in the spreadsheet output&lt;/li&gt;
&lt;li&gt;Reviewable extracted values&lt;/li&gt;
&lt;li&gt;Export options for Excel, CSV, QBO, QIF, OFX, Xero, Sage 50 UK, and other accounting workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool is free to try. There is also a Professional plan for users who process more documents. The current pricing is available at &lt;a href="https://www.cleanstmt.com/pricing" rel="noopener noreferrer"&gt;cleanstmt.com/pricing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical testing strategy
&lt;/h2&gt;

&lt;p&gt;A financial extraction pipeline should be tested with more than one clean sample.&lt;/p&gt;

&lt;p&gt;A useful test set includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Native digital bank statement
2. Scanned statement with low contrast
3. Multi-page statement
4. Statement with wrapped descriptions
5. Statement with separate debit and credit columns
6. Statement with one signed amount column
7. Credit card statement with fees and rewards sections
8. Invoice with line items and totals
9. Receipt photographed at an angle
10. Statement with ambiguous date formats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For every fixture, compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of extracted rows&lt;/li&gt;
&lt;li&gt;Date values&lt;/li&gt;
&lt;li&gt;Debit and credit totals&lt;/li&gt;
&lt;li&gt;Ending balance&lt;/li&gt;
&lt;li&gt;Exported column names&lt;/li&gt;
&lt;li&gt;Import compatibility&lt;/li&gt;
&lt;li&gt;Handling of uncertain values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal should not be to claim that every document is perfect. The goal is to make errors visible, recoverable, and easy to review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Financial document extraction is not just an OCR problem.&lt;/p&gt;

&lt;p&gt;It combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Document inspection&lt;/li&gt;
&lt;li&gt;Table reconstruction&lt;/li&gt;
&lt;li&gt;Semantic classification&lt;/li&gt;
&lt;li&gt;Amount normalization&lt;/li&gt;
&lt;li&gt;Balance validation&lt;/li&gt;
&lt;li&gt;User review&lt;/li&gt;
&lt;li&gt;Format-specific export rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most reliable systems are conservative. They preserve source values, avoid silently guessing, separate extraction from export, and show warnings only when they are relevant to the user's next action.&lt;/p&gt;

&lt;p&gt;That approach may produce slightly less “magical” marketing copy, but it creates a workflow that accounting users can actually trust.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Bank Statement to Excel: Why Generic PDF Converters Fail (and What to Do Instead)</title>
      <dc:creator>cleanstmt</dc:creator>
      <pubDate>Fri, 07 Aug 2026 09:46:01 +0000</pubDate>
      <link>https://dev.to/cleanstmt/bank-statement-to-excel-why-generic-pdf-converters-fail-and-what-to-do-instead-4mnk</link>
      <guid>https://dev.to/cleanstmt/bank-statement-to-excel-why-generic-pdf-converters-fail-and-what-to-do-instead-4mnk</guid>
      <description>&lt;p&gt;You export your bank statement PDF to Excel. You get a spreadsheet where every &lt;br&gt;
row is merged, amounts are in the wrong columns, and half the dates are missing.&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;p&gt;This isn't a bug in your PDF converter. It's working exactly as designed — &lt;br&gt;
and that's the problem.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why PDFs Are Not Spreadsheets
&lt;/h2&gt;

&lt;p&gt;A PDF is a visual format. It describes where ink goes on a page — coordinates, &lt;br&gt;
fonts, colors. It has no concept of "this is a table" or "these numbers are in &lt;br&gt;
the same column."&lt;/p&gt;

&lt;p&gt;When you open a bank statement PDF, you see a clean table with Date, &lt;br&gt;
Description, Amount, and Balance columns. But the underlying data looks &lt;br&gt;
more like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text "01/15" at position x=72, y=340
text "Amazon.com" at position x=120, y=340
text "-89.99" at position x=380, y=340
text "12,450.00" at position x=450, y=340
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generic PDF-to-Excel tools work by mapping these coordinates to spreadsheet &lt;br&gt;
cells. The problem: bank statement PDFs were designed for printing, not for &lt;br&gt;
data extraction. Column boundaries are inconsistent. Multi-line descriptions &lt;br&gt;
bleed into adjacent cells. Running totals share rows with transactions.&lt;/p&gt;

&lt;p&gt;The result: merged cells, broken columns, and data you can't use in formulas.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Generic" Actually Means
&lt;/h2&gt;

&lt;p&gt;Tools like Adobe Acrobat Export, Smallpdf, ILovePDF, and most online converters &lt;br&gt;
use the same underlying approach: coordinate-based text extraction. They're &lt;br&gt;
good at extracting text from simple documents. Financial statements are not &lt;br&gt;
simple documents.&lt;/p&gt;

&lt;p&gt;Specific failure modes you'll recognize:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merged cells&lt;/strong&gt; — Two or three text elements at similar Y coordinates get &lt;br&gt;
merged into one cell. Your pivot table breaks immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Split amounts&lt;/strong&gt; — "-1,234.56" becomes two cells: "-1,234" and ".56". &lt;br&gt;
VLOOKUP returns errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing rows&lt;/strong&gt; — Multi-line transaction descriptions consume two rows. &lt;br&gt;
The amount ends up on the wrong row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Broken date formatting&lt;/strong&gt; — "Jan 15" becomes a text string instead of a date. &lt;br&gt;
Sorting doesn't work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Works
&lt;/h2&gt;

&lt;p&gt;The problem requires understanding document &lt;em&gt;structure&lt;/em&gt;, not just extracting &lt;br&gt;
coordinates. There are two approaches that get this right.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Bank-specific parsing rules
&lt;/h3&gt;

&lt;p&gt;Some tools hardcode extraction rules for specific banks. Chase statements have &lt;br&gt;
consistent column positions; Wells Fargo uses a different layout. This works &lt;br&gt;
well for the banks supported, but breaks on anything outside the ruleset.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. AI-based document understanding
&lt;/h3&gt;

&lt;p&gt;Modern vision models can look at a bank statement the same way a human does — &lt;br&gt;
recognizing that a table has headers, that indented lines belong to the previous &lt;br&gt;
transaction, that a running balance column is different from a transaction amount &lt;br&gt;
column.&lt;/p&gt;

&lt;p&gt;This approach is more expensive to run but generalizes across banks and &lt;br&gt;
statement formats. The model reads the document semantically rather than &lt;br&gt;
geometrically.&lt;/p&gt;

&lt;p&gt;The key difference in output: AI extraction produces one row per transaction &lt;br&gt;
with clean, separate columns — no merging, no splitting. You get a spreadsheet &lt;br&gt;
you can drop directly into a VLOOKUP or pivot table.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Test
&lt;/h2&gt;

&lt;p&gt;Next time you convert a bank statement, check three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can you sort the Date column chronologically without manual cleanup?&lt;/li&gt;
&lt;li&gt;Can you SUM the Amount column without fixing cell types first?&lt;/li&gt;
&lt;li&gt;Are all transaction descriptions in a single cell, with no spillover?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any of those fail, your converter is doing coordinate extraction, not &lt;br&gt;
structure understanding.&lt;/p&gt;

&lt;p&gt;For anyone dealing with this regularly — whether you're reconciling accounts, &lt;br&gt;
preparing tax records, or importing into QuickBooks — &lt;br&gt;
&lt;a href="https://www.cleanstmt.com" rel="noopener noreferrer"&gt;CleanStmt&lt;/a&gt; uses Claude Vision to extract &lt;br&gt;
transactions with zero merged cells, ready for formulas immediately. &lt;br&gt;
Supports the major US banks and exports to Excel, CSV, QBO, and QIF.&lt;/p&gt;

&lt;p&gt;But honestly, even if you don't use a dedicated tool: knowing &lt;em&gt;why&lt;/em&gt; your &lt;br&gt;
current converter fails will save you a lot of frustration. The fix is usually &lt;br&gt;
switching to a tool that understands financial document structure, not a better &lt;br&gt;
version of the same coordinate-based approach.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How to Convert Bank Statement PDF to Excel for QuickBooks</title>
      <dc:creator>cleanstmt</dc:creator>
      <pubDate>Fri, 31 Jul 2026 03:27:54 +0000</pubDate>
      <link>https://dev.to/cleanstmt/how-to-convert-bank-statement-pdf-to-excel-for-quickbooks-2m26</link>
      <guid>https://dev.to/cleanstmt/how-to-convert-bank-statement-pdf-to-excel-for-quickbooks-2m26</guid>
      <description>&lt;p&gt;Bank statement PDFs are terrible for bookkeeping. You can't import them into QuickBooks, you can't filter transactions, and copying data manually takes forever. If you've ever spent an hour retyping 200 transactions from a PDF, you know the pain.&lt;/p&gt;

&lt;p&gt;This guide walks through the problem, why most solutions fail, and three practical methods you can use today.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Bank Statement PDFs Are a Problem
&lt;/h2&gt;

&lt;p&gt;Most banks only offer two formats for downloading statements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PDF&lt;/strong&gt; (easy to read, impossible to work with)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSV/OFX&lt;/strong&gt; (if you're lucky — many banks don't offer this)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PDFs are designed for printing, not data processing. When you try to copy transaction data from a PDF into Excel, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Merged cells that break formulas&lt;/li&gt;
&lt;li&gt;Headers that repeat on every page&lt;/li&gt;
&lt;li&gt;Date and amount columns that don't line up&lt;/li&gt;
&lt;li&gt;Extra spacing and line breaks everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then when you try to import that into QuickBooks, it rejects the file because the format doesn't match what it expects.&lt;/p&gt;




&lt;h2&gt;
  
  
  What QuickBooks Expects
&lt;/h2&gt;

&lt;p&gt;QuickBooks requires a specific CSV format for importing bank transactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;Amount&lt;/span&gt;
&lt;span class="ld"&gt;01/15/2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;Starbucks&lt;/span&gt; &lt;span class="k"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;5.67&lt;/span&gt;
&lt;span class="ld"&gt;01/16/2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;Salary&lt;/span&gt; &lt;span class="k"&gt;Deposit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3500.00&lt;/span&gt;
&lt;span class="ld"&gt;01/17/2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;Rent&lt;/span&gt; &lt;span class="k"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1200.00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three columns. Clean data. No merged cells. No extra headers.&lt;/p&gt;

&lt;p&gt;If your CSV has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Merged cells spanning multiple rows&lt;/li&gt;
&lt;li&gt;Column names like "Transaction Date" instead of "Date"&lt;/li&gt;
&lt;li&gt;Amounts formatted as &lt;code&gt;($1,200.00)&lt;/code&gt; instead of &lt;code&gt;-1200.00&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;QuickBooks will throw an error and refuse to import.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 1: Manual Copy and Clean (The Hard Way)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What you do:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the PDF in Adobe Reader&lt;/li&gt;
&lt;li&gt;Select all transaction rows, copy&lt;/li&gt;
&lt;li&gt;Paste into Excel&lt;/li&gt;
&lt;li&gt;Manually fix:

&lt;ul&gt;
&lt;li&gt;Unmerge all merged cells&lt;/li&gt;
&lt;li&gt;Delete repeated headers from every page&lt;/li&gt;
&lt;li&gt;Rename columns to match QuickBooks format&lt;/li&gt;
&lt;li&gt;Reformat dates (banks love &lt;code&gt;Jan 15, 2024&lt;/code&gt; but QuickBooks wants &lt;code&gt;01/15/2024&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Convert negative amounts from &lt;code&gt;($100)&lt;/code&gt; to &lt;code&gt;-100&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free&lt;/li&gt;
&lt;li&gt;Works for any bank&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Takes 30-60 minutes per statement&lt;/li&gt;
&lt;li&gt;High chance of copy-paste errors&lt;/li&gt;
&lt;li&gt;You'll miss transactions if the PDF layout is messy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use this:&lt;/strong&gt;&lt;br&gt;
Only if you're doing this once and the statement is short (under 20 transactions).&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 2: Use Your Bank's CSV Export (If Available)
&lt;/h2&gt;

&lt;p&gt;Some banks offer direct CSV or OFX download. Check your online banking portal under:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Statements → Download → CSV/OFX&lt;/li&gt;
&lt;li&gt;Transaction History → Export&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean data straight from the source&lt;/li&gt;
&lt;li&gt;No OCR errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Many banks don't offer this (especially for older statements)&lt;/li&gt;
&lt;li&gt;CSV format still might not match QuickBooks (you'll need to rename columns and reformat dates)&lt;/li&gt;
&lt;li&gt;OFX files require conversion to CSV before importing to QuickBooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use this:&lt;/strong&gt;&lt;br&gt;
If your bank offers it and you're downloading recent statements.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 3: Use an Automated PDF Converter
&lt;/h2&gt;

&lt;p&gt;If you're doing this regularly (monthly bookkeeping, tax prep, migrating historical data), manual methods don't scale. Automated tools use OCR to read the PDF and extract transaction data into clean Excel/CSV.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to look for in a tool:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No merged cells&lt;/strong&gt; — critical for QuickBooks import&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Column name flexibility&lt;/strong&gt; — lets you map to QuickBooks format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Date format conversion&lt;/strong&gt; — handles different bank date formats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negative amount handling&lt;/strong&gt; — converts &lt;code&gt;($100)&lt;/code&gt; to &lt;code&gt;-100&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Three tools worth trying:&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Adobe Acrobat Pro (Expensive)
&lt;/h3&gt;

&lt;p&gt;Adobe's "Export PDF" feature can convert tables to Excel, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subscription costs $19.99/month&lt;/li&gt;
&lt;li&gt;Still creates merged cells&lt;/li&gt;
&lt;li&gt;You'll need to clean data manually after export&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option B: Online OCR Tools (Hit or Miss)
&lt;/h3&gt;

&lt;p&gt;Tools like &lt;code&gt;pdftables.com&lt;/code&gt; or &lt;code&gt;pdftoexcel.com&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free tier usually limits you to 5 files/month&lt;/li&gt;
&lt;li&gt;Quality varies — some create worse formatting than copy-paste&lt;/li&gt;
&lt;li&gt;Upload limits (usually 10 pages max)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option C: Financial Document Converters
&lt;/h3&gt;

&lt;p&gt;Tools specifically built for bank statements (like CleanStmt, BankStatementConverter, or InvoiceDataExtraction):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Designed to output QuickBooks-compatible format&lt;/li&gt;
&lt;li&gt;Handle merged cell issues automatically&lt;/li&gt;
&lt;li&gt;Support multi-page statements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use CleanStmt for this because it outputs clean columns with no merged cells, but any tool that avoids the merged cell problem will work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvugwhx6mvylq3fvicwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvugwhx6mvylq3fvicwv.png" alt=" " width="800" height="747"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Merged Cell Problem (Why This Matters)
&lt;/h2&gt;

&lt;p&gt;Here's what most PDF converters produce:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Debit&lt;/th&gt;
&lt;th&gt;Credit&lt;/th&gt;
&lt;th&gt;Balance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01/15/2024&lt;/td&gt;
&lt;td&gt;Opening Balance&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;5000.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Starbucks&lt;/td&gt;
&lt;td&gt;5.67&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Gas Station&lt;/td&gt;
&lt;td&gt;45.00&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01/16/2024&lt;/td&gt;
&lt;td&gt;Salary Deposit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;3500.00&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;See how the date column has blank cells? Excel interprets this as merged cells or empty rows. When you try to import this into QuickBooks, it fails because every transaction needs a date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you actually need:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01/15/2024&lt;/td&gt;
&lt;td&gt;Opening Balance&lt;/td&gt;
&lt;td&gt;5000.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01/15/2024&lt;/td&gt;
&lt;td&gt;Starbucks&lt;/td&gt;
&lt;td&gt;-5.67&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01/15/2024&lt;/td&gt;
&lt;td&gt;Gas Station&lt;/td&gt;
&lt;td&gt;-45.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01/16/2024&lt;/td&gt;
&lt;td&gt;Salary Deposit&lt;/td&gt;
&lt;td&gt;3500.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row has a complete date. Debit/Credit collapsed into one Amount column (negative = debit). No merged cells.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhldta2zixpyi4hu46yvf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhldta2zixpyi4hu46yvf.png" alt=" " width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step: Converting and Importing to QuickBooks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Get your bank statement PDF&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Download from your bank's online portal or use a scanned/emailed copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Convert to Excel/CSV&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Choose one of the three methods above. Make sure the output has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One row per transaction&lt;/li&gt;
&lt;li&gt;Date in every row&lt;/li&gt;
&lt;li&gt;Three columns: Date, Description, Amount&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Clean the data (if needed)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open in Excel and verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No merged cells (select all → Home → Merge &amp;amp; Center should NOT be highlighted)&lt;/li&gt;
&lt;li&gt;Date format is &lt;code&gt;MM/DD/YYYY&lt;/code&gt; (US) or whatever your QuickBooks region expects&lt;/li&gt;
&lt;li&gt;Amount column uses negative numbers for debits (not parentheses)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Save as CSV&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;File → Save As → CSV (Comma delimited) &lt;code&gt;*.csv&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Import to QuickBooks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QuickBooks Online:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Banking → Upload Transactions&lt;/li&gt;
&lt;li&gt;Click Browse and select your CSV file&lt;/li&gt;
&lt;li&gt;Map the columns (Date → Date, Description → Description, Amount → Amount)&lt;/li&gt;
&lt;li&gt;Click Import&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;QuickBooks Desktop:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Banking → Bank Feeds → Import Bank Data&lt;/li&gt;
&lt;li&gt;Select your CSV file&lt;/li&gt;
&lt;li&gt;Follow the mapping wizard&lt;/li&gt;
&lt;li&gt;Confirm and import&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the format is correct (no merged cells, proper dates, negative amounts for debits), the import will succeed on the first try.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Import Errors and Fixes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Date format not recognized"&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix: Excel dates must be formatted as &lt;code&gt;MM/DD/YYYY&lt;/code&gt; (or your region's format)&lt;/li&gt;
&lt;li&gt;In Excel: Select date column → Format Cells → Date → choose format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;"Amount must be a number"&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix: Remove currency symbols (&lt;code&gt;$&lt;/code&gt;), commas, and convert &lt;code&gt;($100)&lt;/code&gt; to &lt;code&gt;-100&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Find/Replace: &lt;code&gt;($&lt;/code&gt; → &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;)&lt;/code&gt; → (blank)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;"Duplicate transactions"&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix: QuickBooks remembers past imports. If you're re-importing, it will skip duplicates. This is normal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;"Row X is missing required data"&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix: Every row must have Date, Description, and Amount. Check for blank cells.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When to Use Which Method
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Best Method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One-time conversion, short statement&lt;/td&gt;
&lt;td&gt;Manual copy-paste&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your bank offers CSV download&lt;/td&gt;
&lt;td&gt;Use the bank's CSV export&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly bookkeeping (recurring task)&lt;/td&gt;
&lt;td&gt;Automated converter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Historical statements (5+ years)&lt;/td&gt;
&lt;td&gt;Automated converter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Messy scanned PDFs&lt;/td&gt;
&lt;td&gt;Automated converter with OCR&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Converting bank statements shouldn't take an hour every month. If you're doing this regularly, investing in an automated tool (or at least a good process) saves you time and reduces errors.&lt;/p&gt;

&lt;p&gt;The key thing QuickBooks cares about: clean, consistent data. No merged cells, correct date format, and negative numbers for debits. Get those three things right, and imports work every time.&lt;/p&gt;

</description>
      <category>quickbooks</category>
      <category>bookkeeping</category>
      <category>accounting</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Financial Document OCR with Claude Vision API: Lessons from Production</title>
      <dc:creator>cleanstmt</dc:creator>
      <pubDate>Mon, 27 Jul 2026 03:43:26 +0000</pubDate>
      <link>https://dev.to/cleanstmt/building-a-financial-document-ocr-with-claude-vision-api-lessons-from-production-3m3o</link>
      <guid>https://dev.to/cleanstmt/building-a-financial-document-ocr-with-claude-vision-api-lessons-from-production-3m3o</guid>
      <description>&lt;p&gt;After processing thousands of bank statements, invoices, and receipts through Claude Vision API, I've learned that financial document OCR is harder than it looks. Here's what actually works in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Why Traditional OCR Fails on Financial Documents
&lt;/h2&gt;

&lt;p&gt;Traditional OCR tools like Tesseract or AWS Textract struggle with financial documents for three reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Table structure is implicit&lt;/strong&gt; — Banks don't use HTML tables. Columns are separated by whitespace, making it hard to know where one column ends and another begins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Numbers must be perfect&lt;/strong&gt; — Confusing &lt;code&gt;1&lt;/code&gt; with &lt;code&gt;l&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt; with &lt;code&gt;O&lt;/code&gt; creates accounting errors. A single misread digit can break double-entry bookkeeping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Format chaos&lt;/strong&gt; — Every bank uses different layouts. Chase statements look nothing like Wells Fargo statements.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Traditional OCR gives you raw text. You still need to write hundreds of lines of regex to parse it into structured data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Claude Vision API Changes the Game
&lt;/h2&gt;

&lt;p&gt;Claude Vision doesn't just extract text — it &lt;strong&gt;understands document structure&lt;/strong&gt;. You give it an image and a prompt like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Extract this bank statement into JSON with transaction date, description, debit, credit, and balance columns."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude returns structured JSON directly. No regex. No manual column detection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real Example
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Input:&lt;/strong&gt; Bank statement PDF (converted to PNG)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract all transactions from this bank statement. Return JSON with:
- header: {accountNumber, statementPeriod, bankName}
- transactions: [{date, description, debit, credit, balance}]

Rules:
- Dates in YYYY-MM-DD format
- All amounts as numbers (no currency symbols)
- If a field is unclear, use null (never guess)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accountNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"****1234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"statementPeriod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-01 to 2024-01-31"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bankName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chase Bank"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Amazon.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"debit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;49.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"credit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"balance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1450.01&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-05"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Salary Deposit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"debit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"credit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"balance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4950.01&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No parsing code. No regex. Just structured data ready for your database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Challenges and Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenge 1: Low-Quality Scans
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Users upload phone photos of statements — blurry, skewed, poor lighting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Preprocess images before sending to Claude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageEnhance&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;preprocess_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert to grayscale (reduces noise)
&lt;/span&gt;    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Increase contrast
&lt;/span&gt;    &lt;span class="n"&gt;enhancer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageEnhance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contrast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enhancer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enhance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Resize if too large (Claude Vision has 5MB limit)
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Accuracy improved from 78% to 94% on mobile-captured statements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 2: Multi-Page Statements
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Statements can be 5-10 pages. Sending all pages in one request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hits token limits&lt;/li&gt;
&lt;li&gt;Increases latency&lt;/li&gt;
&lt;li&gt;Costs more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Process first + last page only for most use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First page:&lt;/strong&gt; Account info, statement period, opening balance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Last page:&lt;/strong&gt; Closing balance, summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For full transaction history, batch-process middle pages and merge results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_statement_summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_pages&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Extract key info from first and last page only&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# Process first page
&lt;/span&gt;    &lt;span class="n"&gt;first_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdf_pages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-20250514&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base64&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image/png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;first_page&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Extract account number, statement period, opening balance as JSON.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Parse JSON from response
&lt;/span&gt;    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cost savings:&lt;/strong&gt; $0.15 per statement → $0.03 per statement (5× reduction)&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 3: Decimal Point Errors
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Claude occasionally misreads &lt;code&gt;1,234.56&lt;/code&gt; as &lt;code&gt;123456&lt;/code&gt; or &lt;code&gt;12.34&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Add validation rules in your prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rules for amount extraction:
1. All amounts must have exactly 2 decimal places
2. If you see "1,234.56", extract as 1234.56
3. If you see "1234", extract as 1234.00
4. If unclear whether "1234" means 1234.00 or 12.34, use null
5. Never guess — uncertain values must be null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also validate in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_amount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="c1"&gt;# Ensure 2 decimal places
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Decimal errors dropped from 3.2% to 0.4%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 4: Model Fallback
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Claude Sonnet 4 sometimes rate-limits during peak hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Implement model fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MODELS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-20250514&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Primary
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-3-5-sonnet-20241022&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Fallback
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-3-haiku-20240307&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;     &lt;span class="c1"&gt;# Last resort
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_with_fallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;MODELS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[...]&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RateLimitError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All models rate-limited&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; 99.7% uptime even during peak usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 5: Handling Edge Cases
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Real-world statements have weird formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Split transactions across pages&lt;/li&gt;
&lt;li&gt;Negative balances shown as &lt;code&gt;(1234.56)&lt;/code&gt; instead of &lt;code&gt;-1234.56&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing dates (e.g., pending transactions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Explicit edge case handling in prompts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Edge cases:
- Amounts in parentheses like "(123.45)" mean negative (debit)
- If a transaction has no date, use the statement end date
- If balance column is empty, calculate it from previous balance +/- amount
- "Pending" transactions go in a separate "pending" array
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And post-process in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Convert parentheses to negative
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;debit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;debit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;debit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;debit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;()&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Fill missing dates
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statement_end_date&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cost Optimization
&lt;/h2&gt;

&lt;p&gt;Financial document OCR can get expensive. Here's what we learned:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Optimization&lt;/th&gt;
&lt;th&gt;Cost Impact&lt;/th&gt;
&lt;th&gt;Accuracy Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Process first+last page only&lt;/td&gt;
&lt;td&gt;-80%&lt;/td&gt;
&lt;td&gt;-5% (acceptable for summaries)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use Haiku for simple receipts&lt;/td&gt;
&lt;td&gt;-90%&lt;/td&gt;
&lt;td&gt;-2% (receipts are easier)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch similar documents&lt;/td&gt;
&lt;td&gt;-30%&lt;/td&gt;
&lt;td&gt;+3% (context helps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt caching (reuse bank-specific rules)&lt;/td&gt;
&lt;td&gt;-50%&lt;/td&gt;
&lt;td&gt;No change&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Current costs:&lt;/strong&gt; $0.03 per bank statement, $0.01 per receipt using this setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy Metrics from Production
&lt;/h2&gt;

&lt;p&gt;After 10,000+ documents processed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Document Type&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Digital bank statements (PDF)&lt;/td&gt;
&lt;td&gt;98.2%&lt;/td&gt;
&lt;td&gt;High contrast, clean layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanned bank statements&lt;/td&gt;
&lt;td&gt;94.1%&lt;/td&gt;
&lt;td&gt;Preprocessed with contrast enhancement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile photos of statements&lt;/td&gt;
&lt;td&gt;91.7%&lt;/td&gt;
&lt;td&gt;Users must follow photo guidelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invoices (structured)&lt;/td&gt;
&lt;td&gt;96.8%&lt;/td&gt;
&lt;td&gt;Consistent format helps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Receipts (printed)&lt;/td&gt;
&lt;td&gt;89.4%&lt;/td&gt;
&lt;td&gt;Small text, low contrast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handwritten receipts&lt;/td&gt;
&lt;td&gt;72.3%&lt;/td&gt;
&lt;td&gt;Use case too hard for automation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Accuracy" = extracted data matches manual review.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to Use Claude Vision
&lt;/h2&gt;

&lt;p&gt;Claude Vision isn't perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Handwritten documents&lt;/strong&gt; — Accuracy drops to 70-80%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time processing&lt;/strong&gt; — Latency is 2-4 seconds per page (too slow for POS systems)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-security contexts&lt;/strong&gt; — Data leaves your infrastructure (though Anthropic doesn't train on API data)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For these cases, consider AWS Textract + custom parsing or on-premise OCR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Preprocessing matters&lt;/strong&gt; — Contrast enhancement and grayscale conversion boost accuracy 10-15%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate everything&lt;/strong&gt; — Never trust raw OCR output. Check decimals, date formats, and null handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit prompts win&lt;/strong&gt; — The more rules you specify, the fewer surprises in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-optimize aggressively&lt;/strong&gt; — Full-page processing is overkill for most use cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model fallback is essential&lt;/strong&gt; — Rate limits happen. Have backup models ready&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Want to test Claude Vision on your own statements? I built &lt;a href="https://cleanstmt.com" rel="noopener noreferrer"&gt;CleanStmt&lt;/a&gt; as a free tool to convert bank statements to Excel/CSV using the techniques above.&lt;/p&gt;

&lt;p&gt;Source code for the preprocessing pipeline: &lt;a href="https://github.com/cleanstmt/cleanstmt" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; (coming soon)&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your experience with financial document OCR?&lt;/strong&gt; Drop a comment if you've hit similar challenges or found better solutions.&lt;/p&gt;

&lt;h1&gt;
  
  
  ai #ocr #claude #fintech #python
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>ocr</category>
      <category>claude</category>
      <category>fintech</category>
    </item>
    <item>
      <title>How to Convert Bank Statements to CSV (Without Losing Data Accuracy)</title>
      <dc:creator>cleanstmt</dc:creator>
      <pubDate>Thu, 23 Jul 2026 03:49:27 +0000</pubDate>
      <link>https://dev.to/cleanstmt/how-to-convert-bank-statements-to-csv-without-losing-data-accuracy-2egk</link>
      <guid>https://dev.to/cleanstmt/how-to-convert-bank-statements-to-csv-without-losing-data-accuracy-2egk</guid>
      <description>&lt;p&gt;If you've ever tried converting a bank statement PDF to CSV and ended up with&lt;br&gt;
a jumbled mess of merged cells, missing rows, or split transaction&lt;br&gt;
descriptions — you're not alone. This is one of the most common data pain&lt;br&gt;
points for accountants, bookkeepers, and anyone who does their own finances.&lt;/p&gt;

&lt;p&gt;In this post, I'll walk through why this happens, what the right approach&lt;br&gt;
looks like, and how to get clean, analysis-ready CSV output from any bank&lt;br&gt;
statement.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Bank Statement PDFs Are So Hard to Parse
&lt;/h2&gt;

&lt;p&gt;Bank statements aren't structured documents — they're designed for printing,&lt;br&gt;
not data extraction. Here's what generic converters run into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Merged cells&lt;/strong&gt;: PDF renderers often group date + description + amount
into a single visual block. Naive converters pick one cell boundary and
split it wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-line transactions&lt;/strong&gt;: A single transaction entry (especially with
memos) can span 2–3 lines in the PDF, but gets split into separate rows
in the spreadsheet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negative vs. positive amounts&lt;/strong&gt;: Debit/credit columns vary by bank.Chase uses a single "Amount" column with negatives for debits. BoA uses
two separate columns. A generic converter treats them identically and
produces wrong signs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running balance drift&lt;/strong&gt;: If even one row is misaligned, every balance
figure below it is off.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Method 1: Manual Copy-Paste (What You're Probably Doing Now)
&lt;/h2&gt;

&lt;p&gt;The baseline. Open the PDF, select all, paste into Excel, then spend 30&lt;br&gt;
minutes fixing column alignment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Free, no tools required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Slow (20–40 minutes per statement), error-prone, completely&lt;br&gt;
unscalable if you have multiple accounts or months to process.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 2: Python + pdfplumber
&lt;/h2&gt;

&lt;p&gt;For developers who want a scriptable solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pdfplumber&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pdfplumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statement.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_table&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works reasonably well for simple, text-layer PDFs. But it breaks on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scanned/image PDFs (no text layer to extract)&lt;/li&gt;
&lt;li&gt;Non-standard table layouts&lt;/li&gt;
&lt;li&gt;Banks that render statements as one continuous text block without table
structure (US Bank, some credit unions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll also need bank-specific post-processing to normalize column names,&lt;br&gt;
handle debit/credit sign conventions, and filter out header/footer rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Scriptable, free, good for clean PDFs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Breaks on scanned documents, needs per-bank customization.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 3: AI-Powered OCR (Best Accuracy)
&lt;/h2&gt;

&lt;p&gt;This is what actually works reliably across different banks, statement&lt;br&gt;
formats, and even photographed documents.&lt;/p&gt;

&lt;p&gt;The key difference: instead of trying to detect table boundaries from PDF&lt;br&gt;
structure (which is unreliable), a vision model looks at the document the&lt;br&gt;
way a human does — reads column headers, understands that "Withdrawal" means&lt;br&gt;
debit, knows that a running balance should decrease when there's a debit —&lt;br&gt;
and extracts accordingly.&lt;/p&gt;

&lt;p&gt;We built &lt;a href="https://cleanstmt.com" rel="noopener noreferrer"&gt;CleanStmt&lt;/a&gt; specifically for this use case.&lt;br&gt;
It uses Claude's vision API to extract transactions digit-by-digit, outputs&lt;br&gt;
clean CSV (and Excel, QBO, QIF, OFX) with no merged cells, and handles 17+&lt;br&gt;
major banks including Chase, Bank of America, Wells Fargo, and Citi.&lt;/p&gt;

&lt;p&gt;The extracted CSV structure is consistent regardless of source format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;Balance&lt;/span&gt;
&lt;span class="ld"&gt;2024-01-03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;AMAZON&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="k"&gt;COM&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="k"&gt;A&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="k"&gt;B&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;42.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1957.01&lt;/span&gt;
&lt;span class="ld"&gt;2024-01-05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;DIRECT&lt;/span&gt; &lt;span class="k"&gt;DEPOSIT&lt;/span&gt; &lt;span class="k"&gt;EMPLOYER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;2500.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;4457.01&lt;/span&gt;
&lt;span class="ld"&gt;2024-01-07&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;WHOLE&lt;/span&gt; &lt;span class="k"&gt;FOODS&lt;/span&gt; &lt;span class="k"&gt;MARKET&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mf"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;87.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;4369.51&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No merged cells. Amounts with correct signs. Dates in ISO format. Ready for&lt;br&gt;
pivot tables, VLOOKUP, or import into any accounting tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Look for in a Bank Statement to CSV Converter
&lt;/h2&gt;

&lt;p&gt;Whether you build your own or use a tool, the output should pass these checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Row count matches the transaction count on the last page of the statement&lt;/li&gt;
&lt;li&gt;[ ] Sum of Amount column reconciles with (closing balance − opening balance)&lt;/li&gt;
&lt;li&gt;[ ] No merged cells in the CSV (check by opening in a text editor)&lt;/li&gt;
&lt;li&gt;[ ] Multi-line descriptions are joined, not split into separate rows&lt;/li&gt;
&lt;li&gt;[ ] Debits are consistently negative, credits consistently positive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these fail, your data has integrity issues — and downstream reports&lt;br&gt;
or QuickBooks imports will silently be wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;For a one-off statement&lt;/strong&gt;: Use an AI-powered converter. Fastest path to
clean data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For recurring monthly processing&lt;/strong&gt;: Script it with pdfplumber + a
post-processing layer, or use a tool with API access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For scanned/photographed statements&lt;/strong&gt;: AI OCR is the only reliable
option. pdfplumber can't read image-only PDFs.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The goal isn't just getting rows out of a PDF — it's getting rows you can&lt;br&gt;
trust. The merged cells problem is a symptom; the root cause is treating a&lt;br&gt;
financial document like a generic table. Once you account for bank-specific&lt;br&gt;
conventions and use a parser that understands financial context, CSV&lt;br&gt;
conversion becomes a solved problem.&lt;/p&gt;

&lt;p&gt;If you run into a specific bank or format that's giving you trouble, drop a comment — happy to help debug.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>productivity</category>
      <category>python</category>
      <category>statement</category>
    </item>
    <item>
      <title>How to Import Bank Statements to QuickBooks (3 Simple Methods)</title>
      <dc:creator>cleanstmt</dc:creator>
      <pubDate>Thu, 16 Jul 2026 03:25:11 +0000</pubDate>
      <link>https://dev.to/cleanstmt/how-to-import-bank-statements-to-quickbooks-3-simple-methods-3j45</link>
      <guid>https://dev.to/cleanstmt/how-to-import-bank-statements-to-quickbooks-3-simple-methods-3j45</guid>
      <description>&lt;p&gt;If you're managing books in QuickBooks, importing bank statements is essential for accurate reconciliation and financial reporting. But the process isn't always straightforward—especially if your bank doesn't offer a direct QuickBooks connection or you need to import historical data.&lt;/p&gt;

&lt;p&gt;In this guide, I'll show you three reliable methods to get bank transactions into QuickBooks Desktop and QuickBooks Online, along with tips to avoid common pitfalls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Import Bank Statements Instead of Manual Entry?
&lt;/h2&gt;

&lt;p&gt;Manual transaction entry is time-consuming and error-prone. Importing bank statements offers several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: Import hundreds of transactions in seconds instead of typing each one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accuracy&lt;/strong&gt;: Reduce data entry errors (transposed numbers, typos, missing decimals)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconciliation&lt;/strong&gt;: Match imported transactions against QuickBooks records to spot discrepancies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical data&lt;/strong&gt;: Backfill months or years of transactions for new QuickBooks setups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit trail&lt;/strong&gt;: Keep a digital record that matches your bank's official statement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's look at the three main import methods.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 1: Direct Bank Connection (Easiest, But Limited)
&lt;/h2&gt;

&lt;p&gt;QuickBooks Online and QuickBooks Desktop (via Bank Feeds) can connect directly to most major banks. Transactions download automatically each day.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Set It Up:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;QuickBooks Online:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Banking&lt;/strong&gt; → &lt;strong&gt;Add Account&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Search for your bank name&lt;/li&gt;
&lt;li&gt;Enter your online banking credentials&lt;/li&gt;
&lt;li&gt;Select the accounts to connect&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Connect&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;QuickBooks Desktop:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Banking&lt;/strong&gt; → &lt;strong&gt;Bank Feeds&lt;/strong&gt; → &lt;strong&gt;Set Up Bank Feed for an Account&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Search for your bank&lt;/li&gt;
&lt;li&gt;Log in and authorize QuickBooks&lt;/li&gt;
&lt;li&gt;Choose which accounts to sync&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Pros:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Automatic daily updates&lt;/li&gt;
&lt;li&gt;No file conversion needed&lt;/li&gt;
&lt;li&gt;Works seamlessly for ongoing bookkeeping&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limited history&lt;/strong&gt;: Most banks only provide 90 days of past transactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not all banks supported&lt;/strong&gt;: Smaller credit unions or international banks may not be available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No control over import timing&lt;/strong&gt;: Transactions appear when the bank pushes them, not when you need them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security concerns&lt;/strong&gt;: Some businesses prefer not to share banking credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Ongoing bookkeeping with supported banks where 90 days of history is sufficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 2: Upload CSV Files (Most Flexible)
&lt;/h2&gt;

&lt;p&gt;Most banks let you download transaction history as CSV (comma-separated values). QuickBooks can import these files, but you need to format the CSV correctly first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Download Transactions from Your Bank
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log into your bank's online portal&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Statements&lt;/strong&gt; or &lt;strong&gt;Transaction History&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select the date range you need&lt;/li&gt;
&lt;li&gt;Download as &lt;strong&gt;CSV&lt;/strong&gt; or &lt;strong&gt;Excel&lt;/strong&gt; format (choose CSV if both are available)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Format the CSV for QuickBooks
&lt;/h3&gt;

&lt;p&gt;QuickBooks expects specific column headers. Open the CSV in Excel or Google Sheets and adjust:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required columns:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Date&lt;/strong&gt; (format: MM/DD/YYYY)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt; (payee or transaction detail)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amount&lt;/strong&gt; (positive for deposits, negative for withdrawals)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Optional columns:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Check Number&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memo&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reference Number&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example CSV structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date,Description,Amount
01/15/2026,Coffee Shop,−4.50
01/16/2026,Client Payment - Invoice 1042,500.00
01/17/2026,Office Supplies - Staples,−23.67
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common formatting issues:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two-column amounts&lt;/strong&gt;: If your bank CSV has separate "Debit" and "Credit" columns, combine them into one "Amount" column (use a formula: &lt;code&gt;=IF(B2&amp;lt;&amp;gt;"",−B2,C2)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Date format&lt;/strong&gt;: QuickBooks requires MM/DD/YYYY. If your bank uses DD/MM/YYYY or YYYY-MM-DD, reformat the column&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Currency symbols&lt;/strong&gt;: Remove $ signs and commas (QuickBooks expects plain numbers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extra columns&lt;/strong&gt;: Delete any columns QuickBooks doesn't need (balance, branch, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Import into QuickBooks
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;QuickBooks Online:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Banking&lt;/strong&gt; → &lt;strong&gt;File Upload&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Browse&lt;/strong&gt; and select your CSV&lt;/li&gt;
&lt;li&gt;Map the CSV columns to QuickBooks fields&lt;/li&gt;
&lt;li&gt;Review transactions and click &lt;strong&gt;Add&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;QuickBooks Desktop:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;File&lt;/strong&gt; → &lt;strong&gt;Utilities&lt;/strong&gt; → &lt;strong&gt;Import&lt;/strong&gt; → &lt;strong&gt;Bank Statement (CSV)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select your CSV file&lt;/li&gt;
&lt;li&gt;Map columns (Date, Description, Amount)&lt;/li&gt;
&lt;li&gt;Choose the account to import into&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Import&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Pros:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Works with any bank (no direct connection needed)&lt;/li&gt;
&lt;li&gt;Import any date range (great for historical data)&lt;/li&gt;
&lt;li&gt;Full control over what gets imported&lt;/li&gt;
&lt;li&gt;Can clean up data before import&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requires manual formatting (5-10 minutes per file)&lt;/li&gt;
&lt;li&gt;Not automated—you have to download and import manually&lt;/li&gt;
&lt;li&gt;Risk of formatting errors if you're not familiar with CSV files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Importing historical data, working with unsupported banks, or one-time bulk imports.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 3: Upload QBO Files (QuickBooks Native Format)
&lt;/h2&gt;

&lt;p&gt;Some banks offer &lt;strong&gt;QBO&lt;/strong&gt; (QuickBooks Online) or &lt;strong&gt;QFX&lt;/strong&gt; (Quicken Financial Exchange) downloads. These are pre-formatted files designed specifically for QuickBooks.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Get QBO Files:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log into your bank's online portal&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Download Transactions&lt;/strong&gt; or &lt;strong&gt;Export&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;QuickBooks (QBO)&lt;/strong&gt; or &lt;strong&gt;Quicken (QFX)&lt;/strong&gt; as the format&lt;/li&gt;
&lt;li&gt;Select the date range&lt;/li&gt;
&lt;li&gt;Download the file&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How to Import:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;QuickBooks Online:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Banking&lt;/strong&gt; → &lt;strong&gt;File Upload&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Browse&lt;/strong&gt; and select the &lt;code&gt;.qbo&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;QuickBooks auto-maps the fields&lt;/li&gt;
&lt;li&gt;Review and add transactions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;QuickBooks Desktop:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;File&lt;/strong&gt; → &lt;strong&gt;Utilities&lt;/strong&gt; → &lt;strong&gt;Import&lt;/strong&gt; → &lt;strong&gt;Web Connect Files&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;.qbo&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Choose the account&lt;/li&gt;
&lt;li&gt;Review and accept transactions&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Pros:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero formatting needed&lt;/strong&gt;: QBO files are ready to import as-is&lt;/li&gt;
&lt;li&gt;Faster than CSV (no column mapping)&lt;/li&gt;
&lt;li&gt;Includes metadata (check numbers, reference IDs)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not all banks support it&lt;/strong&gt;: Fewer banks offer QBO downloads compared to CSV&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less flexible&lt;/strong&gt;: You can't edit transactions before import&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Date range limits&lt;/strong&gt;: Some banks restrict QBO downloads to 2 years&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Banks that offer QBO/QFX downloads and you need a quick, no-fuss import.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which Method Should You Use?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Scenario&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Best Method&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Why&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ongoing bookkeeping with a major bank&lt;/td&gt;
&lt;td&gt;Direct Bank Connection&lt;/td&gt;
&lt;td&gt;Automatic, no manual work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Importing 2+ years of historical data&lt;/td&gt;
&lt;td&gt;CSV Upload&lt;/td&gt;
&lt;td&gt;Most banks offer CSV with unlimited history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your bank isn't supported by QuickBooks&lt;/td&gt;
&lt;td&gt;CSV Upload&lt;/td&gt;
&lt;td&gt;Works with any bank&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One-time bulk import (e.g., migrating to QuickBooks)&lt;/td&gt;
&lt;td&gt;CSV or QBO&lt;/td&gt;
&lt;td&gt;Depends on what your bank offers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need full control over imported data&lt;/td&gt;
&lt;td&gt;CSV Upload&lt;/td&gt;
&lt;td&gt;You can clean/edit before import&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your bank offers QBO/QFX downloads&lt;/td&gt;
&lt;td&gt;QBO Upload&lt;/td&gt;
&lt;td&gt;Fastest, zero formatting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Common Import Errors and How to Fix Them
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. "Duplicate transactions detected"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; You already imported this date range.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Filter the CSV to exclude already-imported dates, or skip duplicates during import review.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. "Invalid date format"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; QuickBooks expects MM/DD/YYYY.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; In Excel, select the Date column → Format Cells → Date → MM/DD/YYYY.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. "Amounts not importing correctly"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; CSV has separate Debit/Credit columns or includes currency symbols.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Combine into one "Amount" column with negatives for debits, remove $ symbols.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. "QuickBooks isn't mapping my columns"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Column headers don't match QuickBooks expectations.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Rename headers to: &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;Description&lt;/code&gt;, &lt;code&gt;Amount&lt;/code&gt; (exact capitalization may matter).&lt;/p&gt;

&lt;h3&gt;
  
  
  5. "My bank's CSV has extra rows (header text, totals, etc.)"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Some banks add non-transaction rows to CSV files.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Open in Excel, delete the extra rows, save, then import.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tips for Smooth Bank Statement Imports
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Always review before finalizing&lt;/strong&gt;: QuickBooks shows a preview—check for duplicates, incorrect amounts, or misclassified transactions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use batch imports for historical data&lt;/strong&gt;: If importing years of transactions, do one month at a time to avoid overwhelming the review screen.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep original files&lt;/strong&gt;: Save downloaded CSV/QBO files in case you need to re-import or audit later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set up bank rules&lt;/strong&gt;: After importing, create QuickBooks rules to auto-categorize recurring transactions (e.g., "Office Depot" → Office Supplies).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reconcile immediately&lt;/strong&gt;: Once imported, reconcile the account in QuickBooks against your bank statement to catch any discrepancies.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What If Your Bank Statement Is Only Available as a PDF?
&lt;/h2&gt;

&lt;p&gt;Many banks (especially for older statements) only offer PDF downloads. QuickBooks cannot import PDFs directly, so you'll need to convert the PDF to CSV first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manual transcription&lt;/strong&gt;: Copy/paste transactions into Excel (slow, error-prone)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generic PDF to Excel converters&lt;/strong&gt;: Tools like Adobe Acrobat, PDFTables, Smallpdf (often create messy outputs with merged cells)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bank statement-specific converters&lt;/strong&gt;: Tools designed for financial documents that output clean CSV files ready for QuickBooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have a clean CSV, follow Method 2 above.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Importing bank statements into QuickBooks doesn't have to be complicated. Direct bank connections work great for ongoing bookkeeping, but CSV and QBO uploads give you the flexibility to import historical data, work with any bank, and maintain full control over your transaction records.&lt;/p&gt;

&lt;p&gt;The key is choosing the right method for your situation—and making sure your data is clean before import. A few minutes of formatting can save hours of manual reconciliation later.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your preferred method for importing bank data into QuickBooks? Have you encountered any import challenges? Share your experience in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>quickbooks</category>
      <category>accounting</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why Bank Statement PDFs Convert to Excel with Merged Cells (And How to Fix It)</title>
      <dc:creator>cleanstmt</dc:creator>
      <pubDate>Mon, 06 Jul 2026 06:37:34 +0000</pubDate>
      <link>https://dev.to/cleanstmt/why-bank-statement-pdfs-convert-to-excel-with-merged-cells-and-how-to-fix-it-2of5</link>
      <guid>https://dev.to/cleanstmt/why-bank-statement-pdfs-convert-to-excel-with-merged-cells-and-how-to-fix-it-2of5</guid>
      <description>&lt;h1&gt;
  
  
  Why Bank Statement PDFs Convert to Excel with Merged Cells (And How to Fix It)
&lt;/h1&gt;

&lt;p&gt;If you've ever converted a bank statement PDF to Excel, you've probably encountered this frustrating problem: &lt;strong&gt;merged cells everywhere&lt;/strong&gt;. These merged cells break formulas, prevent pivot tables from working, and make data analysis nearly impossible.&lt;/p&gt;

&lt;p&gt;In this guide, I'll explain why this happens and show you three practical ways to get clean, analysis-ready Excel files from your bank statements.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Do Bank Statements Convert with Merged Cells?
&lt;/h2&gt;

&lt;p&gt;Most PDF converters analyze the visual layout of a PDF page. When they see text positioned in the center of a wide area, they assume it should span multiple columns. This works fine for documents like contracts or reports, but bank statements have a unique structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Section headers&lt;/strong&gt; (like "Deposits" or "Withdrawals") span the full width&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction rows&lt;/strong&gt; have multiple narrow columns (Date, Description, Amount)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summary totals&lt;/strong&gt; often appear in the right columns only&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Generic PDF converters can't tell the difference between these three layouts, so they create merged cells for headers and totals—then carry that pattern into the transaction rows.&lt;/p&gt;

&lt;p&gt;The result? An Excel file where half the cells are merged, formulas return errors, and sorting becomes impossible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 1: Manual Unmerge and Cleanup
&lt;/h2&gt;

&lt;p&gt;The most straightforward approach is to fix the Excel file after conversion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert your PDF using any tool (Adobe Acrobat, PDFTables, Smallpdf, etc.)&lt;/li&gt;
&lt;li&gt;Open the Excel file&lt;/li&gt;
&lt;li&gt;Select all cells (&lt;code&gt;Ctrl+A&lt;/code&gt; or &lt;code&gt;Cmd+A&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Home&lt;/strong&gt; → &lt;strong&gt;Merge &amp;amp; Center&lt;/strong&gt; → &lt;strong&gt;Unmerge Cells&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Clean up the data:

&lt;ul&gt;
&lt;li&gt;Delete extra blank columns&lt;/li&gt;
&lt;li&gt;Move misaligned text back to the correct columns&lt;/li&gt;
&lt;li&gt;Fix header rows&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works with any PDF converter&lt;/li&gt;
&lt;li&gt;No additional tools required&lt;/li&gt;
&lt;li&gt;You have full control over the final layout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time-consuming (10-20 minutes per statement)&lt;/li&gt;
&lt;li&gt;Error-prone if you have hundreds of transactions&lt;/li&gt;
&lt;li&gt;Doesn't scale if you need to process multiple statements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; One-off conversions where you have time to review the data manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 2: Use Spreadsheet Formulas to Restructure Data
&lt;/h2&gt;

&lt;p&gt;If your converted Excel file has a consistent pattern (even with merged cells), you can use formulas to extract clean data into a new sheet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert the PDF to Excel&lt;/li&gt;
&lt;li&gt;Create a new sheet called "Clean Data"&lt;/li&gt;
&lt;li&gt;Use formulas to pull values from the messy sheet:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;=IF(ISBLANK(A2), "", A2)&lt;/code&gt; to skip empty cells&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;=INDEX()&lt;/code&gt; and &lt;code&gt;MATCH()&lt;/code&gt; to find specific columns&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;=TRIM()&lt;/code&gt; to remove extra spaces&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Copy the formula down for all rows&lt;/li&gt;
&lt;li&gt;Convert formulas to values (&lt;code&gt;Paste Special&lt;/code&gt; → &lt;code&gt;Values&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semi-automated once you set up the formulas&lt;/li&gt;
&lt;li&gt;Reusable for future statements with the same layout&lt;/li&gt;
&lt;li&gt;No need for additional software&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires Excel formula knowledge&lt;/li&gt;
&lt;li&gt;Fragile—breaks if the PDF layout changes slightly&lt;/li&gt;
&lt;li&gt;Still starts with a messy conversion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Regular monthly statements from the same bank where the layout is consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 3: Use a Bank Statement-Specific Converter
&lt;/h2&gt;

&lt;p&gt;The most reliable solution is to use a tool designed specifically for financial documents. These tools understand the structure of bank statements and apply specialized extraction rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They recognize transaction tables vs. header sections&lt;/li&gt;
&lt;li&gt;They enforce a stable column structure (Date, Description, Debit, Credit, Balance)&lt;/li&gt;
&lt;li&gt;They &lt;strong&gt;guarantee no merged cells&lt;/strong&gt; in the output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload your bank statement PDF&lt;/li&gt;
&lt;li&gt;The tool uses OCR or AI to detect the table structure&lt;/li&gt;
&lt;li&gt;It exports a clean Excel file with proper columns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fastest method (30 seconds per statement)&lt;/li&gt;
&lt;li&gt;Consistent output format&lt;/li&gt;
&lt;li&gt;Works across different banks and statement layouts&lt;/li&gt;
&lt;li&gt;Output is immediately ready for pivot tables and formulas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires finding a tool that specializes in bank statements&lt;/li&gt;
&lt;li&gt;May have upload limits or require an account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Anyone who regularly processes bank statements for accounting, reconciliation, or financial analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which Method Should You Choose?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use Method 1&lt;/strong&gt; if you only need to convert one or two statements and have 15-20 minutes to spare.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Method 2&lt;/strong&gt; if you process the same bank's statements every month and want to build a reusable workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Method 3&lt;/strong&gt; if you need speed, consistency, and analysis-ready data without manual cleanup.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tips for Better PDF to Excel Conversions
&lt;/h2&gt;

&lt;p&gt;Regardless of which method you choose, these tips will improve your results:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use the highest quality PDF&lt;/strong&gt;: If your bank offers "Print to PDF" vs. "Download PDF," choose the download option—it usually has better text encoding.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoid scanned PDFs&lt;/strong&gt;: Screenshots or scanned copies require OCR, which is less accurate than text-based PDFs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check column alignment&lt;/strong&gt;: Before finalizing, verify that all amounts are in the same column. Misaligned numbers break SUM formulas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test with a small sample&lt;/strong&gt;: If you're processing dozens of statements, convert one first to make sure the method works for your bank's format.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep the original PDF&lt;/strong&gt;: Always retain the original statement as your source of truth.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Merged cells in converted bank statements aren't just annoying—they waste hours of manual cleanup time and introduce errors into financial data. Whether you choose manual cleanup, formula-based restructuring, or a specialized converter, the key is to end up with a stable column structure that Excel can actually work with.&lt;/p&gt;

&lt;p&gt;Once you have clean data, you can use pivot tables, VLOOKUP, and conditional formatting to analyze spending patterns, reconcile accounts, and generate reports—without fighting Excel every step of the way.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What challenges have you faced when converting bank statements? Share your experience in the comments below!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>pdf</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
