<?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: Rostyslav</title>
    <description>The latest articles on DEV Community by Rostyslav (@rostyslavdzhohola).</description>
    <link>https://dev.to/rostyslavdzhohola</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%2F568749%2F6462daa1-7e5b-4107-be48-e7d61d8491cc.png</url>
      <title>DEV Community: Rostyslav</title>
      <link>https://dev.to/rostyslavdzhohola</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rostyslavdzhohola"/>
    <language>en</language>
    <item>
      <title>Receipt Sloth: let Gemma file it, I'll get to it eventually</title>
      <dc:creator>Rostyslav</dc:creator>
      <pubDate>Tue, 20 Oct 2026 21:26:31 +0000</pubDate>
      <link>https://dev.to/rostyslavdzhohola/receipt-sloth-let-gemma-file-it-ill-get-to-it-eventually-fn9</link>
      <guid>https://dev.to/rostyslavdzhohola/receipt-sloth-let-gemma-file-it-ill-get-to-it-eventually-fn9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Receipt Sloth turns a folder of receipt text files and exported emails into something I can stop thinking about. It reads the merchant, date, total, and currency, picks a spending category, and copies each receipt into a dated folder with a readable name. It also writes a CSV and a monthly summary.&lt;/p&gt;

&lt;p&gt;The little job I wanted to automate is the gap between "I'll keep this receipt" and "I'll actually file it." A folder full of &lt;code&gt;scan (2).txt&lt;/code&gt; files is easy to create and easy to ignore.&lt;/p&gt;

&lt;p&gt;Gemma runs locally through Ollama. The default keeps the originals. A dry run shows every destination before anything is written, duplicates are skipped, and missing dates or totals go into &lt;code&gt;needs-review/&lt;/code&gt;. The sloth still has to look at those.&lt;/p&gt;

&lt;p&gt;This version handles text, Markdown, and &lt;code&gt;.eml&lt;/code&gt; emails. Photos and PDFs need text extraction first. All receipts in the demo are fictional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/RostyslavDzhohola/receipt-sloth/main/docs/demo.mp4" rel="noopener noreferrer"&gt;Watch the real terminal demo, MP4&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdu3frd40x4l6yaoqfots.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdu3frd40x4l6yaoqfots.gif" alt="A recording of real commands: preview the inbox, run local Gemma, inspect the sorted folders and summary" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The recording shows a rules preview, an actual Gemma run, the resulting folder tree, and the spending summary. Commands were typed automatically; every command really executed. The video preserves the recorded timing. No hosted application or paid API is involved.&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%2F4kvftqe1l94tj51toykf.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%2F4kvftqe1l94tj51toykf.png" alt="Actual rules dry run on the full sample folder" width="800" height="767"&gt;&lt;/a&gt;&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%2Fbykadqm9xjvmbx10fgwb.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%2Fbykadqm9xjvmbx10fgwb.png" alt="Actual Gemma run through local Ollama" width="800" height="571"&gt;&lt;/a&gt;&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%2Fw0xwp1hmttv3jlrfcz2u.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%2Fw0xwp1hmttv3jlrfcz2u.png" alt="The real sorted archive and monthly summary" width="800" height="1125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/RostyslavDzhohola" rel="noopener noreferrer"&gt;
        RostyslavDzhohola
      &lt;/a&gt; / &lt;a href="https://github.com/RostyslavDzhohola/receipt-sloth" rel="noopener noreferrer"&gt;
        receipt-sloth
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Lazy, offline receipt sorter powered by local Gemma (Ollama), with a no-model fallback
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;receipt-sloth&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A messy receipt folder goes in. Dated folders, readable filenames, a CSV, and a monthly spending summary come out. Local Gemma reads text and email receipts so you can put off the filing, rather than put off the receipts.&lt;/p&gt;
&lt;p&gt;Built for &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19" rel="nofollow"&gt;DEV's Hacktoberfest Week 3: Slow and Steady&lt;/a&gt;, entering &lt;strong&gt;Best Use of Gemma&lt;/strong&gt;. This new project started and finished on October 20, 2026, during the October 19–25 entry window. There are no post-deadline commits as of this release. Any commits after October 25, 2026 at 11:59 PM PDT must be identified here.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/RostyslavDzhohola/receipt-sloth/docs/screenshots/gemma-run.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FRostyslavDzhohola%2Freceipt-sloth%2FHEAD%2Fdocs%2Fscreenshots%2Fgemma-run.png" alt="A real local Gemma run"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/RostyslavDzhohola/receipt-sloth/main/docs/demo.mp4" rel="nofollow noopener noreferrer"&gt;Watch the terminal video&lt;/a&gt; or view the &lt;a href="https://github.com/RostyslavDzhohola/receipt-sloth/docs/demo.gif" rel="noopener noreferrer"&gt;animated recording&lt;/a&gt;. The &lt;a href="https://github.com/RostyslavDzhohola/receipt-sloth/POST-DRAFT.md" rel="noopener noreferrer"&gt;DEV submission draft&lt;/a&gt; is ready for publication by Ros's publishing bot.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Try it without installing anything&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Python 3.9 or newer. The app and tests use only the standard library. No Python packages, API keys, accounts, or paid services are needed.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/RostyslavDzhohola/receipt-sloth.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/RostyslavDzhohola/receipt-sloth" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://github.com/RostyslavDzhohola/receipt-sloth" rel="noopener noreferrer"&gt;Source and setup instructions&lt;/a&gt;. The original application and fictional fixtures are MIT licensed. The project started and finished on October 20, 2026, inside this challenge's build window.&lt;/p&gt;

&lt;p&gt;Python 3.9 or newer is enough to try the rules engine. There are no third-party Python dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/RostyslavDzhohola/receipt-sloth.git
&lt;span class="nb"&gt;cd &lt;/span&gt;receipt-sloth
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; receipt_sloth samples/ &lt;span class="nt"&gt;--engine&lt;/span&gt; rules &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The app asks &lt;code&gt;gemma4:e2b-it-qat&lt;/code&gt;, served by local Ollama, for a JSON object with five fields. A small validation layer checks the result before the sorter touches a file. A rules engine supplies missing fields and keeps the app useful when Ollama is unavailable.&lt;/p&gt;

&lt;p&gt;On a Mac with Ollama installed and running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma4:e2b-it-qat
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; receipt_sloth samples/ &lt;span class="nt"&gt;--engine&lt;/span&gt; gemma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real Gemma runs worked. That is separate from the unit tests, which use a fake HTTP server. The release passes 71 tests on Python 3.9, 3.11, and 3.15, including malformed model replies, unsafe output paths, and reports surviving repeated move runs.&lt;/p&gt;

&lt;p&gt;The evaluation uses small, hand-labelled fictional fixtures. An all-fields-correct result needs merchant, date, total, currency, and category to match. The same coding agent wrote the engines and receipts, so these numbers are a sanity check, not a benchmark.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evaluation&lt;/th&gt;
&lt;th&gt;Rules&lt;/th&gt;
&lt;th&gt;Gemma with validation and fallback&lt;/th&gt;
&lt;th&gt;Raw Gemma&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original frozen sample set, 11 receipts&lt;/td&gt;
&lt;td&gt;10/11&lt;/td&gt;
&lt;td&gt;11/11&lt;/td&gt;
&lt;td&gt;10/11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original frozen holdout, 6 receipts&lt;/td&gt;
&lt;td&gt;5/6&lt;/td&gt;
&lt;td&gt;5/6&lt;/td&gt;
&lt;td&gt;5/6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewed release, same samples&lt;/td&gt;
&lt;td&gt;10/11&lt;/td&gt;
&lt;td&gt;10/11&lt;/td&gt;
&lt;td&gt;10/11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewed release, same holdout&lt;/td&gt;
&lt;td&gt;5/6&lt;/td&gt;
&lt;td&gt;5/6&lt;/td&gt;
&lt;td&gt;5/6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The original engines were frozen at commit &lt;code&gt;9c6fe3c&lt;/code&gt; before the first holdout score. Once the failures informed fixes, that set stopped being unseen. The later scores are regression checks. Full labels, per-field scores, and raw replies are &lt;a href="https://github.com/RostyslavDzhohola/receipt-sloth/tree/main/eval/results" rel="noopener noreferrer"&gt;in the repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The fresh release evaluation completed all 19 local model requests with zero request failures. Two sample documents needed rules backfill. Both the grocery list and the holdout newsletter were correctly kept for review; the original frozen version had mistakenly accepted the newsletter.&lt;/p&gt;

&lt;p&gt;One failure is a useful reason to keep the guard. On a pizzeria receipt, Gemma invented a &lt;strong&gt;30.33 EUR&lt;/strong&gt; total where the page said &lt;strong&gt;27.80 EUR&lt;/strong&gt;. It also read an ambiguous October date as March. The amount check rejected the invented total. The rules parser could not read the Italian &lt;code&gt;TOTALE&lt;/code&gt; label, so the receipt went to review.&lt;/p&gt;

&lt;p&gt;There is a cost to being cautious. Gemma repaired an OCR date, &lt;code&gt;1O/09/26&lt;/code&gt;, but the parser could not independently read that date. The release sends it to review, which lowers the sample score from 11/11 to 10/11. I prefer an extra item to inspect over a quietly invented filing date. A wrong number that happens to appear elsewhere on the receipt can still pass validation, so the CSV deserves a human check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;A receipt contains more than a price. It can say where someone went, when they went there, and what they bought. Local inference lets the sorting happen without sending that text to an API provider.&lt;/p&gt;

&lt;p&gt;Gemma's downloadable weights make that possible on a laptop. Ollama serves the model locally, and the app only accepts loopback endpoints, bypasses proxies, and refuses redirects. After the initial model download, the workflow runs without internet access. There is no per-receipt API bill, though it still uses your own hardware and electricity.&lt;/p&gt;

&lt;p&gt;The Python code, extraction prompt, validation, and labels are all inspectable. Someone can change the categories, inspect a raw reply, or try another downloaded local model against the same fixtures. A closed API would make offline operation depend on somebody else's service.&lt;/p&gt;

&lt;p&gt;Gemma is Google's open-weight model, and Ollama is MIT licensed. The installed &lt;code&gt;gemma4:e2b-it-qat&lt;/code&gt; artifact reports Apache License 2.0 through Ollama's model metadata; the repository includes that reported license. Model weights are downloaded separately. No non-trivial application code was copied from an earlier project.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;The app was built by AI coding agents, Claude Opus 5.5 then Codex GPT-6.1-Sol, orchestrated in T3 Code, for me, Ros. They implemented the app, ran local Gemma, reviewed the code, fixed the valid findings, and prepared this entry. That help is part of the process, including this write-up.&lt;/p&gt;

&lt;p&gt;The session logs can be shared on request after a privacy review. No public session link is included here. The code, test cases, saved raw model replies, and terminal recordings are available now so readers can check the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;The demo files three unique receipts, skips one duplicate, and leaves one grocery list for review. The larger sample folder exercises multiple currencies, email exports, an unreadable date, and repeated runs. That is what has actually been exercised, not a claim about weeks of personal use.&lt;/p&gt;

&lt;p&gt;For a folder of 30 receipts, saving 20 seconds of manual naming and filing per receipt would be about 10 minutes. That is an estimate, not a measured user study, and review time reduces the saving. The payoff I care about is simpler: one command does the routine copying and naming, and the awkward cases wait in one obvious place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;Best Use of Gemma.&lt;/p&gt;

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