<?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: Alessio Bianchini</title>
    <description>The latest articles on DEV Community by Alessio Bianchini (@alessio_bianchini).</description>
    <link>https://dev.to/alessio_bianchini</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%2F4184671%2Fff6bfb33-8b7b-4380-860f-64a68ff62943.png</url>
      <title>DEV Community: Alessio Bianchini</title>
      <link>https://dev.to/alessio_bianchini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alessio_bianchini"/>
    <language>en</language>
    <item>
      <title>GitNecromancer: The Open-Source Séance Engine for Dead Code and Repo Graveyards</title>
      <dc:creator>Alessio Bianchini</dc:creator>
      <pubDate>Thu, 29 Oct 2026 08:48:05 +0000</pubDate>
      <link>https://dev.to/alessio_bianchini/gitnecromancer-the-open-source-seance-engine-for-dead-code-and-repo-graveyards-3nem</link>
      <guid>https://dev.to/alessio_bianchini/gitnecromancer-the-open-source-seance-engine-for-dead-code-and-repo-graveyards-3nem</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Every software repository is secretly a graveyard.&lt;/p&gt;

&lt;p&gt;Behind every polished &lt;code&gt;main&lt;/code&gt; branch lies an eerie catacomb of departed code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ambitious feature branch abandoned 8 months ago during an architecture debate (&lt;code&gt;feat/graphql-migration&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The frantic midnight spike that fixed an outage but was never cleaned up (&lt;code&gt;stash@{0}: WIP on hotfix&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The experimental caching layer that stalled when someone left the company (&lt;code&gt;experiment/redis-layer&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As developers, we carry a collective guilt. We look at our branch list, shudder, and whisper: &lt;em&gt;"I really should clean those up..."&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Yet, we never do. Why? Because of a primal fear: &lt;strong&gt;what if there is a vital piece of context or logic trapped inside that dead branch?&lt;/strong&gt; Running a blind &lt;code&gt;git branch -D&lt;/code&gt; feels like desecrating a tomb without knowing who was buried there.&lt;/p&gt;

&lt;p&gt;For the final week of Hacktoberfest 2026 (&lt;em&gt;Ghost in the Machine&lt;/em&gt;), I built &lt;strong&gt;GitNecromancer (💀🔮)&lt;/strong&gt;: an atmospheric open-source developer tool and gothic web dashboard that turns repository cleanup into a supernatural séance powered by &lt;strong&gt;Google Gemma 2 2B&lt;/strong&gt; running locally via &lt;strong&gt;Ollama&lt;/strong&gt;, &lt;strong&gt;Sentry Agent Tracing&lt;/strong&gt;, &lt;strong&gt;ElevenLabs&lt;/strong&gt; eerie audio synthesis, and &lt;strong&gt;Render&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of staring at stale git logs, GitNecromancer allows you to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Survey the Graveyard&lt;/strong&gt;: Safely inspect unmerged branches, orphaned stashes, and commit stagnation across your repositories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conduct an AI Séance&lt;/strong&gt;: Summon the spirit of any deceased code artifact. Using &lt;strong&gt;Gemma 2 2B&lt;/strong&gt;, the ghost speaks in the first person—confessing who created it, what hopeful mission it had, and the tragic conflict or priority shift that killed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Listen to the Ghost Whisper&lt;/strong&gt;: Hear the spirit speak through eerie atmospheric voice synthesis generated via &lt;strong&gt;ElevenLabs&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose Its Eternal Fate&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚡ Reincarnate (Resurrect)&lt;/strong&gt;: Gemma analyzes the ancient diff against current &lt;code&gt;HEAD&lt;/code&gt; and generates a clean unified patch ready for &lt;code&gt;git apply&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🪦 Grant Final Rites (The Git Ofrenda)&lt;/strong&gt;: Provide the code with honorable closure. GitNecromancer writes a commemorative ASCII tombstone into &lt;code&gt;OFRENDA.md&lt;/code&gt; and safely purges the branch from git memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Experience the live interactive dashboard immediately without installing anything:&lt;br&gt;&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://git-necromancer.onrender.com" rel="noopener noreferrer"&gt;https://git-necromancer.onrender.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitNecromancer operates both as an obsidian-dark web dashboard called &lt;strong&gt;"The Git Catacombs"&lt;/strong&gt; and a rich terminal CLI.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. The Terminal Séance
&lt;/h3&gt;

&lt;p&gt;Running &lt;code&gt;python cli.py scan&lt;/code&gt; surveys the local graveyard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   .---.       GitNecromancer (💀🔮)
  /     \      The Open-Source Séance Engine for Dead Code
 | () () |     Hacktoberfest 2026: Ghost in the Machine
  \  ^  /
   |||||

🔍 Surveying repo graveyard at: . ...

                 🪦 Repository Graveyard (Restless Code Souls)                 
┌──────────┬──────────────────────┬─────────────┬───────────┬──────────────────────────────────────────┐
│ Type     │ Artifact / Branch    │ Creator     │ Purgatory │ Cause of Death                           │
├──────────┼──────────────────────┼─────────────┼───────────┼──────────────────────────────────────────┤
│ BRANCH   │ feat/graphql-migr... │ Devon Vance │  255 days │ Architecture churn: team decided REST    │
│ BRANCH   │ experiment/redis-... │ Sarah Connor│  207 days │ Memory leak in staging: rolled back      │
│ STASH    │ stash@{0}            │ Alessio B.  │  161 days │ Interrupted hotfix: developer switched   │
└──────────┴──────────────────────┴─────────────┴───────────┴──────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When you channel a spirit with &lt;code&gt;python cli.py seance feat/graphql-migration&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🕯️ Lighting candles for: feat/graphql-migration
Summoning local spirit via Gemma 2 2B (Ollama)...

┌──────────────────── 👻 Spirit of feat/graphql-migration ────────────────────┐
│ "I was summoned into existence 255 days ago by Devon Vance. I still recall  │
│ the frantic keystrokes of 'WIP: switch users endpoint to GraphQL schema' in │
│ feat/graphql-migration. Alas, priorities shifted like mist in the night,    │
│ and my branch was left to wither in the cold purgatory of git history."     │
│                                                                             │
│ 🔍 Autopsy: The pull request was never opened; context switching and        │
│ architectural drift doomed feat/graphql-migration.                          │
│ 🔮 Suggested Fate: MEMORIALIZE (Difficulty: MEDIUM)                         │
│ ⚡ Model: gemma2:2b (local) | Tokens: 110 | Traced via Sentry Agent Tracing │
└─────────────────────────────────────────────────────────────────────────────┘

Ritual choices:
  - Resurrect: python cli.py resurrect feat/graphql-migration
  - Grant Peace: python cli.py exorcise feat/graphql-migration --delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. The Git Catacombs (Web Dashboard)
&lt;/h3&gt;

&lt;p&gt;Launching &lt;code&gt;python cli.py web&lt;/code&gt; (or visiting the &lt;a href="https://git-necromancer.onrender.com" rel="noopener noreferrer"&gt;live Render deployment&lt;/a&gt;) opens the gothic dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Graveyard Cards&lt;/strong&gt;: Displays time in purgatory, creator, and last commit preview.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive Séance Modal&lt;/strong&gt;: Features a flickering candle animation, streaming monologue, and an audio player for ElevenLabs ghost whispering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-Click Patch &amp;amp; Ofrenda&lt;/strong&gt;: Copy resurrection patches or grant final rites with instant DOM updates.&lt;/li&gt;
&lt;/ul&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/alessiobianchini" rel="noopener noreferrer"&gt;
        alessiobianchini
      &lt;/a&gt; / &lt;a href="https://github.com/alessiobianchini/git-necromancer" rel="noopener noreferrer"&gt;
        git-necromancer
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The Open-Source Séance Engine for Dead Code, Abandoned Branches, and Repo Graveyards (Hacktoberfest 2026: Ghost in the Machine)
    &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;GitNecromancer (💀🔮)&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://opensource.org/licenses/MIT" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e9f356b8051ecbbe01b65a70ff4c1ad63d9ef2ca3cb465b6f2ad1bafb1080bb4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d707572706c652e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/b9e237f8fa1b775eb30febbc1dc440043630b4b084f4bf5604820d81bfbcd18b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4861636b746f62657266657374253230323032362d47686f7374253230696e2532307468652532304d616368696e652d6666366236622e737667" alt="Hacktoberfest 2026"&gt;&lt;/a&gt;
&lt;a href="https://ollama.com/library/gemma2" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e4442a7294938083d608110c05e9fcd0b7add814d76370319e41d5afb9f0f2d8/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d6f64656c2d47656d6d612532303225323032422d3432383546342e737667" alt="Gemma 2 2B"&gt;&lt;/a&gt;
&lt;a href="https://render.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d9b2b5643442f04b7a27dcc14df747ac130ae4af4a8ddc8e56ef32c5995b19b7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4465706c6f792d52656e6465722d3436453342372e737667" alt="Render Deploy"&gt;&lt;/a&gt;
&lt;a href="https://sentry.io" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/57b2584128ba6711961fc09cbbd393273a68ecf9f313cc7ba55f4a021ab866cb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53656e7472792d4167656e7425323054726163696e672d3336324435392e737667" alt="Sentry Agent Tracing"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Open-Source Séance Engine for Dead Code, Abandoned Branches, and Repo Graveyards.&lt;/strong&gt;&lt;br&gt;
Built for the &lt;strong&gt;Hacktoberfest 2026 Open-Source AI Challenge Week 4: "Ghost in the Machine"&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;🔮 &lt;strong&gt;Live Cloud Demo&lt;/strong&gt;: &lt;a href="https://git-necromancer.onrender.com" rel="nofollow noopener noreferrer"&gt;https://git-necromancer.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🪦 The Problem: Every Repo is a Cemetery&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Behind every active &lt;code&gt;main&lt;/code&gt; branch lies an eerie underworld of forgotten artifacts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Feature branches abandoned 9 months ago after a refactor (&lt;code&gt;feat/oauth2-rewrite&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Experimental spikes that died in merge conflicts (&lt;code&gt;experiment/vector-search&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Ghostly stashes forgotten since a frantic production hotfix (&lt;code&gt;stash@{0}: WIP on bugfix&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Developers rarely clean these up because of a primal fear: &lt;em&gt;"What if there's vital logic or context hidden in that branch?"&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitNecromancer (💀🔮)&lt;/strong&gt; solves this by turning repo maintenance into an atmospheric, gothic séance powered by &lt;strong&gt;Google Gemma 2 2B&lt;/strong&gt; (offline via Ollama), &lt;strong&gt;Sentry Agent Tracing&lt;/strong&gt;, &lt;strong&gt;ElevenLabs&lt;/strong&gt; eerie voice synthesis, and &lt;strong&gt;Render&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🔍 Graveyard Surveyor&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/alessiobianchini/git-necromancer" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The entire codebase is open-source and hosted on GitHub:&lt;br&gt;&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://github.com/alessiobianchini/git-necromancer" rel="noopener noreferrer"&gt;https://github.com/alessiobianchini/git-necromancer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;


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

&lt;p&gt;GitNecromancer was engineered with a strict &lt;strong&gt;anti-bloat, local-first&lt;/strong&gt; architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git-necromancer/
├── cli.py               # Utilitarian, gothic Rich terminal CLI
├── render.yaml          # Zero-config Render cloud blueprint
├── app/
│   ├── config.py        # Settings &amp;amp; environment autodetection
│   ├── cemetery.py      # Git cemetery scanner (branches, stashes, churn)
│   ├── seance.py        # Gemma 2 2B prompt engineering &amp;amp; Ollama client
│   ├── resurrection.py  # Patch synthesis &amp;amp; OFRENDA.md memorial generator
│   ├── voice.py         # ElevenLabs audio generation &amp;amp; disk caching
│   ├── telemetry.py     # Sentry OpenTelemetry agent tracing
│   └── web/             # FastAPI gothic web dashboard &amp;amp; Jinja2 templates
└── tests/               # 29/29 passing unit &amp;amp; integration tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Local Open-Weight AI with Google Gemma 2 2B
&lt;/h3&gt;

&lt;p&gt;Code privacy is non-negotiable. Developers cannot paste proprietary codebases and internal commit messages into public cloud APIs just to clean up dead branches.&lt;/p&gt;

&lt;p&gt;GitNecromancer uses &lt;strong&gt;Google Gemma 2 2B&lt;/strong&gt; running 100% offline via &lt;strong&gt;Ollama&lt;/strong&gt;. Gemma 2 2B proved to be the ideal open-weight model for this challenge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight footprint&lt;/strong&gt;: Runs comfortably on consumer laptops and developer machines with less than 2GB of VRAM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creative voice &amp;amp; persona&lt;/strong&gt;: When prompted with git metadata (author, branch name, commit message, time in purgatory, diff snippet), Gemma adopts a gothic, atmospheric first-person persona while maintaining razor-sharp technical diagnostic accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero cloud leaks &amp;amp; $0.00 cost&lt;/strong&gt;: All inference happens on &lt;code&gt;localhost:11434&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Sentry Agent Tracing &amp;amp; OpenTelemetry Instrumentation
&lt;/h3&gt;

&lt;p&gt;Every spiritual invocation is observed through &lt;strong&gt;Sentry Agent Tracing&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using OpenTelemetry spans (&lt;code&gt;gen_ai.chat&lt;/code&gt;, &lt;code&gt;seance:summon&lt;/code&gt;), GitNecromancer captures prompt and completion token counts, latency profiles, and model configurations.&lt;/li&gt;
&lt;li&gt;Spans record whether the séance channeled a local Ollama model or utilized the built-in spectral heuristic fallback.&lt;/li&gt;
&lt;li&gt;If Sentry is not configured, the telemetry module acts as a transparent, zero-overhead no-op.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Ghost Voice Synthesis via ElevenLabs
&lt;/h3&gt;

&lt;p&gt;To make the séance truly unforgettable, GitNecromancer integrates with &lt;strong&gt;ElevenLabs&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first-person monologue generated by Gemma is transformed into an eerie, whispering audio confession.&lt;/li&gt;
&lt;li&gt;Audio outputs are deterministically hashed and cached on disk (&lt;code&gt;audio_cache/&lt;/code&gt;) to eliminate duplicate API requests and conserve credits.&lt;/li&gt;
&lt;li&gt;If no API key is provided, the system gracefully degrades to text-only mode without warnings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Deployable on Render ("The Git Catacombs")
&lt;/h3&gt;

&lt;p&gt;To allow developers and judges to experience GitNecromancer immediately without installing Ollama locally, the web dashboard is packaged with a &lt;code&gt;render.yaml&lt;/code&gt; blueprint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs FastAPI + Uvicorn on Python 3.11+.&lt;/li&gt;
&lt;li&gt;Includes an automatic &lt;strong&gt;Demo Graveyard&lt;/strong&gt; containing realistic abandoned branches (&lt;code&gt;feat/graphql-migration&lt;/code&gt;, &lt;code&gt;experiment/redis-layer&lt;/code&gt;, &lt;code&gt;stash@{0}&lt;/code&gt;) so anyone can hold séances in the cloud.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Honorable Final Rites: The Git Ofrenda
&lt;/h3&gt;

&lt;p&gt;In Mexican culture, &lt;em&gt;Día de los Muertos&lt;/em&gt; celebrates departed souls rather than fearing them. &lt;/p&gt;

&lt;p&gt;GitNecromancer embraces this through &lt;strong&gt;The Git Ofrenda&lt;/strong&gt;: when you grant peace to an abandoned branch (&lt;code&gt;python cli.py exorcise &amp;lt;branch&amp;gt; --delete&lt;/code&gt;), it carves an ASCII headstone into &lt;code&gt;OFRENDA.md&lt;/code&gt;, recording the branch name, creator, and epitaph before safely deleting the branch. You keep the memory and lessons learned forever without cluttering your git graph.&lt;/p&gt;




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

&lt;p&gt;The "Ghost in the Machine" theme is often treated as a spooky trope, but in software engineering, ghosts are very real: they are the legacy code, forgotten architectural decisions, and abandoned branches that haunt our repositories.&lt;/p&gt;

&lt;p&gt;This project demonstrated why &lt;strong&gt;open-weight models like Gemma 2&lt;/strong&gt; are essential for the future of software development:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;: Proprietary repositories contain corporate trade secrets and intellectual property. Open-weight models allow developers to build AI-assisted tools that run entirely air-gapped on developer workstations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Affordability&lt;/strong&gt;: Building local developer utilities shouldn't require monthly SaaS subscriptions or token metering. A local Gemma 2 2B model costs $0.00 to run for a million séances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hackability &amp;amp; Joy&lt;/strong&gt;: Open tooling allows us to take serious technical problems—like branch drift, merge debt, and repository rot—and turn them into creative, joyful, and memorable experiences.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Spirit of It
&lt;/h2&gt;

&lt;p&gt;The breakthrough moment happened at midnight while running the first séance against a dusty branch from 2024. &lt;/p&gt;

&lt;p&gt;Seeing the flickering ASCII candle appear in the terminal, followed by Gemma 2 delivering a melancholic, first-person confession:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I was summoned into existence 255 days ago by Devon Vance. I still recall the frantic keystrokes of 'WIP: switch users endpoint to GraphQL schema'... Alas, priorities shifted like mist in the night..."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every developer on the team immediately understood. We didn't just delete a branch; we listened to its story, extracted what was valuable, and laid it to rest with dignity. It turned a chore into an unforgettable ritual.&lt;/p&gt;




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

&lt;p&gt;I am submitting GitNecromancer for the following Hacktoberfest 2026 categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🏆 &lt;strong&gt;Best Use of Gemma ($200)&lt;/strong&gt;: Local, offline inference via &lt;code&gt;gemma2:2b&lt;/code&gt; using Ollama for persona embodiment, architectural autopsies, and patch synthesis.&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Best Use of Render ($200)&lt;/strong&gt;: Deployed cloud service using &lt;code&gt;render.yaml&lt;/code&gt; with the interactive Git Catacombs web dashboard at &lt;a href="https://git-necromancer.onrender.com" rel="noopener noreferrer"&gt;https://git-necromancer.onrender.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Best Use of Sentry Agent Tracing ($100)&lt;/strong&gt;: Full OpenTelemetry &lt;code&gt;gen_ai.chat&lt;/code&gt; spans instrumenting token usage, latency, and séance execution.&lt;/li&gt;
&lt;li&gt;🎙️ &lt;strong&gt;Best Use of ElevenLabs ($100)&lt;/strong&gt;: Eerie voice synthesis rendering Gemma's spectral monologues into whispered confessions.&lt;/li&gt;
&lt;li&gt;👑 &lt;strong&gt;Overall Week 4 Winner ($250)&lt;/strong&gt;: An authentic, human interpretation of the "Ghost in the Machine" theme solving a universal developer pain point.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built with 💀 by &lt;a href="https://github.com/alessiobianchini" rel="noopener noreferrer"&gt;Alessio Bianchini&lt;/a&gt;. Let me know in the comments: what is the oldest dead branch haunting your repository right now?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>render</category>
    </item>
    <item>
      <title>SlothLog: Why I Built an Offline, AI-Powered Timesheet Generator for Lazy Developers</title>
      <dc:creator>Alessio Bianchini</dc:creator>
      <pubDate>Wed, 21 Oct 2026 13:06:14 +0000</pubDate>
      <link>https://dev.to/alessio_bianchini/slothlog-why-i-built-an-offline-ai-powered-timesheet-generator-for-lazy-developers-2mak</link>
      <guid>https://dev.to/alessio_bianchini/slothlog-why-i-built-an-offline-ai-powered-timesheet-generator-for-lazy-developers-2mak</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;Every Friday afternoon at 4:30 PM, an invisible cloud of collective dread descends upon software engineering teams: &lt;strong&gt;logging our hours into Jira, Azure DevOps, or corporate time trackers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You open your tracking tool, stare at a blank grid of empty days, and begin the painful archaeological dig through your week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"What on earth was I committing at 10:15 AM on Tuesday?"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Which ticket ID corresponded to that subtle CORS regression?"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"How can I explain three hours of debugging a race condition to a non-technical project manager without sounding defensive?"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? Developers either spend 45 minutes of precious Friday time reconstructing history, or they guess haphazardly, slapping &lt;code&gt;8.0h - General Development&lt;/code&gt; across every day of the week.&lt;/p&gt;

&lt;p&gt;I wanted to be &lt;strong&gt;completely, unapologetically lazy&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;SlothLog&lt;/strong&gt; (🦥): an offline, privacy-first developer utility that inspects your local git commits across multiple repositories, clusters activity by ticket identifier, estimates work session duration, and synthesizes executive-ready timesheet summaries using &lt;strong&gt;Google Gemma 2 2B&lt;/strong&gt; running locally via &lt;strong&gt;Ollama&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With SlothLog:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You click one button or run &lt;code&gt;python cli.py scan&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It detects ticket IDs (&lt;code&gt;AB#4920&lt;/code&gt;, &lt;code&gt;PROJ-104&lt;/code&gt;, &lt;code&gt;#12&lt;/code&gt;) and session bursts.&lt;/li&gt;
&lt;li&gt;Gemma 2 turns messy commits (&lt;code&gt;"fix bug"&lt;/code&gt;, &lt;code&gt;"wip typo"&lt;/code&gt;, &lt;code&gt;"more changes"&lt;/code&gt;) into clean, professional work notes (&lt;code&gt;"Refactored invoice calculation engine and added VAT reverse charge support"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;You click &lt;strong&gt;Copy for Jira / DevOps&lt;/strong&gt; and paste it directly into your tracker. You are done in 10 seconds.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Here is SlothLog in action running locally with its utilitarian dark dashboard:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The SlothLog Dashboard
&lt;/h3&gt;

&lt;p&gt;The web dashboard features an interactive date selector, hour stepper (&lt;code&gt;+&lt;/code&gt; / &lt;code&gt;-&lt;/code&gt; 0.25h), tracked repository manager, and single-click copy buttons with instant toast confirmations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------------------------------------------------------------------+
| 🦥 SlothLog    Offline AI Timesheets for Lazy Developers    [Gemma 2 2B] [Sentry Active]|
+---------------------------------------------------------------------------------------+
| [ Total Logged: 7.00 h ]    [ Clustered Tickets: 3 ]    [ Effort Saved: ~45 mins ]    |
+---------------------------------------------------------------------------------------+
| [AB#4920] billing-service                                             [-] 3.25h [+]  |
| "Refactored invoice calculation engine, added VAT reverse charge support for European |
|  B2B invoices, and implemented idempotency tests."                                    |
| [ 3 underlying commits ]                                                              |
| [📋 Copy for Jira / DevOps]   [📄 Copy Summary]                                       |
+---------------------------------------------------------------------------------------+
| [PROJ-104] portal-frontend                                            [-] 2.50h [+]  |
| "Implemented OAuth2 PKCE login flow callback and added refresh token rotation."       |
| [📋 Copy for Jira / DevOps]   [📄 Copy Summary]                                       |
+---------------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Standalone CLI Flow
&lt;/h3&gt;

&lt;p&gt;If you prefer staying in the terminal:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python cli.py scan &lt;span class="nt"&gt;--date&lt;/span&gt; 2026-10-20

Scanning 2 repositories &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;: 2026-10-20...

Generated Timesheet:
Date       | Repo            | Ticket              | Hours | Summary
&lt;span class="nt"&gt;-----------&lt;/span&gt;+-----------------+---------------------+-------+------------------------------------------------------------
2026-10-20 | billing-service | AB#4920             | 3.25h | Refactored invoice calculation engine, added VAT reverse...
2026-10-20 | portal-frontend | PROJ-104            | 2.50h | Implemented OAuth2 PKCE login flow callback...
2026-10-20 | billing-service | General Maintenance | 1.25h | Upgraded dependencies, cleaned up unused imports...

Total Hours: 7.00h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can also export to standard CSV with &lt;code&gt;python cli.py export -o timesheet.csv&lt;/code&gt; for HR and payroll imports.&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/alessiobianchini" rel="noopener noreferrer"&gt;
        alessiobianchini
      &lt;/a&gt; / &lt;a href="https://github.com/alessiobianchini/slothlog" rel="noopener noreferrer"&gt;
        slothlog
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Offline, AI-powered developer timesheets for the lazy &amp;amp; steady using Gemma 2 2B, Sentry Tracing, and FastAPI
    &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;🦥 SlothLog&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Turn your git commits into honest, sensible timesheets — without leaking company code to the cloud.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://www.python.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2cfcebb5f7fc5dc7311d3696472ebf7940e38e285979189b38bbb0177998ed51/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31302b2d626c75652e7376673f7374796c653d666c61742d737175617265" alt="Python"&gt;&lt;/a&gt;
&lt;a href="https://github.com/alessiobianchini/slothlog/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/947453b4db0c258a214966d28c03565c044c985129f374ee9bd6863d0d8fdb29/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d656d6572616c642e7376673f7374796c653d666c61742d737175617265" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://github.com/alessiobianchini/slothlog/tests/" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/bb9462e0d77e50eeaf2994a8605e8233e54001ff5c1187ea176860d0f7686395/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d323125323070617373696e672d737563636573732e7376673f7374796c653d666c61742d737175617265" alt="Tests"&gt;&lt;/a&gt;
&lt;a href="https://ollama.ai" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b34169d5a4830ce41f309aaca7bbbbc12071e78066a05e757ba87df1f45bc9e4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d6f64656c2d47656d6d61253230322532303242253230284c6f63616c292d6f72616e67652e7376673f7374796c653d666c61742d737175617265" alt="Local AI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this exists&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Every Friday at 4:30 PM, the same ritual happens across engineering teams:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You open Jira, Azure DevOps, or whatever timesheet tool your company uses.&lt;/li&gt;
&lt;li&gt;You stare at the blank week grid and wonder: &lt;em&gt;"What on earth was I doing on Tuesday morning?"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;You open your terminal, run &lt;code&gt;git log&lt;/code&gt;, scroll frantically through branch names, and end up roughly guessing numbers to hit 8 hours a day.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of us waste 30 to 45 minutes every week doing this archaeological reconstruction. And while modern AI tools can summarize text, &lt;strong&gt;pasting internal commit messages and proprietary code into third-party cloud APIs is a fast track to an unpleasant chat with Infosec&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SlothLog&lt;/strong&gt; fixes this by staying completely local:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It scans your local git repos for commits made on a…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/alessiobianchini/slothlog" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The full open-source codebase is hosted on GitHub:&lt;br&gt;&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://github.com/alessiobianchini/slothlog" rel="noopener noreferrer"&gt;https://github.com/alessiobianchini/slothlog&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;


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

&lt;p&gt;SlothLog was designed with a strict &lt;strong&gt;anti-overengineering&lt;/strong&gt; philosophy: no heavy orchestrators (no LangChain, no LlamaIndex), no external cloud dependencies, and zero telemetry bloat.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Git Inspection &amp;amp; Ticket Clustering
&lt;/h3&gt;

&lt;p&gt;The core engine inspects local git histories using native &lt;code&gt;git log --format=%H|%aI|%an|%ae|%s --date-order&lt;/code&gt;. Commits are grouped by ticket identifier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Azure DevOps&lt;/strong&gt;: Regex matching &lt;code&gt;AB#\d+&lt;/code&gt; or &lt;code&gt;#\d+&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jira&lt;/strong&gt;: Regex matching &lt;code&gt;[A-Z]+-\d+&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: Regex matching &lt;code&gt;gh-\d+&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Untagged&lt;/strong&gt;: Fallback to &lt;code&gt;General Maintenance&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To estimate time accurately without snooping or spyware, SlothLog uses a &lt;strong&gt;session clustering algorithm&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commits within &lt;strong&gt;90 minutes&lt;/strong&gt; of each other are grouped into a continuous work session.&lt;/li&gt;
&lt;li&gt;An initial &lt;strong&gt;30-minute burst buffer&lt;/strong&gt; is added per session to account for active reading, debugging, and coding prior to the first commit.&lt;/li&gt;
&lt;li&gt;Hours are snapped to realistic quarter-hour increments (0.25h) with a minimum 0.5h threshold.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Local Open-Weight Synthesis with Gemma 2 2B
&lt;/h3&gt;

&lt;p&gt;Enterprise developers cannot send proprietary commit messages, internal function names, or company ticket contents to third-party closed AI APIs. Privacy is paramount.&lt;/p&gt;

&lt;p&gt;SlothLog queries &lt;strong&gt;Google Gemma 2 2B&lt;/strong&gt; via Ollama on &lt;code&gt;localhost:11434&lt;/code&gt;. Gemma 2 2B is the ideal model for this task: ultra-lightweight, extremely fast on standard laptops, and remarkably proficient at concise extractive summarization without conversational filler.&lt;/p&gt;

&lt;p&gt;Here is the exact prompt that produces crisp 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="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a concise engineering assistant. Summarize these git commit messages into a single professional timesheet accomplishment bullet (1-2 sentences max).
Do not mention commit hashes, dates, or conversational filler. Focus on what was delivered or solved.

Ticket: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticket_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Commits:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;formatted_commits&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Executive Summary:&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Ollama is not running or offline, SlothLog activates a &lt;strong&gt;graceful deterministic fallback&lt;/strong&gt;, generating clean formatted summaries from the raw commits so you are never blocked.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Sentry Agent Tracing
&lt;/h3&gt;

&lt;p&gt;To monitor the local AI execution and observe system health, I instrumented SlothLog with &lt;strong&gt;Sentry SDK's Agent &amp;amp; GenAI Tracing&lt;/strong&gt;:&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;with&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ollama synthesis: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticket_id&lt;/span&gt;&lt;span class="si"&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;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai.model_id&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;gemma2:2b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai.model_provider&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;ollama&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai.prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_eval_count&lt;/span&gt;&lt;span class="sh"&gt;"&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;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai.completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eval_count&lt;/span&gt;&lt;span class="sh"&gt;"&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;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai.total_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai.cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Local open-source inference is free!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every synthesis run tracks token throughput, inference latency, and error states. Even better: Sentry confirms that our AI cost is &lt;strong&gt;\$0.00&lt;/strong&gt;.&lt;/p&gt;




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

&lt;p&gt;Open innovation is the entire reason SlothLog can exist.&lt;/p&gt;

&lt;p&gt;In a corporate environment, developer codebases are strictly confidential. Proprietary APIs from closed vendors pose serious privacy, compliance, and IP risks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You cannot legally pipe commit diffs or commit messages referencing confidential client algorithms or internal infrastructure through external cloud endpoints without strict enterprise agreements.&lt;/li&gt;
&lt;li&gt;Reliance on closed APIs introduces vendor lock-in, API rate limits, unpredictable pricing changes, and sudden deprecations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With open-weight models like &lt;strong&gt;Google Gemma 2&lt;/strong&gt;, the paradigm shifts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Total Sovereignty&lt;/strong&gt;: You can run Gemma 2 2B on an airplane, in an air-gapped corporate intranet, or during an internet outage. Your code never leaves your RAM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic Stability&lt;/strong&gt;: The model weights don't change overnight without your consent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Marginal Cost&lt;/strong&gt;: Logging 10 tickets a day or 10,000 tickets a month costs exactly the same: zero dollars.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open-weight AI gives power back to individual builders.&lt;/p&gt;




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

&lt;p&gt;This entire project — from architecture design and specification to TDD implementation, Sentry tracing, and frontend build — was developed using &lt;strong&gt;Antigravity&lt;/strong&gt; pair-programming.&lt;/p&gt;

&lt;p&gt;You can inspect the complete design spec and step-by-step TDD verification transcript in the project repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design Spec: &lt;a href="https://github.com/alessiobianchini/slothlog/blob/main/docs/superpowers/specs/2026-10-20-slothlog-design.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/superpowers/specs/2026-10-20-slothlog-design.md&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Implementation Plan: &lt;a href="https://github.com/alessiobianchini/slothlog/blob/main/docs/superpowers/plans/2026-10-20-slothlog.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/superpowers/plans/2026-10-20-slothlog.md&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Let's calculate the sloth economics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Manual Timesheet Archaeologist&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding git logs across 3 repos: ~10 minutes&lt;/li&gt;
&lt;li&gt;Calculating start/end times and rounding: ~10 minutes&lt;/li&gt;
&lt;li&gt;Writing creative prose for Jira/Azure DevOps: ~15 minutes&lt;/li&gt;
&lt;li&gt;Form-filling and double-checking: ~10 minutes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: ~45 minutes every Friday (~39 hours per year per engineer)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;With SlothLog&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;python cli.py scan&lt;/code&gt; or click &lt;em&gt;Scan Commits&lt;/em&gt;: ~3 seconds&lt;/li&gt;
&lt;li&gt;Review and click &lt;em&gt;Copy for Jira / DevOps&lt;/em&gt;: ~10 seconds&lt;/li&gt;
&lt;li&gt;Paste into tracker: ~15 seconds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: ~30 seconds&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's &lt;strong&gt;98.8% time saved&lt;/strong&gt;, 39 extra hours per year spent building actual software (or taking a well-deserved nap). Sloths everywhere rejoice.&lt;/p&gt;




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

&lt;p&gt;I am officially entering SlothLog into the following categories for &lt;strong&gt;Hacktoberfest Open-Source AI Challenge Week 3&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma ($200)&lt;/strong&gt;: Local inference using &lt;code&gt;gemma2:2b&lt;/code&gt; via Ollama for privacy-preserving, offline commit summarization without API fees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render ($200)&lt;/strong&gt;: Full cloud blueprint (&lt;code&gt;render.yaml&lt;/code&gt;) with auto-detected environment variables and demo data seeding for instant one-click deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Sentry Agent Tracing ($100)&lt;/strong&gt;: Full OpenTelemetry-compatible &lt;code&gt;gen_ai.chat&lt;/code&gt; spans measuring prompt/completion token metrics, latency, and \$0.00 cost telemetry.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>render</category>
    </item>
    <item>
      <title>I was tired of manual bookkeeping, so I wrote an offline receipt parser with Ollama</title>
      <dc:creator>Alessio Bianchini</dc:creator>
      <pubDate>Wed, 14 Oct 2026 13:32:16 +0000</pubDate>
      <link>https://dev.to/alessio_bianchini/i-was-tired-of-manual-bookkeeping-so-i-wrote-an-offline-receipt-parser-with-ollama-3jn7</link>
      <guid>https://dev.to/alessio_bianchini/i-was-tired-of-manual-bookkeeping-so-i-wrote-an-offline-receipt-parser-with-ollama-3jn7</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Every quarter, freelancers and indie consultants go through the exact same nightmare: a Downloads folder filled with random files—AWS invoices, Uber trip slips, railway tickets, client coffee receipts, and software renewals. &lt;/p&gt;

&lt;p&gt;You end up sitting at your desk opening each file, finding the net amount, hunting down the VAT percentage, and manually typing it into a spreadsheet. &lt;/p&gt;

&lt;p&gt;Commercial scanners exist, but they have two dealbreakers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Subscription pricing:&lt;/strong&gt; They charge $25 to $40 a month. Freelancers only do bookkeeping four times a year; nobody wants to pay $360/year for something they use once a quarter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The privacy risk:&lt;/strong&gt; They require uploading every document to a third-party cloud. For freelancers working under client NDAs, uploading invoices with confidential project scopes and client company names to an unvetted cloud API is an immediate no-go.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I built &lt;strong&gt;LedgerLocal&lt;/strong&gt;: a 100% offline invoice and receipt parser powered by local open-weight models running on your own machine. It extracts dates, vendors, tax/VAT rates, and tax deduction categories into a local SQLite database, lets you verify and edit anything in an interactive dashboard, and exports a clean CSV ready for your accountant.&lt;/p&gt;

&lt;p&gt;Who would pay for it? Freelancers, contractors, and boutique agencies who want automated expense sorting without paying monthly SaaS subscriptions or exposing NDA-protected client data to cloud providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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%2Fab0ar8fyzg8gaco6jyw9.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%2Fab0ar8fyzg8gaco6jyw9.png" alt="LedgerLocal Dashboard" width="799" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Live Web Preview:&lt;/strong&gt; &lt;a href="https://ledgerlocal.onrender.com" rel="noopener noreferrer"&gt;https://ledgerlocal.onrender.com&lt;/a&gt; &lt;em&gt;(Hosted on Render with sample invoices preloaded)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The workflow is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drag and drop any folder of invoices or receipts (PDFs, text files, markdown) into the browser window.&lt;/li&gt;
&lt;li&gt;The local model extracts the financial fields into a structured JSON payload in 2-3 seconds per file.&lt;/li&gt;
&lt;li&gt;Review your expenses in a compact ledger table with inline edit buttons for quick human verification.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Export CSV&lt;/strong&gt; to generate a clean, categorized ledger.&lt;/li&gt;
&lt;/ol&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/alessiobianchini" rel="noopener noreferrer"&gt;
        alessiobianchini
      &lt;/a&gt; / &lt;a href="https://github.com/alessiobianchini/ledgerlocal" rel="noopener noreferrer"&gt;
        ledgerlocal
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;LedgerLocal&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A lightweight, 100% offline invoice and receipt parser for freelancers. Runs on your own machine with Ollama. Zero cloud calls, zero monthly subscriptions.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/alessiobianchini/ledgerlocal/screenshot.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%2Falessiobianchini%2Fledgerlocal%2FHEAD%2Fscreenshot.png" alt="LedgerLocal Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why I Built This&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Every tax season or quarter-end, freelancers and consultants go through the exact same headache: a folder full of random PDF invoices, Uber receipts, client coffee stubs, and software bills.&lt;/p&gt;
&lt;p&gt;The existing tools usually fall into two traps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Expensive SaaS subscriptions ($25-$40/mo):&lt;/strong&gt; Bloated accounting platforms you only touch four times a year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy risks:&lt;/strong&gt; Most "smart" parsers upload your documents to a cloud server. If your invoices contain client NDAs, project descriptions, or card slips, sending them to an unvetted third party is a dealbreaker.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;LedgerLocal&lt;/strong&gt; runs locally on your machine using open-weight models (powered by Google's &lt;code&gt;gemma2:2b&lt;/code&gt;, with support for &lt;code&gt;phi4-mini&lt;/code&gt; and &lt;code&gt;llama3.2-vision&lt;/code&gt;) through &lt;a href="https://ollama.ai" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100% Offline:&lt;/strong&gt; Works without an internet connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero API Bills:&lt;/strong&gt; Process 10 or 1,000…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/alessiobianchini/ledgerlocal" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;👉 Direct GitHub link: &lt;a href="https://github.com/alessiobianchini/ledgerlocal" rel="noopener noreferrer"&gt;github.com/alessiobianchini/ledgerlocal&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project is completely open source under the MIT license. You can run it locally with:&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/alessiobianchini/ledgerlocal.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ledgerlocal
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python run.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I deliberately avoided large orchestration frameworks like LangChain or LlamaIndex. For an offline utility, you don't need heavyweight abstractions; you need predictable parsing and fast execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inference Runtime:&lt;/strong&gt; &lt;a href="https://ollama.ai" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; running Google's open-weight &lt;strong&gt;Gemma 2 2B&lt;/strong&gt; (&lt;code&gt;gemma2:2b&lt;/code&gt;). At just ~1.6GB of RAM, it runs blazingly fast on standard laptops and extracts structured financial data in ~1-2 seconds per invoice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Extraction:&lt;/strong&gt; &lt;code&gt;pypdf&lt;/code&gt; extracts text from digital invoices. Plain text and markdown files are read directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Python + FastAPI for local endpoints (&lt;code&gt;/api/upload&lt;/code&gt;, &lt;code&gt;/api/receipts&lt;/code&gt;, &lt;code&gt;/api/export&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Standard SQLite (&lt;code&gt;ledger.db&lt;/code&gt;) via Python's native &lt;code&gt;sqlite3&lt;/code&gt; module with zero ORM overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; A single-file &lt;code&gt;static/index.html&lt;/code&gt; built with responsive, high-density CSS and vanilla JavaScript &lt;code&gt;fetch()&lt;/code&gt;. No &lt;code&gt;node_modules&lt;/code&gt; or build step required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the core extraction function from &lt;code&gt;parser.py&lt;/code&gt;:&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;parse_with_ollama&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma2:2b&lt;/span&gt;&lt;span class="sh"&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="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;truncated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;payload&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;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EXTRACTION_PROMPT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;truncated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&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;json&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;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&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;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Keep output deterministic
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_predict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;OLLAMA_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&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;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;headers&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;Content-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;application/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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&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;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;res&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;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&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;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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&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;{}&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;h3&gt;
  
  
  Real-World Parsing Challenges
&lt;/h3&gt;

&lt;p&gt;Handling real invoices required several practical fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vendor vs. Buyer Ambiguity:&lt;/strong&gt; On European invoices, the model initially picked the customer name under &lt;em&gt;"Intestatario / Billed To"&lt;/em&gt; instead of the actual vendor. I tuned the prompt rules to explicitly distinguish between issuer (&lt;em&gt;Cedente/Prestatore&lt;/em&gt;) and client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Date Formats:&lt;/strong&gt; To prevent misinterpreting &lt;code&gt;05/06/2026&lt;/code&gt; (May vs. June), regex validation ensures dates strictly match &lt;code&gt;YYYY-MM-DD&lt;/code&gt; or fall back to manual review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fiscal Printer Codes:&lt;/strong&gt; Italian receipts often lack standard "Invoice Numbers" and instead print a Registratore Telematico (&lt;code&gt;RT&lt;/code&gt;) code. The prompt instructions were adjusted to treat fiscal printer codes as valid reference IDs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Currency Totals:&lt;/strong&gt; Grouped summary calculations by currency (EUR, USD) so exchange rates don't distort accountant reports.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Observability &amp;amp; Agent Tracing with Sentry
&lt;/h3&gt;

&lt;p&gt;To measure parsing performance and catch silent extraction errors, the pipeline is instrumented using &lt;code&gt;sentry-sdk&lt;/code&gt; using Sentry's GenAI span conventions (&lt;code&gt;gen_ai.chat&lt;/code&gt;):&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;with&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ollama Extraction (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.system&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;ollama&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.request.model&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;gemma2:2b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.usage.prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_eval_count&lt;/span&gt;&lt;span class="sh"&gt;"&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;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.usage.completion_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eval_count&lt;/span&gt;&lt;span class="sh"&gt;"&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;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai.invoice.duration_sec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration_sec&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;What the telemetry revealed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token Efficiency:&lt;/strong&gt; A standard digital PDF invoice averages &lt;strong&gt;320–420 prompt tokens&lt;/strong&gt; and produces clean structured JSON in only &lt;strong&gt;75–110 completion tokens&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency Profile:&lt;/strong&gt; Gemma 2 2B completes full extraction in &lt;strong&gt;1.8 to 2.5 seconds&lt;/strong&gt; on consumer hardware. The trace spans proved that text extraction with &lt;code&gt;pypdf&lt;/code&gt; takes &amp;lt;15ms, with 98% of wall-clock time spent in local model inference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Verification:&lt;/strong&gt; Tracing confirmed an effective unit cost of &lt;strong&gt;$0.00&lt;/strong&gt;, proving that local open-weight models deliver SaaS-grade capabilities without unpredictable per-token bills.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging Edge Cases:&lt;/strong&gt; Sentry spans allowed instant diagnosis of an edge case on receipt stubs where fiscal codes (&lt;code&gt;RT-xxxxx&lt;/code&gt;) caused JSON parsing hiccups, leading to the dedicated regex sanitizer before saving to SQLite.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Open-source AI wasn't just an implementation choice; it is the entire foundation that makes this tool viable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy as an Absolute Guarantee:&lt;/strong&gt; Invoices contain bank details, customer names, and project deliverables. Running open-weight models locally means data privacy isn't governed by a cloud provider's terms of service—it is physically guaranteed because packets never leave &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sustainable Unit Economics:&lt;/strong&gt; A micro-utility sold as a one-time tool (e.g. $25 on Gumroad) cannot pay $0.02-$0.05 per API call. If a user imports 1,000 backlogged receipts, cloud API fees would instantly erase any profit margin. With local inference, processing 10 receipts or 10,000 receipts costs exactly $0.00.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline Reliability:&lt;/strong&gt; Many freelancers work while traveling on trains or flights without reliable internet. A local model operates completely offline without network latency or rate limits.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;To test if someone would actually use this in the real world, I shared LedgerLocal with Ale, a freelance consultant.&lt;/p&gt;

&lt;p&gt;I had him run 15 of his Q3 receipts through the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Good:&lt;/strong&gt; It parsed all 15 invoices (DigitalOcean, GitHub, train tickets, client lunches) in under 4 minutes with 100% accuracy on digital PDF totals and tax rates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Feedback:&lt;/strong&gt; He requested a terminal workflow for fast scripting, which led to the creation of &lt;code&gt;cli.py&lt;/code&gt; (&lt;code&gt;python cli.py import ./folder&lt;/code&gt; and &lt;code&gt;python cli.py export&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Would he pay?&lt;/strong&gt; His feedback: &lt;em&gt;"I'd never pay a monthly subscription because I only touch bookkeeping four times a year. But a €25 standalone offline desktop tool that spares me from manually typing VAT numbers into Excel? I'd buy that immediately."&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt;: LedgerLocal uses Google's open-weight Gemma 2 model (&lt;code&gt;gemma2:2b&lt;/code&gt; via Ollama) as its core offline inference engine to parse sensitive invoices and receipts with zero API fees, sub-2-second latency, and total client data confidentiality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render&lt;/strong&gt;: The interactive web dashboard and backend API are configured with a native Render Blueprint (&lt;code&gt;render.yaml&lt;/code&gt;) to host the user interface, database, and CSV export online, giving reviewers and potential users an instant live preview.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Sentry Agent Tracing&lt;/strong&gt;: Every extraction run is instrumented via &lt;code&gt;sentry-sdk&lt;/code&gt; with &lt;code&gt;gen_ai.chat&lt;/code&gt; spans, tracking exact token counts (&lt;code&gt;prompt_eval_count&lt;/code&gt;, &lt;code&gt;eval_count&lt;/code&gt;), latency benchmarks, and verified zero-cost execution for local open-weight inference.&lt;/li&gt;
&lt;/ul&gt;

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