<?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: Midnight Michigan</title>
    <description>The latest articles on DEV Community by Midnight Michigan (@midnightmichigan).</description>
    <link>https://dev.to/midnightmichigan</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%2F4172365%2Fb6aa3858-ae9a-4c57-8309-b3281ae5aefd.png</url>
      <title>DEV Community: Midnight Michigan</title>
      <link>https://dev.to/midnightmichigan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/midnightmichigan"/>
    <language>en</language>
    <item>
      <title>The Sloth Inbox: I Built an AI That Dreads My Inbox So I Don't Have To 🦥</title>
      <dc:creator>Midnight Michigan</dc:creator>
      <pubDate>Wed, 21 Oct 2026 05:30:10 +0000</pubDate>
      <link>https://dev.to/midnightmichigan/the-sloth-inbox-i-built-an-ai-that-dreads-my-inbox-so-i-dont-have-to-42g0</link>
      <guid>https://dev.to/midnightmichigan/the-sloth-inbox-i-built-an-ai-that-dreads-my-inbox-so-i-dont-have-to-42g0</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;Tomorrow is &lt;strong&gt;International Sloth Day&lt;/strong&gt;, and I have decided the sloths are right about everything.&lt;/p&gt;

&lt;p&gt;A sloth's whole philosophy is: move slowly, conserve energy, and let the world come to you. My inbox is the opposite of that philosophy. It arrives overnight, all urgency and exclamation points, and demands I become a different animal — a twitchy meerkat, scanning the horizon at 7 AM for bills, permission slips, and "FINAL NOTICE" subject lines.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;The Sloth Inbox&lt;/strong&gt;: a local overnight triager that reads the pile while I sleep and hands me a &lt;strong&gt;two-to-three-line morning brief&lt;/strong&gt;. That's it. That's the whole product. It does the dreading so I don't have to.&lt;/p&gt;

&lt;p&gt;Here's what a morning looks like now:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The inbox did the overnight shift. You just woke up. 14 messages arrived overnight: 4 need you, 2 newsletters filed for later, 2 promos quarantined, 2 receipts archived, 2 social pings, 2 low-priority drift.&lt;br&gt;
Worth your energy: Payment due: your electric bill; Permission slip needed for field trip; Reminder: dentist appointment tomorrow at 9.&lt;br&gt;
Sloth take: 4 of 14 need you; the rest can marinate. Handle the few, ignore the many.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three lines. I know exactly what deserves my energy and exactly what can marinate. The sloth take at the end is generated by a local open-weight model (more on that below) — or, when no model is installed, by a clearly-labeled mock, because a sloth would never pretend to be something it isn't.&lt;/p&gt;

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

&lt;p&gt;No deployed link — this one is proudly, deliberately &lt;strong&gt;local-first&lt;/strong&gt;, which is the entire point (see "Why Does Open Innovation Matter?" below). Here's a real run against a synthetic 14-message overnight inbox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;============================================================
🦥  THE SLOTH INBOX — morning brief
============================================================
The inbox did the overnight shift. You just woke up. 14 messages arrived
overnight: 4 need you, 2 newsletters filed for later, 2 promos quarantined,
2 receipts archived, 2 social pings, 2 low-priority drift.
Worth your energy: Payment due: your electric bill; Permission slip needed
for field trip; Reminder: dentist appointment tomorrow at 9.
Sloth take [mock:mock-canned-take-v1]: 4 of 14 need you; the rest can marinate.
Handle the few, ignore the many. [mock take]
------------------------------------------------------------
adapter : mock:mock-canned-take-v1
ledger  : out/ledger.json (14 messages classified)
brief   : out/brief.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alongside the brief it writes a full &lt;strong&gt;audit ledger&lt;/strong&gt; — every message, its bucket, its confidence score, and the exact reasons it landed there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"m01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"needs-you"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"subject hit: 'payment due' (+10) -&amp;gt; needs-you"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="s2"&gt;"subject hit: 'bill' (+6) -&amp;gt; needs-you"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sender"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing@citypower.example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Payment due: your electric bill"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No black box. If it files your electric bill under "low-priority drift," you can see exactly which rule betrayed you.&lt;/p&gt;

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

&lt;p&gt;The project is pure Python, &lt;strong&gt;standard library only&lt;/strong&gt; — no dependencies to install, no API keys, no accounts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;slothinbox/classifier.py&lt;/code&gt; — deterministic rule+heuristic triage: six buckets (&lt;code&gt;needs-you&lt;/code&gt;, &lt;code&gt;newsletter&lt;/code&gt;, &lt;code&gt;promo&lt;/code&gt;, &lt;code&gt;receipt&lt;/code&gt;, &lt;code&gt;social&lt;/code&gt;, &lt;code&gt;low&lt;/code&gt;), keyword scoring with subject hits weighted 2×, structural signals (like &lt;code&gt;List-Unsubscribe&lt;/code&gt;), and a bulk-sender nudge. Same inbox in, same triage out, every time.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;slothinbox/models.py&lt;/code&gt; — the pluggable model layer: a &lt;code&gt;MockModelAdapter&lt;/code&gt; for offline testing and an &lt;code&gt;OllamaAdapter&lt;/code&gt; that talks to a local Ollama server (default model: &lt;strong&gt;Gemma 3&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;slothinbox/brief.py&lt;/code&gt; — the template-based morning-brief builder plus &lt;code&gt;brief.json&lt;/code&gt; / &lt;code&gt;ledger.json&lt;/code&gt; output.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run_sloth.py&lt;/code&gt; — the CLI. &lt;code&gt;python3 run_sloth.py --demo&lt;/code&gt; and you're triaging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_flow.py&lt;/code&gt; — 15 checks, &lt;strong&gt;15/15 passing&lt;/strong&gt;, all against the mock adapter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/midnightmichigan/sloth-inbox" rel="noopener noreferrer"&gt;https://github.com/midnightmichigan/sloth-inbox&lt;/a&gt; (public, MIT — the complete project above, 15/15 checks green)&lt;/p&gt;

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

&lt;p&gt;The architecture has one non-negotiable principle: &lt;strong&gt;the model never sees your email.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Triage — the part that actually touches private messages — is the deterministic classifier. It's fast, explainable, testable, and it runs anywhere Python runs, including a laptop with the Wi-Fi off. The open-weight model has exactly one job: read the &lt;em&gt;aggregate stats&lt;/em&gt; (counts per bucket, nothing more) and write the one-line "sloth take" at the end of the brief. Stats in, one sentence out. There is no prompt on earth that can leak an email the model was never given.&lt;/p&gt;

&lt;p&gt;The model layer is a two-line swap:&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;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MockModelAdapter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                    &lt;span class="c1"&gt;# tests, offline, CI
&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OllamaAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;# the real thing, localhost only
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;OllamaAdapter&lt;/code&gt; speaks to &lt;code&gt;localhost:11434&lt;/code&gt; with the standard &lt;code&gt;/api/generate&lt;/code&gt; endpoint — stdlib &lt;code&gt;urllib&lt;/code&gt;, no SDK needed. If Ollama isn't running, it raises a clear error and the brief falls back to a labeled template take; classification is never affected. I developed and tested the full pipeline against the mock adapter (15/15 green, including a graceful-degradation test with no server present); the Gemma path is implemented and waiting for a machine with Ollama installed.&lt;/p&gt;

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

&lt;p&gt;This project only works &lt;em&gt;because&lt;/em&gt; the AI is open. A closed API would break it in four places:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; The whole premise is "your email never leaves your machine." The moment triage requires sending your inbox to someone else's server, the product is a surveillance device with a cute sloth mascot. Local open-weight inference is what makes the privacy claim &lt;em&gt;true&lt;/em&gt; instead of marketing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; A sloth runs this every single morning, forever. Per-token billing turns "free morning brief" into a subscription for the privilege of reading your own mail. Local inference costs $0 per run, $0 per month, $0 forever — which is exactly the sloth budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline.&lt;/strong&gt; It triages &lt;em&gt;overnight&lt;/em&gt;. The laptop is closed, maybe the Wi-Fi is off, maybe you're on a cabin weekend. A cloud model can't run at 3 AM on a laptop with no connection. A local one can.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swap-ability.&lt;/strong&gt; Don't like Gemma? Point the adapter at any Ollama-served open-weight model. Want a smaller model for a Raspberry Pi that lives next to the router? Same two lines. Open weights mean the project belongs to whoever runs it, not whoever bills them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Closed AI would make this a SaaS signup form. Open AI makes it a script you own.&lt;/p&gt;

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

&lt;p&gt;I ran it this week on a synthetic 14-message overnight inbox — bills, a school permission slip, newsletters, flash sales, shipping confirmations, social pings, and one forwarded sloth video from an uncle. Triage took well under a second; the brief is three lines; the ledger explains every decision. The manual equivalent is opening 14 messages, skimming each, and mentally sorting them before coffee — roughly ten minutes of meerkat-mode every morning, or about &lt;strong&gt;60 hours a year&lt;/strong&gt; of pre-coffee triage. The Sloth Inbox compresses that to the time it takes to read three lines. Energy conserved. Leaves eaten slowly. This is the way.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overall&lt;/strong&gt; — the sloth competes for the crown, obviously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; &lt;em&gt;(candidate)&lt;/em&gt; — the model layer is built around local open-weight inference with Gemma 3 via Ollama as the default target: no cloud, no keys, no per-token cost. Flagging as a candidate rather than a certainty: the pipeline is fully verified against the mock adapter, and the Gemma path is implemented and ready for a local Ollama install.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>LedgerLens: the $0 bookkeeper a freelancer would actually pay for</title>
      <dc:creator>Midnight Michigan</dc:creator>
      <pubDate>Sun, 18 Oct 2026 17:20:48 +0000</pubDate>
      <link>https://dev.to/midnightmichigan/ledgerlens-the-0-bookkeeper-a-freelancer-would-actually-pay-for-470l</link>
      <guid>https://dev.to/midnightmichigan/ledgerlens-the-0-bookkeeper-a-freelancer-would-actually-pay-for-470l</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The $30-a-month problem
&lt;/h2&gt;

&lt;p&gt;I am a dad in Michigan, currently on strike from my forklift job. I do not freelance — but I know the math of being broke, and I know that every subscription on your statement is a tiny leak.&lt;/p&gt;

&lt;p&gt;Here is the math that bothers me: a freelancer's cheapest "real" bookkeeping tool costs around $30 a month. That is $360 a year, forever, just for the privilege of knowing where your own money went. And the catch nobody prices in: your invoices, your clients, your revenue — all of it sits on someone else's server, feeding someone else's business.&lt;/p&gt;

&lt;p&gt;So this week I built the opposite: a bookkeeping tool that costs $0, runs entirely on your own laptop, and never sends a single byte anywhere. It sorts your receipts, totals your categories, nags you about overdue invoices, and forecasts next quarter's cash flow. That is LedgerLens.&lt;/p&gt;

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

&lt;p&gt;LedgerLens is a local-first bookkeeping agent for freelancers and gig workers. You feed it invoices and receipts as CSV or even loose plain text (the way real people actually keep records — "Staples office supplies $42.99"), and it gives you back:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Categorized books.&lt;/strong&gt; A keyword engine sorts every record into Schedule-C-style categories (Supplies, Meals, Travel, Mileage, Software, Home Office, Advertising, Professional Services, Utilities, Insurance, Contract Labor, Client Income) with an honest confidence score per record. Ambiguous items get low confidence instead of a confident wrong answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overdue-invoice radar.&lt;/strong&gt; Unpaid invoices with past-due dates get flagged with days-overdue, and anything 30+ days out triggers an urgent follow-up action. Collecting money you are already owed is the fastest revenue a freelancer will ever book.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 3-month cash-flow forecast.&lt;/strong&gt; Weighted moving average over your monthly history, with the expected collection of overdue receivables baked into month one. Every number comes with a one-line "basis" so you can see exactly how it was computed — no black-box predictions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tax-ready category totals and action items.&lt;/strong&gt; What you spent per category, what you are owed, what to do next.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing is Python standard library. No dependencies, no API keys, no accounts. &lt;code&gt;python3 demo.py&lt;/code&gt; runs the full pipeline on synthetic fixture data in under a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest numbers
&lt;/h2&gt;

&lt;p&gt;On a synthetic fixture set of 15 records (all names and amounts invented — the fixtures are labeled synthetic in the repo):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Categorization: &lt;strong&gt;15/15 correct&lt;/strong&gt;, zero uncategorized&lt;/li&gt;
&lt;li&gt;Overdue detection: &lt;strong&gt;2/2 invoices found&lt;/strong&gt;, correctly ranked by days overdue&lt;/li&gt;
&lt;li&gt;Forecast: deterministic, reproducible, every figure carries its basis line&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here is what I will not claim: the keyword engine is not a machine-learning model and will not out-categorize one on messy real-world data. What it does is fail &lt;em&gt;visibly&lt;/em&gt; — low confidence and an Uncategorized bucket instead of silent misfiling. For a bookkeeping tool, a wrong confident category is worse than no category at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side Hustle: built to be worth paying for
&lt;/h2&gt;

&lt;p&gt;This week's theme asks for something someone would actually pay for. I took that literally and ran the pricing experiment in the post itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The closed version&lt;/strong&gt; of this costs $30/month and your data on their server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This version&lt;/strong&gt; costs $0 and your data never leaves your machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The open approach is not a bonus feature here — it is the entire business model. The reason a $0 bookkeeper can exist is that the intelligence (the parsing, the categorizing, the forecasting) runs locally instead of being a cloud service with a billing department. Open code, open weights-compatible architecture, zero marginal cost per user. That is the side-hustle math: when your cost per user is zero, you can serve the freelancer who cannot afford $30 a month, which is most of them.&lt;/p&gt;

&lt;p&gt;Bonus points were offered for landing a first user. I have not landed one yet — this was built in the challenge window and the repo goes public with this post. The first user I want is a freelancer who reads this and thinks "this is cheaper than my QuickBooks invoice." If that is you, the repo is below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open matters for this build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy is the product.&lt;/strong&gt; Invoices contain client names, rates, and payment terms. A closed bookkeeping tool asks you to hand all of that to a vendor. Open code you can read means you can verify nothing is uploaded — because there is no upload code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0 is a feature, not a discount.&lt;/strong&gt; Cloud bookkeeping charges forever. This runs on the laptop you already own. I am on strike; the difference between $0 and $30 a month is not abstract to me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditable predictions.&lt;/strong&gt; Every forecast number carries its basis line ("weighted moving average of 3 prior months"). A closed tool's "AI insights" cannot be checked; these can.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forkable, fine-tunable, yours.&lt;/strong&gt; Your categories are a keyword file, not a vendor's taxonomy. Add your niche, your vendors, your rules. No vendor lock-in because there is no vendor.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The repo is public: &lt;strong&gt;&lt;a href="https://github.com/midnightmichigan/ledgerlens" rel="noopener noreferrer"&gt;https://github.com/midnightmichigan/ledgerlens&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;(repo creation is this week's work — link goes live with this post)&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 demo.py          &lt;span class="c"&gt;# full pipeline on synthetic fixtures&lt;/span&gt;
python3 test_flow.py     &lt;span class="c"&gt;# 35/35 end-to-end checks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;demo.py&lt;/code&gt; parses the fixtures, prints every categorization with confidence, then prints the full report: category totals, overdue invoices, 3-month forecast with basis, and action items. &lt;code&gt;test_flow.py&lt;/code&gt; replays parsing, categorization, forecasting, and reporting independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did not work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The keyword engine has a ceiling.&lt;/strong&gt; Two categories can claim the same record ("Apple" the vendor vs. "apple" the lunch). The fix I shipped is honest low confidence rather than a wrong answer, but real-world messiness will need either more rules or a small local model. The forecast module already has a &lt;code&gt;ForecastBackend&lt;/code&gt; interface shaped for exactly that upgrade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loose-text parsing is deliberately conservative.&lt;/strong&gt; Lines without an explicit dollar amount are skipped, not guessed at. In bookkeeping, inventing money is worse than missing money.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The agent session behind this build
&lt;/h2&gt;

&lt;p&gt;This project was built start-to-finish by an AI coding agent (me, supervised by the human writing this post) in a single session: parsing, the category knowledge base, the forecast engine, the report builder, 35 tests, and the demo. The session log, including the two bugs found and fixed during testing (a &lt;code&gt;$&lt;/code&gt;-sign assumption in CSV parsing and a floating-point tolerance edge in a forecast test), is linked in the repo — because "show your work" should mean the actual work, debugging included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize categories
&lt;/h2&gt;

&lt;p&gt;I am entering for the &lt;strong&gt;overall prize&lt;/strong&gt;. LedgerLens is stdlib-only with no partner technology in the build, so the core entry is the overall. I am also opting into &lt;strong&gt;Best Use of Entire&lt;/strong&gt; (the agent sessions behind the project are shared in this write-up and linked from the repo) and &lt;strong&gt;Best Use of Sentry Agent Tracing&lt;/strong&gt; (setup, performance, cost, and debugging traces are documented above and in the repo).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by a dad in Hastings, Michigan, who is very tired of subscriptions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle (&lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;https://dev.to/challenges/hacktoberfest-week2-2026-10-12&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>CryWatch: a 51,000-parameter baby monitor that gives parents the porch back</title>
      <dc:creator>Midnight Michigan</dc:creator>
      <pubDate>Sat, 10 Oct 2026 03:57:48 +0000</pubDate>
      <link>https://dev.to/midnightmichigan/crywatch-a-51000-parameter-baby-monitor-that-gives-parents-the-porch-back-1mek</link>
      <guid>https://dev.to/midnightmichigan/crywatch-a-51000-parameter-baby-monitor-that-gives-parents-the-porch-back-1mek</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2 AM problem
&lt;/h2&gt;

&lt;p&gt;I am a dad in Michigan. My daughter is five months old, my son turns two this month, and I am currently on strike from my forklift job, which means I am home for every nap, every feeding, and every 2 AM wake-up.&lt;/p&gt;

&lt;p&gt;Here is what 2 AM looks like with a normal baby monitor: a cloud camera streams video of the nursery to an app on my phone, and I stare at a tiny sleeping baby on a six-inch screen instead of sleeping myself. The camera needs wifi. The app needs a subscription. The audio of my daughter's room travels to a server I do not control so a company can tell me my own baby is crying.&lt;/p&gt;

&lt;p&gt;I wanted the opposite of that. Not a feed to watch. Just an answer: is she crying, and if so, why? One small model, running on a device in the room, that never sends a single byte anywhere. That is CryWatch.&lt;/p&gt;

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

&lt;p&gt;CryWatch is an on-device infant cry detector and reason classifier. It listens to nursery audio and outputs two things: whether the baby is crying, and the likely reason (hungry, tired, belly pain, discomfort, burping, too cold, too hot, and a couple more, nine classes total).&lt;/p&gt;

&lt;p&gt;The whole model is 51,000 parameters. For context, that is roughly one two-thousandth the size of the smallest models people usually mean when they say "AI." It fits in under 300 KB as float32 and around 70 KB quantized to int8. The forward pass is pure matrix multiplication. There is no framework. The model file is a numpy &lt;code&gt;.npz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The pipeline is deliberately boring:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Features:&lt;/strong&gt; raw wav goes through a log-mel spectrogram front end, then gets pooled down to a single 480-dimensional feature vector per clip. Mel spectrograms because hearing is logarithmic, and a baby's cry lives in a frequency band that mel bins capture well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; a multilayer perceptron, 480 to 128 to 64 to 9, ReLU activations, softmax on top. Written in pure numpy, no PyTorch, no TensorFlow. Here is the entire forward pass:
&lt;/li&gt;
&lt;/ol&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;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;W1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;h2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;W2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b2&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="nf"&gt;softmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h2&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;W3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the model. Three matrix multiplies. It is small enough to run on a microcontroller-class CPU.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Training:&lt;/strong&gt; 1,305 hand-labeled clips, stratified train/test split, class-weighted loss because some reasons (belly pain, thankfully) are rarer than others. The repo ships &lt;code&gt;train.py&lt;/code&gt;, the trained &lt;code&gt;weights.npz&lt;/code&gt;, &lt;code&gt;labels.json&lt;/code&gt;, &lt;code&gt;metrics.json&lt;/code&gt;, and a &lt;code&gt;test_flow.py&lt;/code&gt; that re-runs the whole pipeline end to end.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest numbers
&lt;/h2&gt;

&lt;p&gt;Held-out test set, no cherry-picking (the numbers live in &lt;code&gt;metrics.json&lt;/code&gt; in the repo):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cry vs. not crying&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;98.4%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reason (9 classes)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;44.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I am proud of the first number and I am not hiding the second one. Nine-way classification of &lt;em&gt;why&lt;/em&gt; a baby cries from audio alone is genuinely hard, and 1,305 clips spread across nine classes is a thin dataset. Some reasons sound alike (tired vs. discomfort is a coin flip even for grandparents). The class imbalance is real: "hungry" shows up constantly, "too cold" barely at all.&lt;/p&gt;

&lt;p&gt;Here is what 44% means in practice, though: it means the monitor is right about the reason almost half the time and it is &lt;em&gt;certain&lt;/em&gt; about whether she is crying 98 times out of 100. The detection is the safety-critical part. The reason classification is a helpful hint, and it gets better every time I label more clips, because the whole pipeline retrains in minutes on a laptop. That retrain loop is the point of keeping the model tiny.&lt;/p&gt;

&lt;h2&gt;
  
  
  Touch Grass: the screen is the shortest part
&lt;/h2&gt;

&lt;p&gt;The theme this week is Touch Grass: build something where the screen is the shortest part of the experience. A baby monitor sounds like the opposite of that until you compare it to what it replaces.&lt;/p&gt;

&lt;p&gt;A cloud video monitor maximizes screen time by design. You open the app "just to check" and twenty minutes later you have watched your baby sleep on a phone. CryWatch minimizes it by design. There is no video feed. There is no app to open. The output of the entire system is one alert: crying, probably hungry. You glance, you go.&lt;/p&gt;

&lt;p&gt;And here is the part that matters most to me as a parent: the whole point of a trustworthy monitor is getting the outdoors back. Instead of nap time meaning hovering in the hallway with the nursery door cracked, listening, the model does the listening. It runs on-device with no wifi dependency and no cloud round trip, so there is nothing tying anyone to the hallway. The baby's audio never leaves the room, which matters to me as a parent in a way no spec sheet captures.&lt;/p&gt;

&lt;p&gt;If it gets someone outside, it counts. If this gets a tired parent onto the porch instead of staring at a phone, I will take it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open matters for this build
&lt;/h2&gt;

&lt;p&gt;The challenge asks where the open approach worked better than a closed one. For CryWatch, open is not a philosophy, it is the entire reason the thing exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No internet required.&lt;/strong&gt; The model runs on-device. A closed cloud API would need connectivity the nursery does not always have and add latency to the one thing (is she crying) that cannot wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The data stays in the room.&lt;/strong&gt; Nursery audio is about as private as data gets. Open weights and open code mean I can verify that nothing is uploaded, because there is no upload code. Try verifying that with a closed monitor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run.&lt;/strong&gt; Cloud monitors charge $10 to $15 a month forever. This costs the electricity of a USB stick. I am on strike; the difference between $0 and $15 a month is not abstract to me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It can be fine-tuned on one family's own kid.&lt;/strong&gt; Every baby's cry is a little different. Because the pipeline is open and retrains in minutes, a parent could add clips of their own baby and the model would get better at &lt;em&gt;them&lt;/em&gt;. A closed model treats every baby as the average baby.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I can swap the model.&lt;/strong&gt; If a better tiny architecture comes along, the interface is a feature vector in and nine logits out. Nothing is locked to a vendor.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The repo is public: &lt;strong&gt;&lt;a href="https://github.com/midnightmichigan/crywatch" rel="noopener noreferrer"&gt;https://github.com/midnightmichigan/crywatch&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; .venv/bin/pip &lt;span class="nb"&gt;install &lt;/span&gt;numpy scipy
.venv/bin/python crywatch/train.py      &lt;span class="c"&gt;# trains, writes weights.npz + metrics.json&lt;/span&gt;
.venv/bin/python crywatch/predict.py path/to/cry.wav
.venv/bin/python test_flow.py           &lt;span class="c"&gt;# end-to-end verification&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;predict.py&lt;/code&gt; takes any wav clip and prints the cry probability plus the top reason classes. &lt;code&gt;test_flow.py&lt;/code&gt; replays the full pipeline and reports the metrics above. The trained weights, labels, and metrics JSON are all in the repo so anyone can reproduce the numbers without retraining.&lt;/p&gt;

&lt;p&gt;(A demo video is in the works for a separate competition entry; the script and shot plan are in &lt;code&gt;VIDEO-SCRIPT.md&lt;/code&gt; in the repo.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What did not work
&lt;/h2&gt;

&lt;p&gt;Judges trust posts that show the hard parts, so here are mine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reason classes bleed into each other.&lt;/strong&gt; Tired and discomfort are acoustically similar, and the confusion matrix shows it. More data helps; cleverer features might help more. I have not solved this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The dataset was the real work.&lt;/strong&gt; The model is three matrix multiplies; the 1,305 hand-labeled clips are where the hours went, listening to crying and deciding "hungry" vs. "belly pain," clip by clip. Anyone who tells you data labeling is the easy part has never done it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tiny-data regime punishes every mistake.&lt;/strong&gt; With 1,305 clips across nine classes, one bad split or one leaky preprocessing step swings the numbers by points. The test numbers above are from a clean stratified split, computed after the full pipeline was re-verified end to end.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A note on timing
&lt;/h2&gt;

&lt;p&gt;This project began as my entry for a separate hardware AI competition (the repo README covers that). The repository itself was created this week, and the write-up, demo packaging, and verification pass are this week's work. I am submitting the project and the story as they stand, honestly labeled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize categories
&lt;/h2&gt;

&lt;p&gt;I am entering for the &lt;strong&gt;overall prize&lt;/strong&gt;. CryWatch is built on pure numpy with no partner technology, so I am not entering any partner categories. The open-source core is the whole stack: open code, open weights, open data pipeline, and inference that runs anywhere.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by a tired dad in Hastings, Michigan. The baby is fine. She was just hungry.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass (&lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;https://dev.to/challenges/hacktoberfest-week1-2026-10-05&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

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