<?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: Mokshraj(ssr7)</title>
    <description>The latest articles on DEV Community by Mokshraj(ssr7) (@mokshrajssr7).</description>
    <link>https://dev.to/mokshrajssr7</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%2F4091827%2F8d953bfd-9e35-4696-8a5c-2471300849ef.jpg</url>
      <title>DEV Community: Mokshraj(ssr7)</title>
      <link>https://dev.to/mokshrajssr7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mokshrajssr7"/>
    <language>en</language>
    <item>
      <title>I built a bank reconciliation engine that never uploads your files</title>
      <dc:creator>Mokshraj(ssr7)</dc:creator>
      <pubDate>Mon, 24 Aug 2026 07:44:51 +0000</pubDate>
      <link>https://dev.to/mokshrajssr7/i-built-a-bank-reconciliation-engine-that-never-uploads-your-files-2eep</link>
      <guid>https://dev.to/mokshrajssr7/i-built-a-bank-reconciliation-engine-that-never-uploads-your-files-2eep</guid>
      <description>&lt;p&gt;First post here, so bear with me.&lt;/p&gt;

&lt;p&gt;I've spent the last few weeks building a site full of browser-side calculators and tools, and&lt;br&gt;
somewhere along the way I went down a rabbit hole about what bookkeepers actually spend their&lt;br&gt;
time on. The answer, over and over: reconciliation. Books against the bank statement, every&lt;br&gt;
month, mostly by hand, mostly in Excel.&lt;/p&gt;

&lt;p&gt;There are tools for this. But every one I found — the CSV converters, the SaaS reconcilers, the&lt;br&gt;
inevitable "just paste it into ChatGPT" suggestion — starts the same way: upload the bank&lt;br&gt;
statement. And that's exactly the step a lot of these people can't take. A client's bank&lt;br&gt;
statement might be the most sensitive file a bookkeeper touches. Sending it to some website's&lt;br&gt;
server is either against firm policy or just a bad instinct they've rightly developed.&lt;/p&gt;

&lt;p&gt;So my pitch is almost stupid in its simplicity: this one can't upload anything, because there's&lt;br&gt;
nowhere to upload to. No server side at all. You drop two CSVs — your ledger and the bank&lt;br&gt;
export — and the parsing and matching run entirely in the browser. You can open the network tab&lt;br&gt;
and watch nothing leave.&lt;/p&gt;

&lt;p&gt;It's here if you want to poke at it: &lt;a href="https://stepwisecalc.com/tools/reconciliation" rel="noopener noreferrer"&gt;https://stepwisecalc.com/tools/reconciliation&lt;/a&gt;&lt;br&gt;
(free, no signup — and I'm a developer, not an accountant, so part of why I'm posting is to&lt;br&gt;
find out where it falls apart).&lt;/p&gt;

&lt;p&gt;The rest of this is the four parts that were genuinely fun to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working out which column is which, without trusting headers
&lt;/h2&gt;

&lt;p&gt;My first version trusted header names. That survived about two test files. Banks rename headers&lt;br&gt;
constantly — &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;Txn Date&lt;/code&gt;, &lt;code&gt;Value Date&lt;/code&gt;, &lt;code&gt;Posted&lt;/code&gt; — so matching on names is hopeless.&lt;/p&gt;

&lt;p&gt;What stays consistent is the data itself. Dates look like dates. Amounts look like amounts. So&lt;br&gt;
each column gets scored for date-ness, amount-ness and description-ness, and the best-scoring&lt;br&gt;
assignment wins.&lt;/p&gt;

&lt;p&gt;The case that took me longest: banks that split debits and credits into two separate columns.&lt;br&gt;
The giveaway turned out to be fill patterns — you get two numeric columns where every row has a&lt;br&gt;
value in exactly one of them. When the detector sees that disjoint pattern, it folds the pair&lt;br&gt;
into signed amounts. Same general approach handles &lt;code&gt;(500.00)&lt;/code&gt; meaning negative, trailing&lt;br&gt;
&lt;code&gt;DR&lt;/code&gt;/&lt;code&gt;CR&lt;/code&gt; markers, and day-first vs month-first dates (you disambiguate from the rows where the&lt;br&gt;
day is bigger than 12).&lt;/p&gt;

&lt;h2&gt;
  
  
  Matching in tiers, because fuzzy matching in one pass produces confident garbage
&lt;/h2&gt;

&lt;p&gt;I learned quickly that one big fuzzy match gives you results that look great and are wrong. So&lt;br&gt;
matching runs in three labelled tiers, strictest first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exact — same amount, same date.&lt;/li&gt;
&lt;li&gt;Near-date — same amount within a small date window, because deposits clear days after
they're booked.&lt;/li&gt;
&lt;li&gt;Combined — a bounded subset-sum search in both directions: one bank line that equals the sum
of several book lines (a batched deposit), or the reverse. This is the case that breaks
everyone's VLOOKUP, and honestly it's the reason the tool deserves to exist.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every match carries its tier in the output, so you know exactly how much to trust it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The divide-by-9 trick
&lt;/h2&gt;

&lt;p&gt;This one I stole from audit folklore. If two digits get transposed — 54 typed as 45 — the error&lt;br&gt;
is always divisible by 9. Always. So when two amounts almost match, the engine checks the&lt;br&gt;
difference, and if it divides cleanly by 9 it flags "possible transposed digits" instead of a&lt;br&gt;
bare mismatch. Decades-old accountant knowledge, three lines of TypeScript. Easily my favourite&lt;br&gt;
part of the whole build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that makes it trustworthy (I hope)
&lt;/h2&gt;

&lt;p&gt;Here's the thing that bugged me about fuzzy matching: how do you know the reconciliation as a&lt;br&gt;
whole is right, even if individual matches are heuristic?&lt;/p&gt;

&lt;p&gt;The answer I landed on: the exceptions have to explain the difference. So the engine computes&lt;br&gt;
two numbers completely independently — the difference between the files (books total minus bank&lt;br&gt;
total), and the exception schedule (missing-in-bank minus missing-in-books plus the&lt;br&gt;
mismatches). It only ever claims "fully explained" when the two agree to the cent. If they&lt;br&gt;
disagree, the UI says so instead of quietly absorbing it.&lt;/p&gt;

&lt;p&gt;That property, not any particular match, is what the test suite actually asserts. The matcher&lt;br&gt;
is allowed to be heuristic. The arithmetic isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boring implementation notes
&lt;/h2&gt;

&lt;p&gt;TypeScript. Zero dependencies in the engine, including the CSV parser — RFC 4180 is small&lt;br&gt;
enough that writing the ~40 lines felt more honest than pulling in a library. The reconciler is&lt;br&gt;
one of 350+ tools on the site, all built on the same idea: the computation happens on your&lt;br&gt;
machine, not mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually want from you
&lt;/h2&gt;

&lt;p&gt;A bank export that breaks it. I've tested against constructed files and the arithmetic holds,&lt;br&gt;
but real bank CSVs are their own kind of chaos, and the failure modes are where this gets&lt;br&gt;
better. Tell me in the comments what mangled it and I'll dig in.&lt;/p&gt;

&lt;p&gt;Also a genuine question for anyone who's done bookkeeping work: is CSV enough? If most banks&lt;br&gt;
in your world only give PDF statements, the tool needs a different front door, and I'd rather&lt;br&gt;
find that out now.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
