<?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: Raman Tiwari</title>
    <description>The latest articles on DEV Community by Raman Tiwari (@ramantiw45).</description>
    <link>https://dev.to/ramantiw45</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%2F4160245%2F853fe481-ef14-461d-a9f2-06ed47b0cf95.png</url>
      <title>DEV Community: Raman Tiwari</title>
      <link>https://dev.to/ramantiw45</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ramantiw45"/>
    <language>en</language>
    <item>
      <title>Sloth Ledger: the model has an opinion, the ledger has the numbers</title>
      <dc:creator>Raman Tiwari</dc:creator>
      <pubDate>Sat, 24 Oct 2026 17:18:05 +0000</pubDate>
      <link>https://dev.to/ramantiw45/sloth-ledger-the-model-has-an-opinion-the-ledger-has-the-numbers-22gc</link>
      <guid>https://dev.to/ramantiw45/sloth-ledger-the-model-has-an-opinion-the-ledger-has-the-numbers-22gc</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Describe a chore in plain English. An open-weight model decides whether to automate it, hand it to a human, or ignore it forever. When an automation actually runs, the reclaimed minutes go into a Tiger Cloud hypertable, and every figure on screen prints the arithmetic that produced it. Runs locally with &lt;code&gt;python app.py&lt;/code&gt;; also deployed on Render's free plan, where the web process is disposable and the ledger is not.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/ramantiw45/SlothLedger" rel="noopener noreferrer"&gt;https://github.com/ramantiw45/SlothLedger&lt;/a&gt;&lt;br&gt;
Live demo: &lt;a href="https://sloth-ledger.onrender.com" rel="noopener noreferrer"&gt;https://sloth-ledger.onrender.com&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%2F3ho1dm45w20wvsztb7hy.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%2F3ho1dm45w20wvsztb7hy.png" alt="Sloth Ledger on a laptop: a totals card reading 15m reclaimed this week, a new-entry card, and a tray of index-style cards with Automate, Delegate and Ignore Forever stamps, each showing its evidence line." width="800" height="833"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem with "this saved me hours"
&lt;/h2&gt;

&lt;p&gt;Every automation post ends the same way. The tool saves you time, the author says so, and the number is whatever the author felt like typing.&lt;/p&gt;

&lt;p&gt;I have written two of those posts this month. So this week I wanted to build the opposite: a ledger that refuses to print a saving it cannot show. Not a productivity dashboard with a big friendly number, but the receipt behind the number, including the part where the automation did not pay off.&lt;/p&gt;

&lt;p&gt;The theme is Slow and Steady. The slow part is the accounting. The steady part is that the accounting keeps working after the demo is over.&lt;/p&gt;
&lt;h2&gt;
  
  
  One rule, enforced in code
&lt;/h2&gt;

&lt;p&gt;The model never produces a figure that ends up in the ledger.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the chore carries a countable quantity, the minutes come from &lt;strong&gt;count × a per-item rate&lt;/strong&gt; kept in a plain table in &lt;code&gt;sloth/model.py&lt;/code&gt;: a receipt is 20 seconds, a statement line 30, a file rename 8. You can read the table and argue with it.&lt;/li&gt;
&lt;li&gt;If you measured the chore yourself, your number wins.&lt;/li&gt;
&lt;li&gt;Only when there is nothing countable does the model's estimate stand, clamped to 1–480 minutes.&lt;/li&gt;
&lt;li&gt;Weekly and all-time totals come from SQL. Never from text a model generated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a live call during the build, the model proposed 20 minutes for &lt;em&gt;renamed 40 receipts from the shoebox&lt;/em&gt;. The ledger booked 13, because 40 × 20 seconds is arithmetic and arithmetic belongs to the database. That disagreement is the feature. A card prints &lt;code&gt;40 × 8s = 5 min&lt;/code&gt; under its description, so you can check it with a calculator and a bad attitude.&lt;/p&gt;

&lt;p&gt;The trust boundary is not "we used a good model". It is that the opinion and the number travel on separate wires.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;The interface is a card tray, not a dashboard: manila index cards on a dark desk, verdicts as rubber stamps, a punch hole in every card whose minutes came back, and a total that gets restamped when the ledger changes.&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%2Fqe9hm23shf78jz0cunq3.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%2Fqe9hm23shf78jz0cunq3.png" alt="Sloth Ledger at phone width: the totals card, the entry card, and stacked chore cards with their stamps and evidence lines." width="631" height="2671"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I went this way because every productivity tool looks like the same grid of rounded KPI tiles, and a ledger should look like something you could drop on a desk. The ruled lines are not decoration: typed text sits on them at a fixed 28px rhythm.&lt;/p&gt;
&lt;h2&gt;
  
  
  Slow and steady, measured
&lt;/h2&gt;

&lt;p&gt;The demo ledger, after eight chore entries and four automation runs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reclaimed this week&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15m&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All time&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;43m&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Still on you&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16m&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chores judged&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automations run&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The live demo is a shared ledger: its totals move as visitors use it. The table and the screenshots are the snapshot from the build; the arithmetic printed on each card is the part that does not move.&lt;/p&gt;

&lt;p&gt;That &lt;em&gt;still on you&lt;/em&gt; line is the one I care about. It is the manual time in chores the tool declined to automate, and it is allowed to grow. A ledger where every number only goes up is a sales pitch, not a record.&lt;/p&gt;

&lt;p&gt;Fifteen minutes in a week is not a headline. It is what an honest first week looks like when you count setup time against savings and refuse to round up.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why open weights are the core, not a badge
&lt;/h2&gt;

&lt;p&gt;The judgement lane is &lt;strong&gt;Qwen 3.8 27B&lt;/strong&gt;, an open-weight model served over an OpenAI-compatible API by Groq. Three environment variables point the app at any compatible host, including a local server on &lt;code&gt;http://127.0.0.1:11434/v1&lt;/code&gt; if you prefer to keep it on your own machine. The demo used the hosted lane; the model that judged those eight chores was &lt;code&gt;qwen/qwen3.8-27b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Under it sits a deterministic rules floor. With no key, no network, and no model, the app still answers: same output shape, same ledger, a little dumber about intent. That means the product never depends on a vendor staying online, staying affordable, or staying in business.&lt;/p&gt;

&lt;p&gt;Why not a closed model? Two reasons, and the second is the one judges should care about.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Swap cost.&lt;/strong&gt; The model is one file and one HTTP call. Replacing it with next year's open-weight model is a config change, not a migration. The per-item rates, the schema, and the arithmetic do not move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditability.&lt;/strong&gt; The whole argument of this project is that an opinion and a number should not come from the same black box. A closed API that both judged the chore and totalled the savings would reproduce exactly the problem the ledger exists to solve.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open innovation here is not a licensing preference. It is what lets a stranger re-run my arithmetic on their own machine and get the same ledger.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;


&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  A[Browser: card tray] --&amp;gt;|POST /api/log| B[Python stdlib server]
  B --&amp;gt; C{Judgement lane}
  C --&amp;gt;|key set| D[Qwen 3.8 27B&amp;lt;br/&amp;gt;OpenAI-compatible API]
  C --&amp;gt;|no key| E[Deterministic rules floor]
  D --&amp;gt; F[Reconcile: count x rate beats estimate]
  E --&amp;gt; F
  F --&amp;gt; G[(Tiger Cloud&amp;lt;br/&amp;gt;hypertable + continuous aggregate)]
  G --&amp;gt;|SQL totals| A&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The stack is deliberately small: &lt;code&gt;http.server&lt;/code&gt;, vanilla HTML/CSS/JS, SQLite as the default store, and &lt;code&gt;pg8000&lt;/code&gt; (pure Python) for the Postgres lane. Python 3.14 has no wheels for most Postgres drivers, and a compiler on the judge's laptop is a bad install story.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Tiger Data is doing that SQLite is not
&lt;/h3&gt;

&lt;p&gt;The reclaimed minutes are a time series, so they live in a hypertable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reclaimed&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;reclaimed_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chore_id&lt;/span&gt;     &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chore&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;automation&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;minutes&lt;/span&gt;      &lt;span class="nb"&gt;numeric&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;source&lt;/span&gt;       &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'run'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;create_hypertable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ledger.reclaimed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'reclaimed_at'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The top-automations panel does not scan that table. It reads &lt;code&gt;ledger.reclaimed_daily&lt;/code&gt;, a continuous aggregate bucketed by day with a refresh policy, so the query stays small as history grows. A compression policy moves chunks older than 30 days to columnar storage. In this deployment the app connects as a least-privilege role (&lt;code&gt;sloth_app&lt;/code&gt;) with grants on the ledger schema only, not as the database owner.&lt;/p&gt;

&lt;p&gt;None of that is visible in a screenshot, which is the point: it is the difference between a demo that survives a year of entries and one that gets slower every week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployed on Render, including the parts that sleep
&lt;/h2&gt;

&lt;p&gt;The public demo is a Render web service built from the repo's &lt;code&gt;render.yaml&lt;/code&gt;: Python 3.12, free plan, Singapore region, health check on &lt;code&gt;/healthz&lt;/code&gt;, and a build step that installs exactly one package. The two secrets are &lt;code&gt;sync: false&lt;/code&gt; in the blueprint and pasted into the dashboard once, so neither the Tiger connection string nor the model key ever enters git.&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%2F4yyqam5golmpb77omdis.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%2F4yyqam5golmpb77omdis.png" alt="Sloth Ledger running on Render: the tray with a 15m week and 43m all time, and the header reading public demo · shared ledger." width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Render's free plan sleeps after fifteen minutes without traffic, and the first request afterwards takes a few seconds. That constraint is why the split matters: the web process is disposable, the ledger is not. I can redeploy the service and every reclaimed minute is still there when it wakes, because the minutes live in the hypertable, not on the instance's disk.&lt;/p&gt;

&lt;p&gt;Because every visitor shares one ledger, the deployed build sets &lt;code&gt;DEMO_MODE=1&lt;/code&gt;: writes are rate-limited to eight per visitor per ten minutes, and the header says &lt;code&gt;public demo · shared ledger&lt;/code&gt;. The page is honest about being a demo instead of pretending the seeded chores are yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four things that broke, written down
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Groq returned 403 with no useful body.&lt;/strong&gt; Python's default &lt;code&gt;User-Agent&lt;/code&gt; was being rejected by the WAF in front of the API. Identifying the client (&lt;code&gt;sloth-ledger/0.1&lt;/code&gt;) fixed it. This is the kind of bug that looks like a bad key for an hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;llama-3.3-70b-versatile&lt;/code&gt; was gone.&lt;/strong&gt; The model name from every tutorial returned 404. Listing &lt;code&gt;/models&lt;/code&gt; for the key showed the current catalogue and I moved to &lt;code&gt;qwen/qwen3.8-27b&lt;/code&gt;. Pinning a model name in a tutorial is a gift with an expiry date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiger returns &lt;code&gt;numeric&lt;/code&gt; as &lt;code&gt;Decimal&lt;/code&gt; and timestamps as &lt;code&gt;datetime&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;json.dumps&lt;/code&gt; refused both. The fix belongs at the store boundary, not in every route: one normalising step in &lt;code&gt;_rows&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first deployed request used the rules floor and said nothing.&lt;/strong&gt; Falling back quietly is the right behaviour for a person typing a chore and the wrong behaviour for me. I added one stderr line naming the lane and the HTTP status; the next build called the model. The cause of that first miss was never proven, which is the argument for logging a fallback rather than trusting it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Related work
&lt;/h2&gt;

&lt;p&gt;Week 3 has a healthy sloth population. &lt;a href="https://dev.to/anishisbusy/sloth-errands-an-agent-that-does-my-web-chores-while-i-sleep-and-asks-before-it-pays-2n20"&gt;Sloth Errands&lt;/a&gt; runs web chores overnight and asks before it pays; &lt;a href="https://dev.to/rudraksh_karmakar_28/slothdesk-ai-do-less-busywork-get-more-of-your-day-back-36mp"&gt;SlothDesk&lt;/a&gt; goes after digital busywork; &lt;a href="https://dev.to/leafyte/invoicesnap-ai-turn-receipt-photos-into-organized-expenses-with-local-ai-1dli"&gt;InvoiceSnap&lt;/a&gt; turns receipt photos into expenses with local AI.&lt;/p&gt;

&lt;p&gt;All three automate the chore. Sloth Ledger asks the next question: did it actually pay? That is why the tray keeps the chores it refused, and why the total is allowed to be small.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ramantiw45/SlothLedger
&lt;span class="nb"&gt;cd &lt;/span&gt;SlothLedger
python app.py                       &lt;span class="c"&gt;# http://127.0.0.1:8787&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No install step. Without a &lt;code&gt;.env&lt;/code&gt; you get the rules floor and a SQLite file. To use the same lanes as the demo, copy &lt;code&gt;.env.example&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt; and set &lt;code&gt;MODEL_BASE_URL&lt;/code&gt;, &lt;code&gt;MODEL_API_KEY&lt;/code&gt;, &lt;code&gt;MODEL_NAME&lt;/code&gt;, and optionally &lt;code&gt;DATABASE_URL&lt;/code&gt; for Tiger Cloud.&lt;/p&gt;

&lt;p&gt;The hosted demo is this same code with &lt;code&gt;HOST=0.0.0.0&lt;/code&gt; and &lt;code&gt;DEMO_MODE=1&lt;/code&gt;; &lt;code&gt;render.yaml&lt;/code&gt; and &lt;code&gt;docs/deploy.md&lt;/code&gt; are in the repo. It sleeps on Render's free plan, so give the first request a few seconds.&lt;/p&gt;

&lt;p&gt;The checks I ran before writing this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python tests/test_ledger.py     -&amp;gt; 11 tests, OK
python tools/smoke_http.py      -&amp;gt; 15 checks passed (live server, real database)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The smoke test logs a chore, books an automation, refuses a second booking of the same chore, and rejects an empty description. The SQLite and Postgres lanes share one interface, so the same suite covers both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits and disclosure
&lt;/h2&gt;

&lt;p&gt;Built for the Hacktoberfest Open-Source AI Challenge, Week 3. MIT licensed. The demo corpus is a labelled synthetic freelancer week, not a real person's measurements; the eight chores live in &lt;code&gt;tools/seed_demo.py&lt;/code&gt; and you can replace them with your own.&lt;/p&gt;

&lt;p&gt;AI assistance: I used an AI coding agent for implementation and editing, and an open-weight model at runtime for judgement. Every number in the screenshots comes from the database, and the arithmetic that produced each one is printed on its card.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>tigerdata</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Udhaar Book: photograph the khata, get the udhaar back</title>
      <dc:creator>Raman Tiwari</dc:creator>
      <pubDate>Sun, 18 Oct 2026 19:54:43 +0000</pubDate>
      <link>https://dev.to/ramantiw45/udhaar-book-photograph-the-khata-get-the-udhaar-back-5bej</link>
      <guid>https://dev.to/ramantiw45/udhaar-book-photograph-the-khata-get-the-udhaar-back-5bej</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;p&gt;TL;DR — Photograph one page of a shop's handwritten credit notebook. An open-weight model reads it (a deterministic parser takes over when there is no key), the shopkeeper confirms every line, the balances follow, and each customer gets a polite WhatsApp reminder &lt;strong&gt;in their own language&lt;/strong&gt;. The AI does the reading; the shopkeeper does the deciding. Repo: &lt;a href="https://github.com/ramantiw45/UdhaarBook" rel="noopener noreferrer"&gt;https://github.com/ramantiw45/UdhaarBook&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Every kirana, medical and vegetable shop around me runs on a &lt;em&gt;khata&lt;/em&gt; — a paper notebook where credit gets written in pen, in a hurry, at the counter. &lt;em&gt;"Ramesh — cheeni 2kg + tel — 340."&lt;/em&gt; Nothing is typed anywhere. When money comes back, it's squeezed into the margin in a different pen, and the running balance lives in the shopkeeper's head until the day it doesn't.&lt;/p&gt;

&lt;p&gt;The money isn't lost because customers are dishonest. It's lost because &lt;strong&gt;asking is awkward&lt;/strong&gt;. Nobody wants to walk up to a neighbour and say &lt;em&gt;"aapke ₹140 baaki hain"&lt;/em&gt; — so the shopkeeper waits, the page gets older, and by month end he genuinely isn't sure who owes what any more.&lt;/p&gt;

&lt;p&gt;Udhaar Book does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reads one page of the notebook from a photo.&lt;/strong&gt; Local OCR if it's installed, an open-weight model otherwise, a plain deterministic parser if there is no key at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shows every line for confirmation.&lt;/strong&gt; Nothing is written to the ledger until a human says it's right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turns balances into reminders&lt;/strong&gt;, in English, हिंदी or मराठी — with the figure coming from the ledger, never from the model.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Who would pay for this
&lt;/h2&gt;

&lt;p&gt;This is the part I took seriously, because "Side Hustle" invites you to hand-wave it.&lt;/p&gt;

&lt;p&gt;A shop carrying ₹40,000 of udhaar with even 5% quiet leakage loses ₹2,000 every month to money it has already earned. Recovering a third of that is ~₹650 a month. The app costs ₹0 to run: free-tier model lanes, one SQLite file, one Android phone that is already on the counter. Charge ₹99/month and the tool has paid for itself the first time it recovers a single ₹140 line.&lt;/p&gt;

&lt;p&gt;The competitor is not another app — it's the pocket of khata apps that want the shopkeeper to &lt;strong&gt;type every entry&lt;/strong&gt;. That's the same reason the money gets lost in the first place: the entry never gets made. The only input that already exists in every one of those shops is the photo of a page.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2Fm3idtywr6x9qkjedwekh.jpg" alt="Pick up a page — photo or sample" width="800" height="1058"&gt;&lt;/td&gt;
&lt;td&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%2F7yfqyem9oasswnvi328h.jpg" alt="Confirm every line before it is written down" width="800" height="1340"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Read&lt;/strong&gt; — photo the page, or tick "use the sample page" and walk the whole flow with nothing installed.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Confirm&lt;/strong&gt; — every line the reader found, editable, with the raw text one tap away.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&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%2Fwh6ci2dgku1043k2zr57.jpg" alt="The book: every customer, given / paid / balance" width="800" height="1679"&gt;&lt;/td&gt;
&lt;td&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%2Fmko7bdes4lcn0pe2ynvg.jpg" alt="Who to ask: coded names, hue strips, reminder per customer" width="800" height="1368"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;The book&lt;/strong&gt; — the ledger: what was given, what came back, what is left, per page.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Who to ask&lt;/strong&gt; — the reminder screen. Names stay coded until you tap; the message is already written.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One page of the sample notebook: 12 transactions, 8 customers, ₹3,210 given, ₹1,000 received, &lt;strong&gt;₹2,210 still out&lt;/strong&gt;. Three of those eight have both taken credit and paid some back — that's the state a shop actually lives in, and the one a hand-written margin is worst at tracking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open matters for what I built
&lt;/h2&gt;

&lt;p&gt;The shop's record of &lt;strong&gt;who owes what, and to whom&lt;/strong&gt; is about as private as a small business gets. Names, amounts, neighbourhood relationships. That record should not require an account, a subscription, or an API that reads it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reading:&lt;/strong&gt; &lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt; (open weights) via Google AI Studio's free tier, &lt;code&gt;qwen/qwen3.8-27b&lt;/code&gt; on Groq as failover, &lt;code&gt;gemma3:4b&lt;/code&gt; via Ollama when the shop's own machine can carry it, and a deterministic parser with no key and no network as the floor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swapping is one env var.&lt;/strong&gt; Ollama and Groq speak plain chat-completions; Gemma 4 on AI Studio has a small native-route adapter (it is no longer served on the OpenAI-compatible endpoint). Pointing &lt;code&gt;OLLAMA_BASE_URL&lt;/code&gt; at a local model still moves the whole pipeline on-device with zero code changes. Nobody can re-price a shop's own bookkeeping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost structure:&lt;/strong&gt; at ₹0 per page, the ₹99/month price is a business. Per-token billing turns every read into a cost of goods, and at a ₹99 price point that math gets thin fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data shape:&lt;/strong&gt; one SQLite file. It can be copied, backed up, deleted, or never uploaded at all. There is no vendor in the middle of a shop's ledger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest asterisk: &lt;strong&gt;the model only writes the sentence, never the number.&lt;/strong&gt; The amount in a reminder is read from the ledger; the model gets to soften the wording and nothing else. That rule is in the code, not the prompt.&lt;/p&gt;

&lt;p&gt;One more thing, because "runs locally" is the easiest claim in a hackathon post to fake: the repo ships &lt;code&gt;docs/offline-proof.md&lt;/code&gt;, a five-step test (install the reader, switch the Wi-Fi off &lt;em&gt;physically&lt;/em&gt;, submit a page) and a table of &lt;strong&gt;what each possible outcome lets you claim&lt;/strong&gt; — including the claims you are never allowed to write, like "100% offline" after a hosted lane answered the request. On this machine the honest row is the third one, and that is the row I used.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  one photo of a khata page
        │
        ▼
  local OCR (optional, CPU only) ──► raw text
        │  no OCR installed: the photo goes straight to the lane
        ▼
  open-weight lane chain
    gemma3:4b       Ollama, on-device, offline
    gemma 4 26B A4B AI Studio, free tier (native generateContent)
    qwen 3.8        Groq, failover
    deterministic   no key, no network — text only
        │
        ▼
  transaction rows ──► shopkeeper confirms ──► one SQLite file
                                                   │
                                     ┌─────────────┴─────────────┐
                                     ▼                           ▼
                            balances per customer      WhatsApp reminder
                                                        en / हिंदी / मराठी
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; FastAPI + Jinja2 + SQLite + &lt;code&gt;httpx&lt;/code&gt;. No model SDKs, so a new lane is a dozen lines and a Python upgrade can't break the app on a Tuesday. Fonts are self-hosted; nothing on any screen makes a third-party request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The reminder is a link, not a send.&lt;/strong&gt; &lt;code&gt;wa.me/?text=...&lt;/code&gt; opens WhatsApp with the message written. Nothing leaves the server, no customer phone numbers are stored, and the shopkeeper still chooses who gets it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The decision that took the longest
&lt;/h2&gt;

&lt;p&gt;I am not going to pretend this was an OCR problem. Reading a messy page is the easy half.&lt;/p&gt;

&lt;p&gt;The hard half is that &lt;strong&gt;this screen lives on a counter where other people can see it.&lt;/strong&gt; The reminder screen is a list of the shop's debtors, and a shopkeeper will not use a tool that flashes a neighbour's name at whoever is standing there.&lt;/p&gt;

&lt;p&gt;The screen I designed for is 9pm: shutter half down, one tube light, the shopkeeper alone with the notebook and a phone. That is the one moment the names are safe to show — and the moment the screen has to be worth opening.&lt;/p&gt;

&lt;p&gt;So the names are coded: each customer shows as an entry number (their place in the order to ask), a hue strip and an amount, and one tap decodes the row into the name, the plain-language history ("Page 1 · 1 line · last 15/10 · ₹560 given") and the message. The first row is decoded on load, so the screen teaches itself in one look. Decoding is a one-tap act on a 68px row, and Reveal opens all eight when the counter is finally empty.&lt;/p&gt;

&lt;p&gt;The screen is built as a catalogue sleeve, not a dashboard: one white hairline per customer, the amount as the only headline, identity as a hue strip and a number until you touch it. That is written down as a design system rather than left as taste.&lt;/p&gt;

&lt;p&gt;Two more things I made the design carry its weight on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing is signalled by colour alone.&lt;/strong&gt; A filled strip means credit was given; a hollow strip with a line through it means money came back. Strip the colour off and the meaning is still there — which also matters because a shopkeeper will screenshot this and print it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The reminder screen must survive a greyscale print&lt;/strong&gt;, so I checked it in greyscale, not just in the browser. It does.&lt;/li&gt;
&lt;/ul&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%2Fz4ferfc74iywa4bekay3.jpg" 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%2Fz4ferfc74iywa4bekay3.jpg" alt="The reminder screen with colour stripped out" width="800" height="1368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I got wrong
&lt;/h2&gt;

&lt;p&gt;An honest build log, because the flaws are more useful to read than the features.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;My machine cannot install the OCR layer.&lt;/strong&gt; Python 3.14 has no wheels for the usual on-device runtimes yet, so on &lt;em&gt;this&lt;/em&gt; box the photo path is: no local reader → model lane. With an AI Studio key set, an uploaded test khata page went through the Gemma lane and came back as rows in the confirm table; with no key, the screen says exactly that and hands you the sample page. The local OCR path is written, documented in &lt;code&gt;requirements-ocr.txt&lt;/code&gt; and untested here. That is a gap, not a feature, and I've labelled it one in the README.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No shopkeeper has used this yet.&lt;/strong&gt; No field test, no first user, no one has paid me ₹99. The sample notebook is a fixture I wrote; the shop name in the screenshots is a fixture shop. I would rather say that plainly than dress up a demo as a deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handwriting is genuinely hard, and the honest move is to say so.&lt;/strong&gt; &lt;a href="https://dev.to/kaniel_outis/how-well-can-ocr-read-doctor-handwriting-in-2026-54hn"&gt;A benchmark of four open-source OCR engines on 5,578 handwritten prescriptions&lt;/a&gt; found the best of them get &lt;em&gt;one word in three&lt;/em&gt; exactly right. I read that before building and it changed the design: the app does not try to be right, it tries to be &lt;strong&gt;checkable&lt;/strong&gt;. Every figure is one tap from the raw text the reader saw, and the ledger never accepts a row nobody confirmed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Devanagari handwriting is the weakest case&lt;/strong&gt; for every reader I looked at. The reminder templates are in Marathi and Hindi because the &lt;em&gt;message&lt;/em&gt; is the part that must feel native, but I am not going to claim the reading is equally strong in all three scripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Also worth reading, if you are building in this space
&lt;/h2&gt;

&lt;p&gt;Three posts that shaped this build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/prosperiko/i-built-a-receipt-bookkeeper-that-runs-ai-fully-offline-the-business-math-only-works-because-the-58c5"&gt;I Built a Receipt Bookkeeper That Runs AI Fully Offline&lt;/a&gt;&lt;/strong&gt; — the closest project to this one this week, and the cleanest statement of why the economics only close with open weights. Different user: their freelancer has a wallet of printed receipts; mine has a paper notebook and no laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/kaniel_outis/how-well-can-ocr-read-doctor-handwriting-in-2026-54hn"&gt;How Well Can OCR Read Doctor Handwriting in 2026?&lt;/a&gt;&lt;/strong&gt; — the 1-in-3-words number above, measured rather than assumed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/sunny7899/building-for-the-next-billion-users-engineering-for-emerging-markets-40cp"&gt;Building for the Next Billion Users: Engineering for Emerging Markets&lt;/a&gt;&lt;/strong&gt; — why every screen here is phone-first and why a dead end with no key would have been the real failure.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 4 is the default hosted reader (&lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt;, the fast MoE variant, via AI Studio's free tier on the native &lt;code&gt;generateContent&lt;/code&gt; route — the OpenAI-compatible one stopped serving Gemma after 3 was retired in 2026), and Gemma 3 4B (&lt;code&gt;gemma3:4b&lt;/code&gt; via Ollama) is the on-device lane. The two Gemma lanes sit at the two ends of the price/privacy trade-off — one free and remote, one free and offline — and both are selected by environment variable, not by code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ramantiw45/UdhaarBook.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;UdhaarBook
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; .venv&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\a&lt;/span&gt;ctivate     &lt;span class="c"&gt;# or source .venv/bin/activate&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
uvicorn app:app &lt;span class="nt"&gt;--reload&lt;/span&gt;                            &lt;span class="c"&gt;# http://127.0.0.1:8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No key needed to judge it.&lt;/strong&gt; Tick &lt;em&gt;use the sample page&lt;/em&gt;, press &lt;em&gt;Read this page&lt;/em&gt;, and the whole flow runs on the built-in parser: 12 transactions, 8 customers, ₹2,210 outstanding, and a Marathi reminder at the end. Add one free AI Studio key in &lt;code&gt;.env&lt;/code&gt; and the same flow goes through Gemma; add &lt;code&gt;OLLAMA_BASE_URL&lt;/code&gt; and it never leaves the machine. &lt;code&gt;pip install -r requirements-ocr.txt&lt;/code&gt; adds the on-device photo reader where wheels exist.&lt;/p&gt;

&lt;p&gt;Tests: &lt;code&gt;python tests/test_pipeline.py&lt;/code&gt; (5 checks — parsing, money maths, reminders, the no-key path, and the native Gemma request) and &lt;code&gt;python tools/smoke_http.py&lt;/code&gt; (20 checks against the running app, including "an uploaded photo fails honestly, with 200, not a blank page"). Neither needs a test framework installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Built with an AI coding agent in an opencode session over one weekend. The product truth was the human's — the shopkeeper, the counter scene, the rule that names stay coded — and the visual direction was chosen from options the agent dealt and built; the agent wrote the app, the test suite, the design system and this draft, and the human approved each ship decision. FastAPI, Jinja2, SQLite, &lt;code&gt;httpx&lt;/code&gt;, the self-hosted Rajdhani, Mukta and Spline Sans Mono (all SIL OFL), and the open weights of Gemma 4, Gemma 3 and Qwen 3.8. The sample notebook is synthetic and labelled as such in the repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI disclosure: some_ai.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
      <category>gemma</category>
      <category>hacktoberfest</category>
    </item>
    <item>
      <title>I planned a walk in 60 seconds, then walked for two hours (open-weight AI, no GPU)</title>
      <dc:creator>Raman Tiwari</dc:creator>
      <pubDate>Thu, 08 Oct 2026 16:11:41 +0000</pubDate>
      <link>https://dev.to/ramantiw45/i-planned-a-walk-in-60-seconds-then-walked-for-two-hours-open-weight-ai-no-gpu-248o</link>
      <guid>https://dev.to/ramantiw45/i-planned-a-walk-in-60-seconds-then-walked-for-two-hours-open-weight-ai-no-gpu-248o</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;&lt;strong&gt;TL;DR&lt;/strong&gt; — One input gives you one walk: the best time window, a loop idea, a nature cue and a packing line, plus a live map pin. Under a minute on screen, then you go outside. Open-weight &lt;code&gt;openai/gpt-oss-20b&lt;/code&gt; via Groq's free tier, no GPU, $0 to run.&lt;/p&gt;

&lt;p&gt;Live: &lt;a href="https://foliage-walk-planner.onrender.com" rel="noopener noreferrer"&gt;https://foliage-walk-planner.onrender.com&lt;/a&gt;&lt;br&gt;
Repo: &lt;a href="https://github.com/ramantiw45/Hacktoberfest/tree/main/week-01-touch-grass" rel="noopener noreferrer"&gt;https://github.com/ramantiw45/Hacktoberfest/tree/main/week-01-touch-grass&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A walk planner for people who keep meaning to get outside and don't. You enter a place and a time budget; you get back the best two-hour window, a 2–3 km loop idea, one nature cue worth looking for, a packing line, and a map pin. Then you close the tab and walk.&lt;/p&gt;

&lt;p&gt;I built it because &lt;em&gt;planning&lt;/em&gt; the walk is exactly where plans die. Weather apps want a full day of your attention, trail sites want a login and ten minutes of map-fiddling, and foliage trackers are US-only. I wanted the whole decision to fit inside one minute, because at one minute it competes with nothing.&lt;/p&gt;

&lt;p&gt;On my own test walk I spent about &lt;strong&gt;one minute on the screen and one to two hours outside&lt;/strong&gt; — roughly sixty to one. The screen being the shortest part of the experience is a design target here, not a slogan.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Live: &lt;a href="https://foliage-walk-planner.onrender.com" rel="noopener noreferrer"&gt;https://foliage-walk-planner.onrender.com&lt;/a&gt; — try &lt;code&gt;Virar&lt;/code&gt; (19.45510, 72.82513) or &lt;code&gt;Prospect Park&lt;/code&gt; (40.660, -73.969). Cold start takes ~30–60s on Render's free tier.&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%2Fijapl4oenbfhzdxwiy46.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%2Fijapl4oenbfhzdxwiy46.png" alt="The plan, generated live on the deployed app" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Verbatim output for &lt;code&gt;Virar&lt;/code&gt; (this screenshot predates a prompt fix described below, which is why line 3 is wrong for India):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;10:00-12:00&lt;/li&gt;
&lt;li&gt;2-3 km loop around Virar Lake promenade (no car needed)&lt;/li&gt;
&lt;li&gt;First golden leaves on the banyan trees&lt;/li&gt;
&lt;li&gt;Water, a light jacket, and a phone charger&lt;/li&gt;
&lt;li&gt;It's a real nature escape that recharges your brain more than scrolling.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Field test — I took it to Virar
&lt;/h2&gt;

&lt;p&gt;One tap, then I went outside for one to two hours. From the walk:&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%2Fnayrrmjsjrct4eayyvf0.jpeg" 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%2Fnayrrmjsjrct4eayyvf0.jpeg" alt="Hillside above Virar town, estuary beyond" width="800" height="1067"&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%2Fj65hv6gfw5euyhrs87xe.jpeg" 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%2Fj65hv6gfw5euyhrs87xe.jpeg" alt="The city below the viewpoint" width="800" height="1067"&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%2Fbqa6rr8ve43wavlihsfe.jpeg" 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%2Fbqa6rr8ve43wavlihsfe.jpeg" alt="Virar Lake in the morning haze" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked.&lt;/strong&gt; The plan named a destination I would not have picked myself — the lake promenade — and the "no car needed" line meant I walked instead of driving somewhere and sitting in a car park. The packing line (water, light jacket) was correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What failed, honestly.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Weather never rendered on the deployed app.&lt;/strong&gt; Render's free tier shares one outbound IP across many instances, and Open-Meteo rate-limits that IP. So the forecast silently fell back to generic advice. My fix (a 10-minute cache plus one polite retry, with stale-if-error) landed &lt;em&gt;after&lt;/em&gt; this test, which is why the failure is still visible in the screenshot above. I'm leaving it visible rather than retaking a flattering one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"First golden leaves on the banyan trees" is a New England line, not a Maharashtra one.&lt;/strong&gt; The prompt was written for autumn foliage. In mid-October Virar the banyans are still deep green, so I ignored the cue completely. Testing a fall-foliage tool in a city with no fall foliage is my mistake, and it's the clearest lesson of the build. The prompt now asks for a cue that is true for the given location in October.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The suggested window was wrong by hours.&lt;/strong&gt; It said 10:00–12:00; I went early morning, because the midday haze in my own photos is the honest reason to go earlier. The app couldn't tell me that — the weather call was the part that failed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Would I use it again next weekend?&lt;/strong&gt; Yes — for the destination, not for the foliage talk. "Here is a walk you can start in 60 seconds" is the value. If the weather call worked, it would have told me early morning beats midday, which is exactly what I did.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ramantiw45/Hacktoberfest/tree/main/week-01-touch-grass" rel="noopener noreferrer"&gt;https://github.com/ramantiw45/Hacktoberfest/tree/main/week-01-touch-grass&lt;/a&gt; — public, MIT LICENSE, no API keys required for the map or weather.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
copy .env.example .env   &lt;span class="c"&gt;# add GROQ_API_KEY from console.groq.com (free, no card)&lt;/span&gt;
uvicorn app:app &lt;span class="nt"&gt;--reload&lt;/span&gt; &lt;span class="nt"&gt;--app-dir&lt;/span&gt; week-01-touch-grass
&lt;span class="c"&gt;# open http://127.0.0.1:8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With no key at all the app still serves a mock plan plus readable lane errors, so you can always click through.&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; &lt;code&gt;openai/gpt-oss-20b&lt;/code&gt; — OpenAI's open-weight model, Apache-2.0, &lt;a href="https://huggingface.co/openai/gpt-oss-20b" rel="noopener noreferrer"&gt;on Hugging Face&lt;/a&gt; — served through Groq's free tier on an OpenAI-compatible endpoint. Swapping models is one env var: &lt;code&gt;GROQ_MODEL&lt;/code&gt;, or &lt;code&gt;GEMINI_MODEL&lt;/code&gt; for Gemma 3, or &lt;code&gt;MODEL_ID&lt;/code&gt; for any Hugging Face model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; FastAPI + Leaflet/OpenStreetMap (no Google Maps key) + Open-Meteo (no key), deployed on Render's free tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No GPU required.&lt;/strong&gt; The same weights run on a laptop later via &lt;code&gt;ollama run gpt-oss:20b&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  User--&amp;gt;Web[Leaflet + FastAPI]
  Web--&amp;gt;Weather[Open-Meteo, cached]
  Web--&amp;gt;Lane[Free lane chain: Groq - AI Studio - HF - mock]
  Lane--&amp;gt;Plan[2-hour walk plan]
  Plan--&amp;gt;Outside[Go outside]&lt;/code&gt;&lt;/pre&gt;


&lt;h3&gt;
  
  
  The honest build log: two providers died mid-week
&lt;/h3&gt;

&lt;p&gt;This is the part I did not expect.&lt;/p&gt;

&lt;p&gt;I started on &lt;strong&gt;Qwen 2.5 via Hugging Face&lt;/strong&gt;. It worked, then stopped:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;HTTP 402, "Payment Required."&lt;/strong&gt; Hugging Face had moved Inference Providers behind paid credits. I checked their free &lt;code&gt;hf-inference&lt;/code&gt; lane programmatically and found essentially no chat models left on it. Dead end.&lt;/li&gt;
&lt;li&gt;So I moved to &lt;strong&gt;Groq&lt;/strong&gt; — and got a &lt;strong&gt;404&lt;/strong&gt;. I probed the endpoint with a deliberately fake API key and got a &lt;strong&gt;401&lt;/strong&gt; back, which proved the path was correct and the &lt;em&gt;model ID&lt;/em&gt; was the problem, not my code.&lt;/li&gt;
&lt;li&gt;Reading Groq's live model docs showed both &lt;strong&gt;Llama 3.x models had been moved to Enterprise&lt;/strong&gt;. Their free tier now offers &lt;code&gt;openai/gpt-oss-20b&lt;/code&gt;, which is open-weight under Apache-2.0 and free — so the swap made the open-source story &lt;em&gt;stronger&lt;/em&gt;, not weaker.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two providers changed their terms in the same week, and my app did not break, because it talks OpenAI-compatible chat with model IDs in environment variables. Every swap was one line. That portability is the whole argument for open weights, and I only appreciate it because it happened to me on a deadline.&lt;/p&gt;

&lt;p&gt;There was one more bug worth naming, because it is the kind that ships silently. With weather unavailable, my prompt contained literal question marks:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Weather tomorrow: high ?C low ?C rain ?%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The model read that as a request and replied: &lt;em&gt;"Could you share tomorrow's forecast?"&lt;/em&gt; The app looked broken — it was asking the user to do its job. Three fixes: tell the model in the prompt to give a complete plan and never ask a question, treat a question-shaped reply as a &lt;em&gt;failure&lt;/em&gt; so it falls through to the next provider, and make the weather cache stale-if-error so one good reading survives later rate limits.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;$0 to run.&lt;/strong&gt; No per-token billing, no card, free tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No vendor lock, demonstrated.&lt;/strong&gt; I changed models three times in one week without touching application logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable and hackable.&lt;/strong&gt; The model ID is an env var and the prompt is mine to edit. With a closed API I cannot fine-tune, cannot self-host, and cannot reason about why the output changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data stays in the open ecosystem.&lt;/strong&gt; Coordinates go to a free inference lane, not to a vendor I can't audit or leave.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this had been built on a single closed API, I would have been stranded the moment that provider changed its pricing. Open weights meant there was always another lane.&lt;/p&gt;
&lt;h2&gt;
  
  
  Theme mapping
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Touch Grass&lt;/strong&gt;, literally: about one minute on a screen, one to two hours of hill, lake and estuary. &lt;strong&gt;Getting people into the world&lt;/strong&gt; — the app's only job is to end its own usefulness; every output is an instruction to close the tab. &lt;strong&gt;Screen is the shortest part&lt;/strong&gt;: sixty to one, measured on my own walk, not asserted.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;The full build, including both dead provider integrations and the empty-completion bug, is saved as a session — so you can read the process, not just the finished app.&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="OpenCode"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Building Foliage Walk Planner with open-weight AI (no local GPU)&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;New to open source, want to submit to this week's Hacktoberfest DEV challenge. Help me get started.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Read the project skills and the live challenge page. Key finding: 2026 Hacktoberfest does NOT count PRs. You must build a brand-new project with open-source AI at its core and publish a DEV post in-window. Week 1 theme is 'Touch Grass' - get people off the screen and outside. Window Oct 05-11, 11:59pm PDT. Judging weights Writing Quality most heavily, then relevance, creativity, technical execution, plus 16 partner categories ($200 each) as smaller pools.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Suggest something related to finance or market or money management.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Proposed three finance-adjacent ideas that still satisfy Touch Grass: Farmers Market Saver, Walk-to-Save Challenge, Yard-Sale Flipper. Recommended Farmers Market Saver because it hits the theme, money management, and two partner categories.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Start with Foliage Walk Planner. I cannot run a local LLM, use Python.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Scaffolded FastAPI + Leaflet/OSM + Open-Meteo with a hosted open-weight model so no GPU is needed. Added requirements.txt, render.yaml, .env.example, LICENSE, draft.md, field-test.md.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Can you push to GitHub?&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Initialized git, added .gitignore protecting .env, verified no secrets staged via grep for hf_/gho_/sk- patterns, committed, created public repo ramantiw45/Hacktoberfest. SSH push failed with publickey denied, so switched the remote to HTTPS and pushed successfully.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;How can I deploy? Also create a simple frontend.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Rebuilt the frontend: autumn paper palette, Bitter display typeface, authored SVG leaf mark instead of emoji, side-by-side form and map collapsing on mobile, and real loading, error, and empty states plus keyboard focus rings and reduced-motion support. Ran the mechanical design detector, which flagged Fraunces as an overused AI-convergent font, so swapped to Bitter and re-ran clean. Gave step-by-step Render instructions including the Root Directory setting people miss.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I reached this point - model call failed with 402 Payment Required.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Hugging Face had moved Inference Providers behind paid credits. Probed their free hf-inference lane programmatically and found no usable chat models left. Built a free-lane chain instead: Groq, then Google AI Studio, then Hugging Face, then a mock fallback.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Groq returns 404 Not Found.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Probed with a deliberately fake key: got 401, which proved the path was correct and the model ID was wrong. Improved error reporting to surface the provider's actual response body instead of a bare status code.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now model_not_found for llama-3.3-70b-versatile.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Fetched Groq's live model docs and found both Llama 3.x models had moved to Enterprise. Switched the default to openai/gpt-oss-20b, which is open-weight under Apache-2.0 and free, making the open-source story stronger.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now the plan is empty and weather unavailable.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Two causes. GPT-OSS is a reasoning model, so it spent the entire 300-token budget thinking and returned nothing; raised to 1024 and made an empty completion an explicit failure. Weather 429 came from Render's free tier sharing one egress IP across many instances while Open-Meteo rate-limits that IP, so added a 10-minute cache and one polite retry.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;It works now but the live API asks me to share tomorrow's forecast.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Caught a genuine production bug before publishing: with no weather the prompt contained literal question marks, so the model replied by asking the user for a forecast instead of planning. Rewrote the prompt to instruct a complete plan with no questions, made the nature cue location and October specific, added a guard treating a question-shaped reply as a failure so it falls through to the next lane, and made the weather cache stale-if-error.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;How do I field test? I took screenshots.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Walked through the field test, then wrote the results: about one minute on screen against one to two hours outside at Virar, roughly sixty to one. Captured three honest failures for the write-up including the Render shared-IP 429 that killed the weather call, and a 'golden leaves on the banyans' cue that is wrong for monsoon-green Maharashtra.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Help me connect the DevRelay MCP server.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Found DevRelay already installed at ~/.devrelay but never registered with OpenCode. Added it as a local stdio MCP server under mcp.servers in the global config, preserving existing provider and model settings, and confirmed authentication as ramantiw45.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        24 of 24 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Best Use of Render&lt;/strong&gt; — the FastAPI app is hosted on Render's free tier (Root Directory &lt;code&gt;week-01-touch-grass&lt;/code&gt;), with the live URL above. Every part of it — API, static frontend, and the map — is served from that one free service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/pratik_12b3f8bf3b50e48bae/stop-overpaying-for-apis-when-to-swap-your-cloud-llm-for-a-local-slm-2n67"&gt;Stop Overpaying for APIs: When to Swap Your Cloud LLM for a Local SLM&lt;/a&gt; — the cost argument for open weights, which I felt rather than read about.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/lynkr/how-i-built-a-free-openai-compatible-api-on-top-of-opencode-67a"&gt;How I Built a Free OpenAI-Compatible API on Top of OpenCode&lt;/a&gt; — the protocol-compatibility trick my provider chain depends on.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/elsie-rainee/comparing-open-source-llm-gateways-in-2026-to-run-enterprise-ai-at-scale-4h4p"&gt;Comparing Open-Source LLM Gateways in 2026&lt;/a&gt; — useful context on why gateways exist at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Open-Meteo, OpenStreetMap contributors, Leaflet, Groq, and the GPT-OSS open weights. Frontend and backend built with an AI coding agent.&lt;/p&gt;

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