<?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: Marcus Richards</title>
    <description>The latest articles on DEV Community by Marcus Richards (@fetchdoge_).</description>
    <link>https://dev.to/fetchdoge_</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%2F4183437%2F254e317d-3b08-4b56-aefc-69364674ecf8.jpg</url>
      <title>DEV Community: Marcus Richards</title>
      <link>https://dev.to/fetchdoge_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fetchdoge_"/>
    <language>en</language>
    <item>
      <title>The Receipt Sorter That Shows Its Work</title>
      <dc:creator>Marcus Richards</dc:creator>
      <pubDate>Wed, 14 Oct 2026 02:40:27 +0000</pubDate>
      <link>https://dev.to/fetchdoge_/the-receipt-sorter-that-shows-its-work-4gdf</link>
      <guid>https://dev.to/fetchdoge_/the-receipt-sorter-that-shows-its-work-4gdf</guid>
      <description>&lt;p&gt;&lt;em&gt;Submission draft for the Hacktoberfest Open-Source AI Challenge, Week 2: Side Hustle. Prize category: Best Use of Gemma. This project was built with AI assistance; the build notes below say what the AI got wrong, too.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with receipt sorters isn't extraction
&lt;/h2&gt;

&lt;p&gt;There are already good tools that read a receipt and hand you the numbers. The problem starts a month later, at tax time, when a number in your spreadsheet looks wrong and there is no way to answer the only question that matters: &lt;em&gt;which document did this come from, and what actually read it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I built Receipt Sorter around the audit trail instead of around the extraction. It is a small command-line tool — Python, standard library only — that turns a folder of receipts and invoices into a &lt;code&gt;ledger.csv&lt;/code&gt;. A local open-weight model (Gemma, served by Ollama) reads each document. Images go through vision; plain-text invoices are read directly. Five fields per document: vendor, date, amount, currency, category.&lt;/p&gt;

&lt;p&gt;Then the part I actually trust: the validation layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation, not vibes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dates must be real ISO dates. Amounts must be positive. Currency must be a 3-letter code. Category must come from a fixed list of 12 — no free text.&lt;/li&gt;
&lt;li&gt;Anything missing or invalid is flagged &lt;code&gt;needs_review&lt;/code&gt;, with the reason written next to it. The tool never guesses a value to make a row look complete.&lt;/li&gt;
&lt;li&gt;Duplicates are skipped, defined strictly: same vendor, date, amount, and currency.&lt;/li&gt;
&lt;li&gt;Every ledger row carries provenance: the source file, the model used, and the extraction timestamp.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Financial documents deserve that care. A receipt shows who was paid, how much, and when — which is also why the tool runs entirely on your own machine. Nothing is uploaded, there is no per-seat API cost, and it works offline once the model is downloaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the demo actually showed — stated carefully
&lt;/h2&gt;

&lt;p&gt;Status, precisely: &lt;strong&gt;built and fixture-tested&lt;/strong&gt;. It is released publicly alongside this post, and it has never touched a real client's receipts.&lt;/p&gt;

&lt;p&gt;On 2026-10-13 I ran it end to end against three sample fixtures created for testing — one rendered grocery receipt as an image, one software invoice as text, one cab receipt as text — and scored the output against a ground-truth file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;15 of 15 fields correct&lt;/strong&gt; — 5 fields across 3 clean fixtures. Zero rows flagged for review.&lt;/li&gt;
&lt;li&gt;Model: &lt;code&gt;gemma3:4b&lt;/code&gt;, fully local, on a small 2-CPU VM.&lt;/li&gt;
&lt;li&gt;Speed, honestly: the warm 3-file run took 2 minutes 58 seconds. A cold single-image call took about 175 seconds, including model load. Small local models are slow, and I would rather publish that than hide it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three clean fixtures prove the pipeline works. They do not prove real-world accuracy, and this post doesn't claim it. Photographed, crumpled, real receipts are the next test, and whatever accuracy shows up there is what gets published.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke while I built it
&lt;/h2&gt;

&lt;p&gt;Two failures worth more than the demo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The model downloads failed their own provenance test.&lt;/strong&gt; The build machine's network intercepted DNS in a way that made &lt;code&gt;ollama pull&lt;/code&gt; refuse the model files. The fix was to fetch each file directly and verify its SHA-256 against the model's manifest before assembling anything — all 9 files across both models, hash-verified. A tool that records where every value came from should hold its own downloads to the same standard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The small model's name lied.&lt;/strong&gt; &lt;code&gt;gemma3:1b&lt;/code&gt; turned out to be text-only in its Ollama packaging — no vision component — so image receipts were rejected until the run moved to &lt;code&gt;gemma3:4b&lt;/code&gt;. The box didn't say that. Testing did.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Run it against messy, real-world photographed receipts and publish the accuracy as measured — good or bad.&lt;/li&gt;
&lt;li&gt;Make the &lt;code&gt;needs_review&lt;/code&gt; queue easier to work through, because flagged rows are where a user's time actually goes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/marsojuji-cmyk/receipt-sorter" rel="noopener noreferrer"&gt;https://github.com/marsojuji-cmyk/receipt-sorter&lt;/a&gt; — the demo ledger, ground truth, and results are in the repo (&lt;code&gt;demo/RESULTS.md&lt;/code&gt;).&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
