<?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: Khushighosh</title>
    <description>The latest articles on DEV Community by Khushighosh (@khushighosh).</description>
    <link>https://dev.to/khushighosh</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%2F4152360%2F6ac43df7-8b20-4acc-913f-03da2c9dd0ef.jpg</url>
      <title>DEV Community: Khushighosh</title>
      <link>https://dev.to/khushighosh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khushighosh"/>
    <language>en</language>
    <item>
      <title>Let It Die: The Lazy Email Agent on TabPFN, Gemma and Temporal</title>
      <dc:creator>Khushighosh</dc:creator>
      <pubDate>Sun, 25 Oct 2026 06:45:27 +0000</pubDate>
      <link>https://dev.to/khushighosh/let-it-die-the-lazy-email-agent-on-tabpfn-gemma-and-temporal-10o0</link>
      <guid>https://dev.to/khushighosh/let-it-die-the-lazy-email-agent-on-tabpfn-gemma-and-temporal-10o0</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;Most “lazy” agents do your chores faster. Let It Die asks whether the chore needs doing at all.&lt;/p&gt;

&lt;p&gt;It reads incoming email and scores each message on how likely it is that ignoring it would cost you something. Then it does one of three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Need you&lt;/strong&gt; (top 20% by score): a local Gemma model writes a two-line summary and a suggested action for a morning digest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low priority&lt;/strong&gt; (middle 60%): left alone and counted in the digest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let it die&lt;/strong&gt; (bottom 20%): parked on a durable Temporal timer. If the sender follows up within 7 days, the email is escalated. If nothing arrives, it is archived.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyone whose inbox is full of mail that sorts itself out gets to be a little lazier: the cheapest task to finish is the one you never started, as long as you can tell which ones those are. On my held-out test slice of 1,981 emails, 398 (20%) were parked, and about 85% of them really got no reply.&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/qxheqsUqbcc" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnojbw2bg4qendvviytez.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%2Fnojbw2bg4qendvviytez.png" alt="Terminal output of the Let It Die run showing need-you, low-priority and parked counts" width="799" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Real output from a run on held-out email:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;397 need you | 1186 low-priority | 398 parked on a timer (let it die)
In the 'need you' bucket, 46% were actually replied to (overall: 30%)

- john.henderson@enron.com | Wharton Tm #3 Presentation | he replied
  Summary: John reviewed the Wharton Tm #3 presentation and found it to be a good start.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the parking demo, 8 emails were picked at random from the let-it-die bucket. 7 were archived, and all 7 truly needed no reply. 1 was escalated, because the sender wrote again.&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/Khushighosh" rel="noopener noreferrer"&gt;
        Khushighosh
      &lt;/a&gt; / &lt;a href="https://github.com/Khushighosh/let-it-die" rel="noopener noreferrer"&gt;
        let-it-die
      &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;Let It Die: an agent whose main skill is deciding not to act&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Built for the Hacktoberfest Open-Source AI Challenge, Week 3 ("Slow and Steady").&lt;/p&gt;

&lt;p&gt;Most "lazy" agents do your chores faster. This one asks whether the chore needs doing at all. It scores each incoming email on how likely ignoring it is to cost you something, then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Need you&lt;/strong&gt; (top 20% by score): a local Gemma model writes a two-line summary and a suggested action for the morning digest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low priority&lt;/strong&gt; (middle 60%): left alone, counted in the digest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let it die&lt;/strong&gt; (bottom 20%): parked on a durable Temporal timer. If the sender follows up within 7 days, the email is escalated. If nothing arrives, it is archived.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;emails ──&amp;gt; TabPFN score ──&amp;gt; triage ──┬─ need you ──&amp;gt; Gemma summary ──&amp;gt; morning digest
                                     ├─ low priority ──&amp;gt; counted only
                                     └─ let it die ──&amp;gt; Temporal timer (7 days)
                                                         ├─&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Khushighosh/let-it-die" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;MIT licensed. The README has the run steps (Windows, Python 3.12).&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emails -&amp;gt; TabPFN score -&amp;gt; triage -+- need you -&amp;gt; Gemma summary -&amp;gt; morning digest
                                  +- low priority -&amp;gt; counted only
                                  +- let it die -&amp;gt; Temporal timer (7 days)
                                                    +- follow-up signal -&amp;gt; escalate
                                                    +- timeout -&amp;gt; archive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TabPFN&lt;/strong&gt; (Prior Labs) predicts whether the owner replied within 7 days, from features built only from information available before each email arrived: sender history, who it was addressed to, time of day, subject and body cues, thread length. It needs no training run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma&lt;/strong&gt; (gemma3:4b through Ollama, running locally) writes the summaries and suggested actions for the need-you bucket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporal&lt;/strong&gt; runs one workflow per parked email. It waits for either a follow-up signal or a 7-day timeout, then escalates or archives. My demo uses Temporal’s time-skipping test server, so a week of waiting runs in seconds. When I made the first archive attempt fail on purpose, Temporal retried every one automatically, and all 7 archived emails logged attempt 2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry&lt;/strong&gt; traces the whole run: the agent, the triage step, each Gemma call (model, token counts, full prompt and response) and the Temporal step. About 22 of the 23 seconds were the five local Gemma calls, each with an HTTP call to 127.0.0.1, my own machine. Triage took about a millisecond and parking eight emails about 1.3 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The data.&lt;/strong&gt; I don’t have a two-sided mailbox of my own, so I used one mailbox of about 28,000 messages from the public Enron corpus (CMU, May 2015 release), which includes sent folders and lets me derive reply labels. The label is “the owner replied in the same thread within 7 days.” Sent mail stops being archived after May 2001, so every later email would look unanswered; I cut the timeline to Nov 1999 through May 2001, which left 9,902 usable emails. I trained on the earliest 80% (7,921 emails) and tested on the latest 20% (1,981 emails, 70.5% of which got no reply). The split is by time, so the model never sees the future.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;ROC AUC&lt;/th&gt;
&lt;th&gt;Ignore lowest 10%: truly no reply / replied-to mail missed&lt;/th&gt;
&lt;th&gt;Ignore lowest 20%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sender reply-rate rule&lt;/td&gt;
&lt;td&gt;0.579&lt;/td&gt;
&lt;td&gt;68.2% / 10.8%&lt;/td&gt;
&lt;td&gt;73.7% / 17.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradient boosting&lt;/td&gt;
&lt;td&gt;0.662&lt;/td&gt;
&lt;td&gt;92.4% / 2.6%&lt;/td&gt;
&lt;td&gt;88.4% / 7.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TabPFN&lt;/td&gt;
&lt;td&gt;0.687&lt;/td&gt;
&lt;td&gt;92.9% / 2.4%&lt;/td&gt;
&lt;td&gt;84.6% / 10.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What I take from it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both models clearly beat a rule based on the sender’s past reply rate.&lt;/li&gt;
&lt;li&gt;TabPFN’s AUC is higher by 0.024 (bootstrap 95% CI 0.003 to 0.043), but gradient boosting wins the 20% bucket. I call them comparable.&lt;/li&gt;
&lt;li&gt;The practical headline: the agent can park about 1 in 10 emails and be right 92 to 93% of the time, missing about 2.5% of the mail that actually got a reply.&lt;/li&gt;
&lt;li&gt;In the test slice, the need-you bucket was replied to 46% of the time against 30% overall, and the let-it-die bucket about 15%. It is a prioritizer, not an oracle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What went wrong, and how I found it&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every file looked missing.&lt;/strong&gt; Enron filenames end in a dot (1., 10.), and Windows quietly strips it, so Python saw an empty mailbox until I switched to Windows’ extended-length path prefix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first test set had no positives.&lt;/strong&gt; The model scored a meaningless 98% accuracy. A month-by-month table showed replies falling to exactly 0 from June 2001, because the archive stops containing sent mail. I cut the timeline there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma summarized the wrong message.&lt;/strong&gt; For an email where the sender wrote one line on top of a long forwarded chain, the summary described the oldest forwarded message. I found it by reading the cached email text next to the summary. Cutting each email at the first forwarded-message marker fixed that case: over five runs each (the last two of each at temperature 0 with a fixed seed), the unfixed version summarized the forwarded message every time and the fixed version summarized the sender’s reply every time. The prompt for that email also shrank from 649 to 90 tokens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Replied within 7 days” is only a proxy for “needed action,” and “the sender wrote again” is a noisy follow-up signal. One escalated email in my demo came from a mailing list that re-sent the same subject line.&lt;/li&gt;
&lt;li&gt;One mailbox, one time-based split. The bootstrap interval treats emails as independent, though threads and senders cluster, so it is probably a little too tight.&lt;/li&gt;
&lt;li&gt;The parking demo uses 8 emails and a simulated clock, and “archive” writes to a log file instead of moving real mail.&lt;/li&gt;
&lt;li&gt;Gemma output is not deterministic, and identical prompts varied about 2x in latency between runs, so I only quote token counts for the forwarded-chain fix. That fix is one email, not a benchmark, and suggested actions vary between runs.&lt;/li&gt;
&lt;li&gt;Attachments aren’t in the email text, so suggested actions for attachment-only emails are generic.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The model that reads my mail runs on my machine.&lt;/strong&gt; Gemma runs locally through Ollama, and the Sentry trace shows every model call going to 127.0.0.1. There is no API bill and no third party reading the text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The waiting is durable and I don’t run a scheduler.&lt;/strong&gt; Temporal’s open-source tooling gave me timers that survive restarts and retries for flaky steps, with no cron job or home-made queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No training run.&lt;/strong&gt; TabPFN fits a small personal mailbox without a training step. The honest caveat: I used the hosted API, so the 5,000 training rows went to Prior Labs’ servers. I only did that with the public Enron data, and a deployment on private mail would need local weights or to accept that tradeoff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone can reproduce the numbers.&lt;/strong&gt; The data is public and the code is in the repo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn’t test a hosted LLM, so I can’t claim open wins on summary quality. The case for open here is privacy, cost and not depending on a service staying up.&lt;/p&gt;

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

&lt;p&gt;I haven’t pointed it at my own inbox, because I don’t have a two-sided mailbox, so these numbers come from public Enron mail, not from my own week. On the held-out slice of 1,981 emails, the agent would have taken 398 (20%) off the owner’s plate, and about 85% of those really got no reply. The price is the other 15%, about 60 emails that did get a reply and would have been archived unless the sender nudged again, which is about 10% of all the mail that was replied to. Each summary takes about 4 seconds on my laptop. I can’t honestly put a number on minutes saved, so I’m not going to.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of TabPFN:&lt;/strong&gt; the ignore-or-act predictor, compared with a rule and gradient boosting, with a bootstrap interval on the difference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; local gemma3:4b summaries and suggested actions, with a documented failure and fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Temporal:&lt;/strong&gt; one durable workflow per parked email, with a follow-up signal, a 7-day timeout and automatic retries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Sentry Agent Tracing:&lt;/strong&gt; a trace of the whole run with token counts, prompts and responses, and the before and after of the forwarded-chain fix.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Using AI coding tools without losing your fundamentals</title>
      <dc:creator>Khushighosh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:21:17 +0000</pubDate>
      <link>https://dev.to/khushighosh/using-ai-coding-tools-without-losing-your-fundamentals-2e77</link>
      <guid>https://dev.to/khushighosh/using-ai-coding-tools-without-losing-your-fundamentals-2e77</guid>
      <description>&lt;p&gt;AI can write a working login route in 10 seconds. That’s amazing, and also a little dangerous. If the tool always writes the code, you stop practicing the one skill the code depends on: understanding what it does and why it breaks.&lt;/p&gt;

&lt;p&gt;I’m an engineering student who uses AI tools regularly. [One line on which tools you use and for what.] Here are the rules I follow to keep the speed without losing the skills.&lt;/p&gt;

&lt;p&gt;The Problem: Code You Can’t Explain&lt;/p&gt;

&lt;p&gt;The first warning sign is code that works but that you couldn’t rewrite. [Your example: a time AI-generated code ran fine, then broke, and you couldn’t tell why.]&lt;/p&gt;

&lt;p&gt;When something fails, the AI can’t tell you whether the code matches what you meant. Only you can, and only if you understand it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Attempt First, Then Ask&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Spend 10 to 15 minutes on the problem before opening the tool. Even a failed attempt shows you where you actually get stuck, and it helps you judge the AI’s answer because you know what the question was.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask for Explanations, Not Just Code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead of “write a JWT logout route”, try “explain how logout works with stateless JWTs and what my options are.” Then write the code yourself. Use the tool as a tutor first and a code generator second.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Never Paste What You Can’t Explain&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before accepting generated code, explain every line to yourself, or to a rubber duck. If a line is a mystery, ask about that line. The test: could I recreate this tomorrow without the tool?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the Diff, Not Just the Result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;“It runs” is not the same as “it’s correct.” Check these every time:&lt;/p&gt;

&lt;p&gt;What happens on bad input, empty values, or a failed network call?&lt;br&gt;
Did it add a dependency you didn’t need?&lt;br&gt;
Does it match the conventions of the rest of your codebase?&lt;br&gt;
Is anything hardcoded that shouldn’t be, like secrets or URLs?&lt;/p&gt;

&lt;p&gt;AI is great at the happy path and often skips the rest.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep a No-AI Practice Habit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pick one slot, such as one small project a month, where you work with no assistant. Think of it as gym time for your brain. It keeps your syntax recall, debugging and design instincts sharp.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Debug With Your Own Eyes First&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before pasting an error into a chat, read the stack trace and add logs or breakpoints. Reading errors is one of the most valuable skills you can have, and it’s the first to fade when you outsource it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify Against the Docs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI can confidently invent function names, options, and API behavior. When something looks unfamiliar, check the official documentation. [Optional: a time the tool gave you something that didn’t exist.]&lt;/p&gt;

&lt;p&gt;Weekly Self-Check&lt;/p&gt;

&lt;p&gt;Ask yourself:&lt;/p&gt;

&lt;p&gt;Could I explain my last three changes to a teammate?&lt;br&gt;
Did I fix my last bug by understanding it, or by retrying prompts until it vanished?&lt;br&gt;
Could I build this feature without the tool, even if slower?&lt;/p&gt;

&lt;p&gt;If any answer is “no,” slow down for a bit.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;AI is good at producing code and bad at knowing what you need. That judgment is what makes you an engineer. Use the tool to move faster, but stay the person who understands the result.&lt;/p&gt;

&lt;p&gt;What rules do you follow? Drop them in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>6 Windows + Git traps I hit this week (and the 30-second fixes)</title>
      <dc:creator>Khushighosh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 06:55:19 +0000</pubDate>
      <link>https://dev.to/khushighosh/6-windows-git-traps-i-hit-this-week-and-the-30-second-fixes-4h65</link>
      <guid>https://dev.to/khushighosh/6-windows-git-traps-i-hit-this-week-and-the-30-second-fixes-4h65</guid>
      <description>&lt;p&gt;I spent this week setting up a public GitHub repo on Windows. Almost every problem came from a tiny detail, and the error messages didn't explain any of them. Here are six, with the fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;cd /d&lt;/code&gt; fails in PowerShell
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Users\me\Desktop\project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-Location&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;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;positional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cannot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;accepts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;argument&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/d&lt;/code&gt; is a &lt;strong&gt;Command Prompt&lt;/strong&gt; option for switching drives. PowerShell doesn't need it. Check your prompt: if it starts with &lt;code&gt;PS&lt;/code&gt;, you're in PowerShell, so just run:&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="nb"&gt;cd &lt;/span&gt;C:&lt;span class="se"&gt;\U&lt;/span&gt;sers&lt;span class="se"&gt;\m&lt;/span&gt;e&lt;span class="se"&gt;\D&lt;/span&gt;esktop&lt;span class="se"&gt;\p&lt;/span&gt;roject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. "Cannot find module" usually means "wrong folder"
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Cannot find module 'C:\Users\me\evaluate.mjs'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the path in the error. It shows the folder your terminal is in, not the folder you meant. Run &lt;code&gt;cd&lt;/code&gt; into the project folder, then &lt;code&gt;dir&lt;/code&gt; to confirm the file is really there.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Downloads quietly rename your files
&lt;/h2&gt;

&lt;p&gt;Download the same file twice and Windows saves &lt;code&gt;evaluate (1).mjs&lt;/code&gt;. Your code asks for &lt;code&gt;evaluate.mjs&lt;/code&gt; and can't find it. Rename it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ren &lt;span class="s2"&gt;"evaluate (1).mjs"&lt;/span&gt; evaluate.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Notepad saves &lt;code&gt;.gitignore.txt&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Windows File Explorer hides file extensions by default, so a stray &lt;code&gt;.txt&lt;/code&gt; is invisible. I ended up with both &lt;code&gt;.gitignore&lt;/code&gt; and &lt;code&gt;.gitignore.txt&lt;/code&gt;, and committed the wrong one by accident.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Turn on &lt;strong&gt;View → Show → File name extensions&lt;/strong&gt; in Explorer.&lt;/li&gt;
&lt;li&gt;In Notepad's Save As, put the name in quotes (&lt;code&gt;".gitignore"&lt;/code&gt;) and set "Save as type" to &lt;strong&gt;All files&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;dir&lt;/code&gt; in the terminal. It shows the real names.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. &lt;code&gt;git push&lt;/code&gt; says "rejected (fetch first)"
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;! [rejected]  main -&amp;gt; main (fetch first)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It means GitHub already has commits your computer doesn't. This usually happens when you tick "Add a README / .gitignore / licence" while creating a repo, and then push your own project. Prevention: create the repo &lt;strong&gt;empty&lt;/strong&gt;. If it's already happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git log origin/main &lt;span class="nt"&gt;--oneline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at what's there. If you want to keep it, merge with &lt;code&gt;git pull origin main --allow-unrelated-histories&lt;/code&gt;. If it's only auto-generated starter files you don't need, &lt;code&gt;git push --force-with-lease&lt;/code&gt; replaces them, and it refuses if the remote changed since your last fetch. That makes it safer than a plain &lt;code&gt;--force&lt;/code&gt;. Never force-push to a repo where other people have real work.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. A page that calls an API can't be opened by double-clicking
&lt;/h2&gt;

&lt;p&gt;Opening &lt;code&gt;index.html&lt;/code&gt; from your folder gives it an address starting with &lt;code&gt;file://&lt;/code&gt;. If the page fetches data from a local server, you get &lt;code&gt;Failed to fetch&lt;/code&gt;. Start the server, then visit &lt;code&gt;http://localhost:PORT&lt;/code&gt; in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: check before your first &lt;code&gt;git add .&lt;/code&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Write &lt;code&gt;.gitignore&lt;/code&gt; &lt;strong&gt;first&lt;/strong&gt;. Adding a file to &lt;code&gt;.gitignore&lt;/code&gt; after you've committed it doesn't remove it from history.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git status&lt;/code&gt; before committing and read the list. Anything private (data files, &lt;code&gt;.env&lt;/code&gt;, exports) shouldn't be there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these took long to fix, but each one cost me ten minutes of confusion. Hopefully this saves someone else those minutes.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>git</category>
      <category>windows</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Three things that surprised me while building this:

Re-runs were identical - Same model, same prompt, `temperature: 0`: byte-for-byte the same failures every time. That turned prompt tweaking into real A/B testing.

**A regex beat the prompt.** I</title>
      <dc:creator>Khushighosh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 06:43:25 +0000</pubDate>
      <link>https://dev.to/khushighosh/three-things-that-surprised-me-while-building-this-re-runs-were-identical-same-model-same-3cdj</link>
      <guid>https://dev.to/khushighosh/three-things-that-surprised-me-while-building-this-re-runs-were-identical-same-model-same-3cdj</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/khushighosh/order-ledger-a-whatsapp-to-ledger-side-hustle-that-runs-offline-on-open-models-3nk0" class="crayons-story__hidden-navigation-link"&gt;Order Ledger: a WhatsApp-to-ledger side hustle that runs offline on open models&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/YMt1OxmEDiQ" title="Order Ledger: a WhatsApp-to-ledger side hustle that runs offline on open models"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/khushighosh/order-ledger-a-whatsapp-to-ledger-side-hustle-that-runs-offline-on-open-models-3nk0" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/khushighosh" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F4152360%2F6ac43df7-8b20-4acc-913f-03da2c9dd0ef.jpg" alt="khushighosh profile" class="crayons-avatar__image" width="236" height="236"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/khushighosh" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Khushighosh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Khushighosh
                
                
              
              &lt;div id="story-author-preview-content-4856517" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/khushighosh" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F4152360%2F6ac43df7-8b20-4acc-913f-03da2c9dd0ef.jpg" class="crayons-avatar__image" alt="" width="236" height="236"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Khushighosh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/khushighosh/order-ledger-a-whatsapp-to-ledger-side-hustle-that-runs-offline-on-open-models-3nk0" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 15&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/khushighosh/order-ledger-a-whatsapp-to-ledger-side-hustle-that-runs-offline-on-open-models-3nk0" id="article-link-4856517"&gt;
          Order Ledger: a WhatsApp-to-ledger side hustle that runs offline on open models
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hf26challenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hf26challenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ollama"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ollama&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/khushighosh/order-ledger-a-whatsapp-to-ledger-side-hustle-that-runs-offline-on-open-models-3nk0" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/khushighosh/order-ledger-a-whatsapp-to-ledger-side-hustle-that-runs-offline-on-open-models-3nk0#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>llm</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Order Ledger: a WhatsApp-to-ledger side hustle that runs offline on open models</title>
      <dc:creator>Khushighosh</dc:creator>
      <pubDate>Thu, 15 Oct 2026 12:45:33 +0000</pubDate>
      <link>https://dev.to/khushighosh/order-ledger-a-whatsapp-to-ledger-side-hustle-that-runs-offline-on-open-models-3nk0</link>
      <guid>https://dev.to/khushighosh/order-ledger-a-whatsapp-to-ledger-side-hustle-that-runs-offline-on-open-models-3nk0</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;Small sellers run on WhatsApp. A home baker, a tiffin service or a tailor gets orders like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C: bhaiya kal subah 20 samosa aur 1 dozen kachori chahiye
S: ho jayega, 20 samosa 10 rs each = 200, kachori 12 x 15 = 180. total 380
C: thik hai, paise delivery pe dungi. Neha, 9000000002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then they copy it into a notebook, and a week later nobody remembers who paid the advance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Order Ledger&lt;/strong&gt; turns that chat into a structured order, shortened here:&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;"customer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Neha"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9000000002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&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="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"samosa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pcs"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kachori"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pcs"&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;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"delivery_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"delivery_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"morning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;380&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"paid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"confirmed"&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;The seller checks it on a review screen, saves it, and gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;schedule&lt;/strong&gt; grouped by day, with "today" and "overdue" markers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pending payments&lt;/strong&gt;, with the total owed and a button that copies a polite WhatsApp reminder&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;all orders&lt;/strong&gt; table with a CSV export for the accountant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Who would pay?&lt;/strong&gt; Anyone who takes orders in chat and tracks them by hand: home bakers, cloud kitchens, tailors, small shops. The pain is the retyping and the money that slips through. Customer names, numbers and prices are also private, which is why the whole thing runs on the seller's own laptop.&lt;/p&gt;

&lt;p&gt;It handles English, Hinglish and Kannada in English letters, and it accepts exported WhatsApp chats as they are.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/YMt1OxmEDiQ" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The demo shows: pasting a WhatsApp-style chat, reading the order, fixing a field on the review screen, the schedule and pending-payments tabs, and then &lt;strong&gt;Wi-Fi switched off&lt;/strong&gt; with one more order processed.&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/Khushighosh" rel="noopener noreferrer"&gt;
        Khushighosh
      &lt;/a&gt; / &lt;a href="https://github.com/Khushighosh/Order-Ledger" rel="noopener noreferrer"&gt;
        Order-Ledger
      &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;Order Ledger&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Turn messy WhatsApp order chats into a clean order book. It runs entirely on your own computer with an open-weight model, so customer names, phone numbers and prices never leave your laptop.&lt;/p&gt;

&lt;p&gt;Built for the Hacktoberfest Open-Source AI Challenge, Week 2 (Side Hustle).&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Who it's for&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Home bakers, tiffin and cloud-kitchen sellers, tailors and small shops who take orders over WhatsApp and keep track in a notebook. Their chats are messy: English mixed with Hindi or Kannada, "kal subah", "advance 500 diya", orders that change halfway through. You paste the chat, and the app turns it into a structured order.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Reads a pasted WhatsApp chat (an exported chat, text copied from WhatsApp Web, or lines typed with &lt;code&gt;C:&lt;/code&gt; for the customer and &lt;code&gt;S:&lt;/code&gt; for you) and extracts the customer, phone, items, quantities, delivery date and time, total, amount paid and status.&lt;/li&gt;
&lt;li&gt;Shows the result on…&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/Khushighosh/Order-Ledger" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. There is nothing to &lt;code&gt;npm install&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull qwen2.5:7b
git clone https://github.com/Khushighosh/Order-Ledger.git
&lt;span class="nb"&gt;cd &lt;/span&gt;Order-Ledger
node server.mjs      &lt;span class="c"&gt;# then open http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;p&gt;&lt;strong&gt;The open pieces&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, serving &lt;code&gt;qwen2.5:7b&lt;/code&gt; (4.7 GB) as the default and &lt;code&gt;llama3.2:3b&lt;/code&gt; for comparison&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App:&lt;/strong&gt; Node.js with no packages (built-in &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;fetch&lt;/code&gt;), one HTML file for the UI, and a JSON file for storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests:&lt;/strong&gt; a small evaluation harness I wrote for this
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pasted chat ──► convertChat()  WhatsApp export → "C:" / "S:" lines (in the browser)
            ──► annotateDates() "kal" → "kal (2026-10-14)"
            ──► Ollama (qwen2.5:7b, temperature 0, JSON-schema output)
            ──► clean()         validate phone, units, dates (server)
            ──► review screen ──► orders.json ──► schedule / pending / CSV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Structured output, not "please reply in JSON"
&lt;/h3&gt;

&lt;p&gt;Ollama accepts a JSON schema as &lt;code&gt;format&lt;/code&gt;, so the model's reply always parses and uses only allowed values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;anyOf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;kg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pcs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;litre&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;box&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;null&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tentative&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancelled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema guarantees the &lt;strong&gt;shape&lt;/strong&gt; of the answer, not that it's &lt;strong&gt;right&lt;/strong&gt;. That gap is why there's a review screen and a test harness.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Let code do code's job
&lt;/h3&gt;

&lt;p&gt;Small models are unreliable at date arithmetic, so the date lookup happens in code and the model only reads the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;annotateDates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;day after tomorrow&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;parso&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;!after &lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;tomorrow&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;kal&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;nale&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;aaj&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;today&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cal&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="nx"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;iso&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// "nale 2 litre dosa hittu"  →  "nale (2026-10-14) 2 litre dosa hittu"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phone numbers are cleaned in code too, because the model kept returning &lt;code&gt;+91 90000 00009&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Measure everything
&lt;/h3&gt;

&lt;p&gt;I wrote 18 synthetic WhatsApp-style chats (fake names and numbers) and hand-labelled the expected JSON for each. &lt;code&gt;evaluate.mjs&lt;/code&gt; scores 9 fields per chat and breaks results down per field and per kind of chat (Hinglish, Kannada, same-day orders, "same as last time", and so on). With &lt;code&gt;temperature: 0&lt;/code&gt;, re-running an unchanged prompt gave identical results, so every prompt change could be A/B tested.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test set&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Before the date fix&lt;/th&gt;
&lt;th&gt;After the date fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;12 chats (I tuned on these)&lt;/td&gt;
&lt;td&gt;qwen2.5:7b&lt;/td&gt;
&lt;td&gt;11/12 orders correct · 99% of fields&lt;/td&gt;
&lt;td&gt;9/12 · 96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12 chats&lt;/td&gt;
&lt;td&gt;llama3.2:3b&lt;/td&gt;
&lt;td&gt;1/12 · 82%&lt;/td&gt;
&lt;td&gt;2/12 · 84%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6 fresh chats&lt;/td&gt;
&lt;td&gt;qwen2.5:7b&lt;/td&gt;
&lt;td&gt;3/6 · 91%&lt;/td&gt;
&lt;td&gt;5/6 · 96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6 fresh chats&lt;/td&gt;
&lt;td&gt;llama3.2:3b&lt;/td&gt;
&lt;td&gt;3/6 · 89%&lt;/td&gt;
&lt;td&gt;3/6 · 91%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On a laptop the 7B model takes about 14 seconds per order once loaded (about 50 seconds for the first order while the model loads). The 3B takes about 7.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. What went wrong (the useful part)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One extra example made the prompt worse.&lt;/strong&gt; Adding a fourth few-shot example dropped the 7B from 10/12 to 7/12 orders correct. With that example in the prompt, the 3B answered "same as last time" by inventing a pineapple cake order. I removed the example and the score came back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A date bug hiding in my prompt.&lt;/strong&gt; For "aaj" (today) the 7B answered &lt;code&gt;2026-10-20&lt;/code&gt;, and for "nale" (Kannada for tomorrow) it answered &lt;code&gt;2026-10-17&lt;/code&gt;. The cause was that the date table in my prompt listed "Tuesday: 2026-10-20" (next week), and the test day was a Tuesday. The model read "today is Tuesday" and looked it up. I fixed it by labelling that row "next Tuesday" and moving the relative-date lookup into code. The fresh set went from 3/6 to 5/6.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two of my labels were debatable.&lt;/strong&gt; Is "2 meals" an item named "meal" in pcs, or "tiffin" in boxes? I left those as known misses instead of tuning them away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 3B is fast and wrong.&lt;/strong&gt; Only 42% of its items were right on the 12-chat set, so I didn't ship it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;An honest note on the numbers:&lt;/strong&gt; I used both test sets to tune the prompt, and the date fix came after looking at the fresh set's failures. Treat these as indicative, not a benchmark. That's the reason the review screen exists.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by default.&lt;/strong&gt; A chat contains customer names, phone numbers and prices. Here it never leaves the laptop: the server only listens on &lt;code&gt;127.0.0.1&lt;/code&gt;, and orders live in a local file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No per-order cost.&lt;/strong&gt; The marginal cost of an order is the electricity on the seller's laptop, so a small seller can use it as much as they like.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could test and choose.&lt;/strong&gt; In an afternoon I ran two models through the same harness, saw that the 3B wasn't good enough, and kept the 7B. Swapping a model is one setting (&lt;code&gt;MODEL=...&lt;/code&gt;). With a closed API I couldn't have inspected the setup this way, and an API-side model update could silently change the results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducible.&lt;/strong&gt; The same model, prompt and &lt;code&gt;temperature: 0&lt;/code&gt; gave identical results on re-runs, which made the debugging above possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The trade-off, honestly:&lt;/strong&gt; I didn't benchmark a closed API, so I'm not claiming open models are more accurate. A hosted frontier model is probably better at the tricky chats and faster per order. What open models gave me is privacy, a zero running cost and full control. For a seller who handles customers' private data on a laptop, that was the better deal.&lt;/p&gt;

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

&lt;p&gt;I built this in a long chat session with Claude as a pair programmer. It drafted the first versions of the prompt, server and UI, and I ran everything on my own laptop, pasted the failures back, and made the decisions: which idea to build, which model to keep, what to revert. The best moments came from the failures, like the &lt;code&gt;2026-10-20&lt;/code&gt; date bug.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overall winner (Side Hustle theme).&lt;/strong&gt; Open-weight models (Qwen2.5 and Llama 3.2) running locally through Ollama are the core of the product, and the code is MIT-licensed on GitHub.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ollama</category>
      <category>ai</category>
    </item>
    <item>
      <title>Go Outside, Take a Photo, Let a Tiny AI Argue About It 🌇</title>
      <dc:creator>Khushighosh</dc:creator>
      <pubDate>Fri, 09 Oct 2026 20:36:06 +0000</pubDate>
      <link>https://dev.to/khushighosh/go-outside-take-a-photo-let-a-tiny-ai-argue-about-it-1n8a</link>
      <guid>https://dev.to/khushighosh/go-outside-take-a-photo-let-a-tiny-ai-argue-about-it-1n8a</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I asked a small AI model on my laptop to tell me how the sky changed between two sunsets. It told me there were more clouds in the first photo. Then I swapped the photos, and it said the exact same thing.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Same Spot&lt;/strong&gt; is a "what changed?" machine for the sky. 🌤️&lt;/p&gt;

&lt;p&gt;Every evening the sky puts on a different show. Same terrace, same skyline, completely different mood. I wanted to know whether a small AI model running on my own laptop could notice those differences the way I do: more clouds, a pinker sky, a hazier horizon, or the camera simply pointing somewhere else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it gets you outside&lt;/strong&gt; 🚶‍♀️&lt;br&gt;
The photos are the whole point. You step outside at sunset, take a picture from the same spot, and go back to your life. The laptop only comes in afterwards, so the screen is the shortest part of the experience. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who it's for&lt;/strong&gt; 🌿&lt;br&gt;
Anyone who keeps a loose record of a place: gardeners watching a patch of soil, hikers returning to a viewpoint, bird watchers, or people who just like watching the same sky.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The twist&lt;/strong&gt; 🔄🧐&lt;br&gt;
I don't trust the model's first answer. Every pair of photos is compared &lt;strong&gt;twice, in both directions&lt;/strong&gt;, and I then check each claim against my own eyes. That's how I caught it inventing shadows, missing camera movement, and giving the same answer no matter which photo came first.&lt;/p&gt;

&lt;p&gt;Everything runs &lt;strong&gt;offline on a CPU-only laptop&lt;/strong&gt;, with no cloud, no account and no uploads. ☁️🚫&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/wHqG7TuP0dY" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Khushighosh" rel="noopener noreferrer"&gt;
        Khushighosh
      &lt;/a&gt; / &lt;a href="https://github.com/Khushighosh/same-spot" rel="noopener noreferrer"&gt;
        same-spot
      &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;p&gt;# Same Spot&lt;/p&gt;
&lt;p&gt;A local, no-cloud tool that compares photos of the same outdoor spot taken on different evenings, using an open-weight model (Gemma 3 4B via Ollama) on a CPU-only laptop.&lt;/p&gt;
&lt;p&gt;## How it works&lt;/p&gt;
&lt;p&gt;- &lt;code&gt;run\_all.py&lt;/code&gt; sends each pair of photos to Gemma 3 in both directions (forward and reversed)&lt;/p&gt;
&lt;p&gt;- results are saved to SQLite and rendered in &lt;code&gt;results.html&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;- a claim only counts if it survives the swap AND matches what I can see&lt;/p&gt;
&lt;p&gt;## Run it&lt;/p&gt;
&lt;p&gt;1. Install Ollama and run &lt;code&gt;ollama pull gemma3:4b&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2. &lt;code&gt;pip install ollama pillow&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;3. Edit the &lt;code&gt;PHOTOS&lt;/code&gt; list in &lt;code&gt;run\_all.py&lt;/code&gt;, then &lt;code&gt;python run\_all.py&lt;/code&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Findings&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Brightness and warmth: often right, but not always (it once called the 9 Sept sky more orange, which was wrong).&lt;/li&gt;
&lt;li&gt;Framing: after the prompt fix it notices the framing differs in every run, but understates it ("slightly different").&lt;/li&gt;
&lt;li&gt;Position bias: in one pair it…&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/Khushighosh/same-spot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Screenshots of the comparison
&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%2Fqxo3nvulr1njf58lspog.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%2Fqxo3nvulr1njf58lspog.png" alt="Results for 9 Sept to 16 Sept: the forward and reverse runs shown next to the two sunset photos" width="800" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7z3rz8l5wpz0fne6uj4w.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%2F7z3rz8l5wpz0fne6uj4w.png" alt="Results for 16 Sept to 19 Sept: the model says " width="799" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It 🛠️🤖
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Gemma 3 4B, an open-weight model that can read images, run through Ollama.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardware:&lt;/strong&gt; an Intel i7-13620H laptop with 16 GB RAM and no GPU. Each comparison took about 60 seconds (58 to 64 s across my runs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline:&lt;/strong&gt; a Python script resizes each photo, sends a pair to Gemma, saves the result in SQLite, and builds a plain &lt;code&gt;results.html&lt;/code&gt; page with the photos and findings side by side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The method that mattered:&lt;/strong&gt; every pair is compared &lt;strong&gt;in both directions&lt;/strong&gt; (A to B, then B to A). A real finding should flip when I swap the order, and I then check each claim against my own eyes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What went wrong first.&lt;/strong&gt; My first prompt made the model invent shadows in silhouette scenes, say "city skyline remains consistent" when the foreground had completely changed, and treat a placeholder I forgot to replace ("EDIT TIME") as a real time of day. It also mixed up two causes: my photos were taken on different days &lt;em&gt;and&lt;/em&gt; at different minutes relative to sunset, so "brighter" could mean either.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I changed.&lt;/strong&gt; I told the model roughly when sunset is, asked it to say whether the framing differs before comparing anything, removed shadows from the prompt, and made it put anything it couldn't judge into an "uncertain" list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results after the fix&lt;/strong&gt; 📊, scored by me against the photos:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pair&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;✅Correct&lt;/th&gt;
&lt;th&gt;⚠️Debatable&lt;/th&gt;
&lt;th&gt;❌Wrong&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;9 Sept to 16 Sept&lt;/td&gt;
&lt;td&gt;forward&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9 Sept to 16 Sept&lt;/td&gt;
&lt;td&gt;reverse&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16 Sept to 19 Sept&lt;/td&gt;
&lt;td&gt;forward&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16 Sept to 19 Sept&lt;/td&gt;
&lt;td&gt;reverse&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;11 of 19 claims correct, 5 debatable, 3 wrong: useful, but not trustworthy on its own.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What improved: no more invented shadows, the model now notices that the framing differs in every run, and it names specific foreground objects correctly (a dark metal beam in one photo, a blue roof in another).&lt;/p&gt;

&lt;p&gt;What still fails: it understates the framing change ("slightly different" when the camera angle is clearly different), and in one pair it said &lt;strong&gt;"more clouds in Image 1" in both directions&lt;/strong&gt;, attaching the claim to whichever photo came first. It also got the sky colour wrong in one run. That is exactly the kind of mistake the both-directions check exists to catch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'd try next:&lt;/strong&gt; describe each photo on its own first, then compare the two text descriptions, so the model never sees two images in a fixed order. I'd also mount my phone in one spot on the terrace, so the framing stays the same and the sky is the only thing that changes.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Because the best thing I found only happened because I was allowed to poke the model.&lt;/strong&gt; 🔄&lt;/p&gt;

&lt;p&gt;Catching the position bias meant running every pair twice, rewriting the prompt again and again, and reading the raw output. With Gemma on my laptop, each extra run cost me about a minute and nothing else. Free to re-run meant free to doubt.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔒 &lt;strong&gt;My sunset photos never left my laptop.&lt;/strong&gt; The only copies online are the ones I chose to publish here.&lt;/li&gt;
&lt;li&gt;🔁 &lt;strong&gt;I could cross-examine it.&lt;/strong&gt; I controlled the model and the prompt, so I could swap the photo order, read what it actually said, and spot the invented shadows and the "more clouds in Image 1" habit.&lt;/li&gt;
&lt;li&gt;📡 &lt;strong&gt;It works anywhere.&lt;/strong&gt; No account, no connection, and no GPU, just a CPU-only laptop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A bigger closed model would probably describe the sky better, and I'd rather say that than hide it. But I'd have had to trust its answer. With this one, I could check it.&lt;/p&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; 🤖: Gemma 3 4B is the whole engine. It's small enough to run locally through Ollama on a CPU-only laptop (about 60 seconds per comparison), and it reads images, so it could compare my sunset photos directly. I then tested it by running every pair in both directions and scoring 19 of its claims against the photos. That showed what Gemma gets right (named foreground objects), where it struggles (understating camera movement), and where it shows position bias.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>samespot</category>
      <category>touchgrass</category>
    </item>
    <item>
      <title>Study Buddy: an offline GATE study partner for my friend, built with Ollama</title>
      <dc:creator>Khushighosh</dc:creator>
      <pubDate>Sun, 04 Oct 2026 20:47:08 +0000</pubDate>
      <link>https://dev.to/khushighosh/study-buddy-an-offline-gate-study-partner-for-my-friend-built-with-ollama-kd7</link>
      <guid>https://dev.to/khushighosh/study-buddy-an-offline-gate-study-partner-for-my-friend-built-with-ollama-kd7</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Study Buddy&lt;/strong&gt;&lt;/em&gt; is an offline exam study partner I built for my friend &lt;strong&gt;Anushka&lt;/strong&gt;, who is preparing for &lt;strong&gt;GATE&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;GATE has a huge syllabus, and she wanted quick answers and practice questions from her own notes. I also wanted her study material to stay on her laptop and not go to someone else's server.&lt;/p&gt;

&lt;p&gt;You add your notes (PDF, text or markdown) to a folder, and Study Buddy does three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Answers from your notes only.&lt;/strong&gt; Every answer cites its source, like &lt;code&gt;[notes.pdf p.3]&lt;/code&gt;. If the notes don't cover a question, it says so instead of guessing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quizzes you on your own material.&lt;/strong&gt; It writes multiple-choice questions from the notes and explains each answer with a pointer to the source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracks your weak spots.&lt;/strong&gt; It remembers which topics you get wrong and lets you practice them, so it works like a study partner and not just a chatbot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs on a laptop. After the one-time download of the models, nothing is uploaded anywhere and it works with Wi-Fi off.&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/NCX1b1YHz8M" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl7ju6agxmfn1b7eix5bd.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%2Fl7ju6agxmfn1b7eix5bd.png" alt="Screenshot of Study Buddy in a browser: the Ask tab with a question about the notes and an answer that cites its source file" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Anushka said after using it:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I usually spend a lot of time searching through my GATE notes whenever I get stuck on a topic. With Study Buddy, I can just ask a question and get an answer directly from my own notes, along with the page where it came from.&lt;/p&gt;

&lt;p&gt;The quiz feature is my favorite because it helps me figure out which topics I actually don't understand instead of just rereading everything. And knowing that my notes stay on my laptop makes me much more comfortable putting all my study material into it.&lt;/p&gt;
&lt;/blockquote&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/Khushighosh" rel="noopener noreferrer"&gt;
        Khushighosh
      &lt;/a&gt; / &lt;a href="https://github.com/Khushighosh/study-buddy" rel="noopener noreferrer"&gt;
        study-buddy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Offline exam study buddy: ask questions and get quizzes from your own notes, using Ollama, local embeddings and ChromaDB. Nothing leaves your laptop.
    &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;Study Buddy&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;An offline exam study partner built for a friend. It answers questions and generates quizzes from their own notes, running entirely on a laptop with open models. Nothing is uploaded anywhere.&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;Ask questions and get answers with citations to the notes&lt;/li&gt;
&lt;li&gt;Says "This isn't covered in your notes" instead of guessing&lt;/li&gt;
&lt;li&gt;Multiple-choice quizzes generated from the notes&lt;/li&gt;
&lt;li&gt;Tracks weak topics and offers to practice them&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ingest.py&lt;/code&gt; splits your notes into chunks and embeds them with &lt;code&gt;nomic-embed-text&lt;/code&gt; into a local ChromaDB store.&lt;/li&gt;
&lt;li&gt;For each question, the closest chunks are retrieved and passed to &lt;code&gt;qwen2.5:7b&lt;/code&gt; with strict instructions to answer only from them and cite the source.&lt;/li&gt;
&lt;li&gt;Quiz mode asks the model for one multiple-choice question per chunk as JSON, validates it, and logs each result in SQLite so weak topics can be practiced again.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Stack&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Ollama (qwen2.5:7b and nomic-embed-text), ChromaDB, SQLite, FastAPI, plain HTML.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tested&lt;/h2&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Khushighosh/study-buddy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;The whole thing is open-source and local:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; runs the models on the laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;qwen2.5:7b&lt;/strong&gt; (open-weight) writes the answers and quiz questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nomic-embed-text&lt;/strong&gt; (open-weight) turns notes into embeddings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ChromaDB&lt;/strong&gt; stores the vectors locally; &lt;strong&gt;SQLite&lt;/strong&gt; stores quiz results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; serves a small plain-HTML page, so it's used in a browser and not in a terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ingest.py&lt;/code&gt; extracts text from the notes, splits it into overlapping chunks, and stores each chunk with its file name and page number so answers can cite sources.&lt;/li&gt;
&lt;li&gt;For each question, the closest chunks are retrieved and passed to the model with strict instructions: answer only from these notes, cite them, and say so if the answer isn't there.&lt;/li&gt;
&lt;li&gt;For quizzes, the model is asked for one question as JSON (&lt;code&gt;topic&lt;/code&gt;, &lt;code&gt;question&lt;/code&gt;, four options, &lt;code&gt;answer_index&lt;/code&gt;, &lt;code&gt;explanation&lt;/code&gt;). The code validates the JSON and retries once if it's malformed.&lt;/li&gt;
&lt;li&gt;Each answer is logged in SQLite by topic, so "Practice my weak topic" picks the topic with the lowest accuracy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tested on:&lt;/strong&gt; a Windows laptop with 16 GB RAM, an Intel Core i7-13620H and no dedicated GPU, so everything runs on the CPU. Timings were about 55 s for the first answer after starting the server (the model has to load), about 42 s for a normal answer, and about 35 s per quiz question. That's slower than a cloud chatbot, but it works on an ordinary laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; a 7B model can still misread a passage, so every answer shows its source for checking. Answers are slow on a CPU-only laptop, and notes are added through a folder and an indexing command, not an upload button.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy:&lt;/strong&gt; notes never leave the laptop. With a closed API, every question would have sent her study material to a server I don't control. Here I could demo it with Wi-Fi off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; it costs nothing per question, so people can ask as much as they like during revision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control:&lt;/strong&gt; the models are open weights, so swapping one is a one-line change (&lt;code&gt;CHAT_MODEL&lt;/code&gt; in &lt;code&gt;rag.py&lt;/code&gt;), which lets someone with a weaker laptop trade answer quality for speed. I also wrote the prompts so the model refuses when the notes don't cover something, which matters for exam prep.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducible:&lt;/strong&gt; anyone with a similar laptop can run it from the README. There are no API keys or accounts.&lt;/li&gt;
&lt;/ul&gt;

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