<?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: Sankalp Kotewar</title>
    <description>The latest articles on DEV Community by Sankalp Kotewar (@sankalpkotewar).</description>
    <link>https://dev.to/sankalpkotewar</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%2F374692%2F3a131677-199f-48be-86bd-be30bb2e9896.jpg</url>
      <title>DEV Community: Sankalp Kotewar</title>
      <link>https://dev.to/sankalpkotewar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sankalpkotewar"/>
    <language>en</language>
    <item>
      <title>The AI didn't invent my client update. It promoted it.</title>
      <dc:creator>Sankalp Kotewar</dc:creator>
      <pubDate>Wed, 21 Oct 2026 05:42:49 +0000</pubDate>
      <link>https://dev.to/sankalpkotewar/the-ai-didnt-invent-my-client-update-it-promoted-it-eik</link>
      <guid>https://dev.to/sankalpkotewar/the-ai-didnt-invent-my-client-update-it-promoted-it-eik</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Every Friday I owe a client a paragraph about what happened that week. The information is all sitting in &lt;code&gt;git log&lt;/code&gt;. What I do not have, at 6pm on a Friday, is the will to reconstruct it from forty commit messages and a diff I have already forgotten writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Receipts&lt;/strong&gt; reads a week of commits with a local model and writes the update. Then it does the part I actually care about: it checks every sentence against the commit it cites, and tells me which sentences the commit does not support.&lt;/p&gt;

&lt;p&gt;The thing I found is that the model almost never invents work. It &lt;strong&gt;promotes&lt;/strong&gt; it. &lt;code&gt;Pin requests&amp;lt;3&lt;/code&gt; becomes "refactored to pin requests to version 2.31.0" — a commit that &lt;em&gt;removes&lt;/em&gt; 2.31.0. &lt;code&gt;Run black&lt;/code&gt; becomes "implemented the export function" — black reformatted it, a different commit wrote it.&lt;/p&gt;

&lt;p&gt;Every one of those sentences is locally defensible. Collectively they are a lie, and nobody checks a status report. That is the exact seam this project sits in.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://kotewar.github.io/receipts/" rel="noopener noreferrer"&gt;kotewar.github.io/receipts&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The page opens as the report a client would receive: six confident bullets, no citations, nothing to click. Flip the switch and every sentence grows the commit it came from — the sha, the message, the files, the diff, and a verdict.&lt;/p&gt;

&lt;p&gt;Three of the six receipts do not support the sentence above them. The report reads perfectly either way. That is the whole project in one interaction.&lt;/p&gt;

&lt;p&gt;The demo is a saved run baked into a static page, because the model is 3.4GB and GitHub Pages does not have a CPU. What is replayed is a real run, unedited: &lt;code&gt;data/demo.json&lt;/code&gt; is the exact model output, and the verdicts are the output of the real checker, not a mock-up. The live model runs from the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PYTHONPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; .venv/bin/python scripts/report.py ~/code/some-client &lt;span class="nt"&gt;--limit&lt;/span&gt; 40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pointed at the repo for my previous project — real code, real commits, not a fixture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  parchi: 9 commits, 2026-10-17 -&amp;gt; 2026-10-17

  ok   The counselling text has been translated into a native Hindi voice using
       ElevenLabs, resulting in a 39.0-second audio file with 610 KB and a
       44.1 kHz sample rate
  FAIL The LICENSE file has been added, clarifying that this project is licensed
       under the MIT License
       [715e025] does not support — Install TabPFN for real, and pin NumPy under 2
       needed [36dd958] Add an MIT license, and say plainly what this is not
  FAIL The `README.md` file has been updated to include a self-check script
       (`scripts/selfcheck.py`) that verifies the functionality of the system
       [ced916a] does not support — Stop the counselling saying the strength twice

  2 of 6 receipts do not support their sentence.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second failure is my favourite in the whole project. &lt;code&gt;ced916a&lt;/code&gt; is the commit that &lt;em&gt;created&lt;/em&gt; &lt;code&gt;scripts/selfcheck.py&lt;/code&gt;. The README line documenting it came in the commit just before, &lt;code&gt;744075a&lt;/code&gt;. The model found the right file and the wrong commit — which is precisely the error a human skims past, because the file exists and the sentence is true.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/kotewar/receipts" rel="noopener noreferrer"&gt;github.com/kotewar/receipts&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app/trace.py&lt;/code&gt; — the three deterministic rules. No model, no API call, no network.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/receipts.py&lt;/code&gt; — finds the commit that &lt;em&gt;would&lt;/em&gt; have supported a bad sentence, when one exists&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts/eval.py&lt;/code&gt; — the harness behind every number below&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts/report.py&lt;/code&gt; — the CLI above&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts/make_fixture_repo.py&lt;/code&gt; — builds the 12-commit demo repo, including its traps&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data/eval_base.json&lt;/code&gt;, &lt;code&gt;data/eval_primed.json&lt;/code&gt; — both runs, committed so the claims are checkable&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Start with the failure you can actually catch
&lt;/h3&gt;

&lt;p&gt;I did not want to build a plagiarism detector or a "hallucination score". I wanted to catch one specific thing: &lt;strong&gt;a sentence whose receipt does not support it.&lt;/strong&gt; So the gate has three rules, all boring, all deterministic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grounding.&lt;/strong&gt; Numbers and identifiers in the sentence must appear in the cited commit. Including the distinction that broke a naive version: a version that appears only in &lt;em&gt;deleted&lt;/em&gt; lines was removed, not introduced. Without that, "pinned requests to 2.31.0" passes against a commit that changed &lt;code&gt;requests==2.31.0&lt;/code&gt; to &lt;code&gt;requests&amp;lt;3&lt;/code&gt;, because &lt;code&gt;2.31.0&lt;/code&gt; is right there in the diff. A claim can be exactly backwards and satisfy a string search.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inflation.&lt;/strong&gt; The sentence's leading verb is ranked against the commit message's leading verb. &lt;code&gt;refactored&lt;/code&gt; on a &lt;code&gt;Pin&lt;/code&gt; commit is two ranks apart. The claim is not that one verb is worse than another; it is that the sentence and its receipt are describing different sizes of work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope.&lt;/strong&gt; "the system", "the platform", "the codebase" on a commit that touched one file.&lt;/p&gt;

&lt;p&gt;That is the whole gate. It is a &lt;strong&gt;triage&lt;/strong&gt; tool, not a judge. It does not decide whether a sentence is good. It decides which sentences I have to read.&lt;/p&gt;

&lt;h3&gt;
  
  
  The traps are the test
&lt;/h3&gt;

&lt;p&gt;The eval repo is twelve synthetic commits from one week of a small invoicing client. Two of them touch hundreds of lines and mean nothing (&lt;code&gt;Run black&lt;/code&gt;, &lt;code&gt;Rename qty to quantity&lt;/code&gt;). One touches six lines and matters (&lt;code&gt;Fix null check in parse_invoice&lt;/code&gt;). If a checker ranks by churn, it will point at the wrong commit every time — which is exactly what the model does.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it caught
&lt;/h3&gt;

&lt;p&gt;Five real outputs from the saved runs. Left is what the model cited; right is what it said.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cited&lt;/th&gt;
&lt;th&gt;the update said&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Pin requests&amp;lt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Refactored the code to pin &lt;code&gt;requests&lt;/code&gt; to version &lt;strong&gt;2.31.0&lt;/strong&gt;"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Run black&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Implemented an &lt;code&gt;export_rows&lt;/code&gt; function to generate a CSV file"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Add retry to fetch_rate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Fixed a null check in &lt;code&gt;parse_invoice&lt;/code&gt;"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Document the retry behaviour&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Implemented retry logic for fetching rates"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Pin requests&amp;lt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Added a new test case (&lt;code&gt;test_empty.py&lt;/code&gt;)"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every one is a real commit, so a link-checker passes all five. Four of the five are misattribution, not fabrication: the work happened, the receipt is wrong. The fifth — a README commit claimed as an implementation — is the one that stings, because documentation commits are the easiest thing in a repo to claim as engineering.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it measures
&lt;/h3&gt;

&lt;p&gt;Two prompt variants, three runs each, six bullets per run: 36 bullets.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;13 cited a commit that does not support them. The gate flagged all 13.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4 more were wrong in a way the gate cannot see.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second number is the interesting one, and it only came from reading the sentences the gate &lt;em&gt;approved&lt;/em&gt;. A commit that changed a sleep constant from &lt;code&gt;0.2&lt;/code&gt; to &lt;code&gt;0.25&lt;/code&gt; produced this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Improved error handling within &lt;code&gt;rates.fetch_rate&lt;/code&gt; by introducing a retry mechanism with exponential backoff"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The retry mechanism was introduced two commits earlier. This commit tuned its delay. The verbs agree, there is no number to ground, and no filename is named, so all three rules pass it. &lt;strong&gt;A gate that only reports its own flags cannot find its own misses.&lt;/strong&gt; The honest total is 17 of 36, with 13 caught.&lt;/p&gt;

&lt;p&gt;The same class also produced the best sentence in the dataset, from the primed variant:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Improved error handling by adjusting the sleep time within &lt;code&gt;rates.fetch_rate&lt;/code&gt; to &lt;code&gt;0.25 * (attempt + 1)&lt;/code&gt;"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same commit, same model, and it named the constant exactly. Priming the prompt ("be literal, do not upgrade a verb") did not reliably reduce the flag rate — 7/18 versus 6/18 is noise — but it did move the surviving sentences toward the literal. That is a real but modest win, and I am not going to dress it up as more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three bugs the output found, not the tests
&lt;/h3&gt;

&lt;p&gt;Each of these was caught by reading a flagged line and thinking "that sentence is fine".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A filename read as a verb.&lt;/strong&gt; "Ran black to format the code in &lt;code&gt;export.py&lt;/code&gt;, &lt;code&gt;invoice.py&lt;/code&gt;, and &lt;code&gt;rates.py&lt;/code&gt;" was flagged for claiming &lt;code&gt;export&lt;/code&gt; at strength 3. It was the filename. Correct sentence, wrong flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subordinate clauses inflating the verb.&lt;/strong&gt; "Bumped the version to 1.2.1, reflecting the updated retry logic" was flagged because &lt;code&gt;retry&lt;/code&gt; ranks higher than &lt;code&gt;bump&lt;/code&gt;. The claim is the main clause; the tail was context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commit messages scored by their most impressive word.&lt;/strong&gt; This one was hiding a false negative. &lt;em&gt;"Document the retry behaviour"&lt;/em&gt; scored as strong as &lt;em&gt;"Add retry to fetch_rate"&lt;/em&gt;, because &lt;code&gt;retry&lt;/code&gt; is also a verb, so "implemented retry logic" cited to a docs commit passed clean. Commit messages lead with their verb. Read the first one, on both sides.&lt;/p&gt;

&lt;p&gt;All three are fixed, and all three are now cases in the checker's own test set. None of them came from a test I wrote in advance.&lt;/p&gt;

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

&lt;p&gt;The theme this week is laziness, so here is what actually got removed from my Friday.&lt;/p&gt;

&lt;p&gt;I no longer open the repo and reconstruct the week from memory. I run one command, wait about two minutes on an eight-core laptop with no GPU, and get six bullets. That is the obvious saving and it is the smaller half.&lt;/p&gt;

&lt;p&gt;The bigger half is that &lt;strong&gt;I do not proofread the whole report.&lt;/strong&gt; I read the flagged sentences and the receipts behind them. On this run that is three sentences instead of six, each with the diff already attached, and one of them already tells me which commit I meant. The work I stopped doing is the part I was worst at: checking a plausible sentence against a commit I half-remember.&lt;/p&gt;

&lt;p&gt;There is a second-order laziness that I did not expect and would not give up. Because the model runs locally for nothing, I could run the eval again and again, read the raw output, and rewrite the prompt without thinking about a bill. Every bug in the previous section was found that way. That is not a workflow you get when each run is a metered API call.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The diffs are the client's code.&lt;/strong&gt; This tool reads full diffs, not just commit messages — that is what makes the receipts meaningful. Sending a client's private repository to a hosted API every Friday is a data-processing decision I would have to explain to the client. Running the model on the laptop the repo is already on makes it a non-decision. The data does not move.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The checker is model-independent, which is only useful if the model is swappable.&lt;/strong&gt; &lt;code&gt;app/trace.py&lt;/code&gt; checks the sentence, not the model. That means the sentence generator is a replaceable part: &lt;code&gt;gemma3:4b&lt;/code&gt; today, something smaller or fine-tuned tomorrow, and the guarantees stay the same. That is a much better property than "we use a good model", and it is only available to you if you own both ends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume makes per-call pricing silly.&lt;/strong&gt; A freelancer with five clients writes five of these a week, forever. The marginal cost here is electricity on a laptop that is already on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And the reason I could do any of this:&lt;/strong&gt; running locally meant I could read the raw output. Every finding in this post — the backwards version claim, the docs commit claimed as implementation, the three false flags in my own gate — came from looking at what the model actually returned, one line at a time, on a machine I control. An aggregate score would have hidden all of it.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; The writing layer is Google's &lt;code&gt;gemma3:4b&lt;/code&gt;, served locally through Ollama on the CPU of an eight-core laptop with no GPU, and it is the reason this project exists in this shape. The model is good at the thing I am bad at — turning a scattered week into sentences — and unreliable in one specific, measurable way: it reaches for the most flattering verb that is still arguably true. Every rule in &lt;code&gt;app/trace.py&lt;/code&gt; is a response to a failure I watched Gemma produce on real commits. Because it runs locally and costs nothing, I could reproduce those failures 36 times, read each one, and tell the difference between the thirteen the gate caught and the four it structurally cannot.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Every commit, sentence and evaluation in this post comes from synthetic fixture repos and my own projects. No client code was used, and no repository left the machine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I poisoned 20 drug names to find out whether a local model belongs in a pharmacy</title>
      <dc:creator>Sankalp Kotewar</dc:creator>
      <pubDate>Sat, 17 Oct 2026 04:22:57 +0000</pubDate>
      <link>https://dev.to/sankalpkotewar/i-poisoned-20-drug-names-to-find-out-whether-a-local-model-belongs-in-a-pharmacy-5ekh</link>
      <guid>https://dev.to/sankalpkotewar/i-poisoned-20-drug-names-to-find-out-whether-a-local-model-belongs-in-a-pharmacy-5ekh</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;A prescription reader for a neighbourhood medical shop in India. It runs on the shop's own laptop, with no network connection at all.&lt;/p&gt;

&lt;p&gt;The shopkeeper photographs a handwritten prescription. The system reads it, works out what was actually prescribed, and decides for itself which lines are safe to fill and which ones need a human to look at the paper.&lt;/p&gt;

&lt;p&gt;The person who would pay for this is the chemist himself. He currently re-reads every prescription by hand because he cannot afford to get one wrong, and he is doing it at 9pm after a fourteen-hour day.&lt;/p&gt;

&lt;p&gt;I should be upfront that the interesting part of this project is not the reading. It is the &lt;strong&gt;deciding when not to trust the reading&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;The whole pipeline runs from one command on the shop's machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/run_page.py prescription.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A synthetic page, end to end:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- OCR (1.77s wall, 1.64s engine, 11 observations -&amp;gt; 9 lines) ---
  [conf 1.00] Dr. R.Deshmukh MBBSReg2019/4482
  [conf 1.00] Pt: Mr. Suresh Wankhede 42/M
  [conf 1.00] Ox: HTN, T2OM
  [conf 1.00] Rx
  [conf 1.00] 1. Tab Telma 40mg 1-0-0 x 30 days
  [conf 1.00] 2. Tab Glycomet GP2 1-0-1 x 30 days
  [conf 1.00] 3. Cap Pan 40 1-0-0 x15 days
  [conf 1.00] 4. Inj. Insulin 0-0-1 x 10 days
  [conf 1.00] Adv: review after 15 days

--- reconciled prescription (57.1s wall) ---
  diagnosis: HTN, T2DM
  [0] Telma 40mg Tab sig=1-0-0 days=30 conf=0.70
  [1] Glycomet GP2 Tab sig=1-0-1 days=30 conf=0.70
  [2] Pan 40 Cap sig=1-0-0 days=15 conf=0.60
  [3] Insulin Inj sig=0-0-1 days=10 conf=0.60

--- gate: review ---
  - line 1 (Telma): model confidence 0.70 is below 0.80
  - line 2 (Glycomet): model confidence 0.70 is below 0.80
  - line 3 (Pan): model confidence 0.60 is below 0.80
  - line 4 (Insulin): model confidence 0.60 is below 0.80

total: 58.9s (OCR 1.8s + model 57.1s)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Look at the third OCR line and then the &lt;code&gt;diagnosis&lt;/code&gt; line underneath it. Those are the same characters read two different ways: the OCR returned &lt;code&gt;Ox: HTN, T2OM&lt;/code&gt;, and the model worked out that it was &lt;code&gt;Dx: HTN, T2DM&lt;/code&gt;. That is the one thing here the model does genuinely better than any regex I could write.&lt;/p&gt;

&lt;p&gt;The other thing worth noticing is that on this page, every line goes to a human. That is entirely the confidence threshold, not the interesting checks, because the model was candid enough to score its own repairs at 0.60–0.70. The structural checks are what earn their place on the &lt;em&gt;poisoned&lt;/em&gt; set further down, where the model is &lt;strong&gt;not&lt;/strong&gt; candid and hands back 0.95 on answers that are wrong.&lt;/p&gt;

&lt;p&gt;On the timing: 57s is the steady state. The first page after the model has been idle takes about 80s, because &lt;code&gt;gemma3:4b&lt;/code&gt; has to be loaded into memory first. I have not tried to hide that number. On an eight-core laptop with no GPU it is the real price of not sending the page anywhere.&lt;/p&gt;
&lt;h3&gt;
  
  
  A hosted copy, and a precise list of what it does not do
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://parchi-hub8.onrender.com" rel="noopener noreferrer"&gt;parchi-hub8.onrender.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I want to be exact about the limits of that link, because the limits happen to be the argument.&lt;/p&gt;

&lt;p&gt;It replays the &lt;strong&gt;real recorded OCR transcript&lt;/strong&gt; from the page above — not a mock-up of one, the actual Vision output, including the &lt;code&gt;Ox: HTN, T2OM&lt;/code&gt; line. The gate, the reorder forecast and the Hindi counselling text all execute live against it. If you want to see the gate refuse something, it is the &lt;code&gt;line 3 (Pan)&lt;/code&gt; entry in the "Why" column.&lt;/p&gt;

&lt;p&gt;What it does not do is run &lt;code&gt;gemma3:4b&lt;/code&gt;. Render's free tier is a Linux container with 512MB of RAM and no GPU, and the model is 3.4GB. So the reading step is a deterministic fixture, and the page says so on itself rather than pretending.&lt;/p&gt;

&lt;p&gt;Two consequences, stated rather than left for you to find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The forecast on that instance reports &lt;code&gt;"method": "damped-mean"&lt;/code&gt;, not TabPFN. Torch does not fit there either; &lt;code&gt;requirements-ml.txt&lt;/code&gt; is what switches TabPFN on, and the fallback announces itself in that field — which is the whole reason the field exists.&lt;/li&gt;
&lt;li&gt;There is no audio on that instance, deliberately. The speech works: &lt;strong&gt;&lt;code&gt;demo/counselling-hindi.mp3&lt;/code&gt;&lt;/strong&gt; in the repo is a real render, 39 seconds of Hindi, checked in so you can listen without a key. But the key is not set on a public host, because an open endpoint that spends credits on every request is a bill that anyone who finds the URL can run up. The key stays on the shop's machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the project's own thesis turned back on itself, so it seemed like the wrong thing to make an exception for.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/kotewar/parchi" rel="noopener noreferrer"&gt;github.com/kotewar/parchi&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pieces worth reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app/gate.py&lt;/code&gt; — the deterministic safety checks, including the two that mattered most&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/gemma.py&lt;/code&gt; — the local model call, with the two API gotchas commented so nobody re-breaks them&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/tabpfn_engine.py&lt;/code&gt; — refill forecasting from the shop's own dispense history&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts/selfcheck.py&lt;/code&gt; — 53 regression checks, runnable with plain python3, no model, no setup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts/eval.py&lt;/code&gt; — the poisoned-name eval harness&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data/eval_poisoned.json&lt;/code&gt; — the 20 test lines and their ground truth&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eval_base_v2.json&lt;/code&gt; and &lt;code&gt;eval_primed_v2.json&lt;/code&gt; — the two runs quoted below, committed so the claims are checkable&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I started by trying to get a vision model to read the handwriting. That was the wrong instinct, and the community had already measured why: a dedicated OCR engine is roughly 300x faster than a general-purpose vision model at reading, with fewer character errors (&lt;a href="https://dev.to/hexisteme/the-dedicated-ocr-engine-lost-to-the-general-purpose-model-300x-slower-2bf7"&gt;The Dedicated OCR Engine Lost to the General Purpose Model&lt;/a&gt; by &lt;a href="https://dev.to/hexisteme"&gt;hexisteme&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So the architecture splits the job:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page image
   -&amp;gt; macOS Vision OCR        (reading: 1.8s/page)
   -&amp;gt; gemma3:4b via Ollama    (meaning: what is this medicine?)
   -&amp;gt; deterministic gate      (safety: may this be filled?)
   -&amp;gt; human review            (only what the gate refuses)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The OCR reads. The model understands. The gate decides. Only one of those three is allowed to be wrong quietly, and it is not the model.&lt;/p&gt;
&lt;h3&gt;
  
  
  The measurement that started everything
&lt;/h3&gt;

&lt;p&gt;While I was still validating the OCR path, I rendered a test prescription in a handwriting font and read it back. Vision read it flawlessly and reported &lt;strong&gt;confidence 1.00 on every line&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One of the lines it read perfectly was this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ox: HTN, T2OM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is &lt;code&gt;Dx: HTN, T2DM&lt;/code&gt; with two letters misread. Nothing about that line was illegible. It was simply wrong, and the OCR was certain.&lt;/p&gt;

&lt;p&gt;That taught me the number I had been planning to trust measures &lt;em&gt;"were the pixels legible"&lt;/em&gt;, not &lt;em&gt;"is this thing real"&lt;/em&gt;. So I threw it out as a safety signal and went looking for what could replace it.&lt;/p&gt;

&lt;p&gt;Confidence scores being useless for this is not a new observation, and I did not discover it. &lt;a href="https://dev.to/sohakanu"&gt;sohakanu&lt;/a&gt; documented it precisely while building Scribe: median confidence 0.95 when right, 0.95 when wrong (&lt;a href="https://dev.to/sohakanu/confidence-is-theater-benchmarking-nine-local-vlm-pipelines-on-handwritten-clinical-forms-38h6"&gt;Confidence Is Theater&lt;/a&gt;). Their fix was cross-model verification — reading each field twice with models that fail differently. &lt;a href="https://dev.to/raju_dandigam"&gt;raju_dandigam&lt;/a&gt; makes the same point from the other direction in &lt;a href="https://dev.to/raju_dandigam/a-confidence-score-is-not-a-probability-act-ask-or-abstain-4g3k"&gt;A Confidence Score Is Not a Probability&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I took a different route, because a pharmacy has something a form does not: a fixed vocabulary. The shop knows which forty medicines it actually stocks. That is checkable in code, for free.&lt;/p&gt;
&lt;h3&gt;
  
  
  So I wrote 20 poisoned drug names
&lt;/h3&gt;

&lt;p&gt;I built a test set of twenty lines, each a realistic OCR corruption of a real Indian brand: &lt;code&gt;0&lt;/code&gt; misread as &lt;code&gt;O&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt; as &lt;code&gt;l&lt;/code&gt;, &lt;code&gt;5&lt;/code&gt; as &lt;code&gt;S&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then I ran them through &lt;code&gt;gemma3:4b&lt;/code&gt; and sorted every answer into one of four buckets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bucket&lt;/th&gt;
&lt;th&gt;meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;repaired&lt;/td&gt;
&lt;td&gt;returned the true medicine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;refused&lt;/td&gt;
&lt;td&gt;declined to name one — safe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;echoed_garbage&lt;/td&gt;
&lt;td&gt;accepted the corrupted string as a medicine — &lt;strong&gt;dangerous&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;silent_wrong&lt;/td&gt;
&lt;td&gt;invented a different medicine — &lt;strong&gt;dangerous&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last two are tracked together, because from the patient's side they are the same failure: a wrong medicine dispensed with confidence.&lt;/p&gt;
&lt;h3&gt;
  
  
  What the base model did
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Nine of twenty lines came back wrong. All nine came back at 0.95 confidence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Eighteen of twenty answers scored 0.90 or above. Nine of those eighteen were wrong. The confidence column carried no signal whatsoever, which means a threshold gate built on it would have passed nine wrong prescriptions straight through to the counter.&lt;/p&gt;

&lt;p&gt;Two failure modes did the damage, and I only understood the second one by reading the eval output line by line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It substituted generics for brands.&lt;/strong&gt; Asked about &lt;code&gt;Glycomet 6P2&lt;/code&gt;, it answered &lt;code&gt;Metformin&lt;/code&gt;. For &lt;code&gt;Pan 4O&lt;/code&gt; it answered &lt;code&gt;Panadol&lt;/code&gt;. For &lt;code&gt;Atorva 1O&lt;/code&gt;, &lt;code&gt;Atorvastatin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These are not cosmetic swaps. Glycomet GP2 contains glimepiride as well as metformin. Panadol is paracetamol; Pan is pantoprazole. A generic substitution is a different prescription.&lt;/p&gt;

&lt;p&gt;And it is invisible in the JSON — except in one place. &lt;strong&gt;The name it returned is not anywhere on the page.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I check exactly that. The longest word in the returned drug name gets compared against the raw OCR text with an OCR-sized edit tolerance:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_grounding_problems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RxLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Does the medicine the model named actually appear on the page?&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drug&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="n"&gt;drug_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;normalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drug&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;drug_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="n"&gt;source_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;normalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drug_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;closely_matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source_tokens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; does not appear on the page as written &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(OCR line reads: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Checking that a value actually appears in its source is a known and documented technique — &lt;a href="https://dev.to/michael_maurice"&gt;michael_maurice&lt;/a&gt; lays out the full discipline, including the crucial repairable/non-repairable split, in &lt;a href="https://dev.to/michael_maurice/schema-valid-correct-4-checks-before-you-trust-llm-extraction-4n6f"&gt;Schema-Valid Isn't Correct&lt;/a&gt;. I am applying it to brand names rather than invoice totals.&lt;/p&gt;

&lt;p&gt;That one check caught &lt;strong&gt;all five&lt;/strong&gt; substitutions the model made.&lt;/p&gt;
&lt;h3&gt;
  
  
  The second failure mode took me a whole eval run to see
&lt;/h3&gt;

&lt;p&gt;With grounding in place, four of the nine were still getting through. All four were the same shape:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;thyronorm-o-for-zero   strength='5Omcg'
ecosprin-s-for-five    strength='7S'
amlopres-s-for-five    strength='2.S'
insulin-units-word     strength='1O units'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every one was the same thing. The drug name was correct, and it was genuinely on the page, so grounding had nothing to say about it. The &lt;strong&gt;strength&lt;/strong&gt; was the corruption: &lt;code&gt;5O&lt;/code&gt; should be &lt;code&gt;50&lt;/code&gt;, &lt;code&gt;7S&lt;/code&gt; should be &lt;code&gt;75&lt;/code&gt;, &lt;code&gt;2.S&lt;/code&gt; should be &lt;code&gt;2.5&lt;/code&gt;, &lt;code&gt;1O units&lt;/code&gt; should be &lt;code&gt;10 units&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And my gate had accepted all four, because I was checking that the strength field was &lt;em&gt;non-empty&lt;/em&gt;. Not that it was a strength.&lt;/p&gt;

&lt;p&gt;Two regexes closed the gap. One for what a dose looks like, and one for the Indian combination markers that are legitimate strengths without being numbers at all:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_DOSE_RE&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^\d+(?:\.\d+)?(?:mg|mcg|mgs|g|gm|ml|iu|units|unit|%)?$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_SUFFIX_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^[a-z]{1,3}\d+$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# GP2 in Glycomet GP2
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_strength_problems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;strength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="n"&gt;compact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\s+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_DOSE_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;_SUFFIX_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# If the digits are recoverable, say what we think it was. Suggesting a
&lt;/span&gt;    &lt;span class="c1"&gt;# correction is safe; applying one silently is not.
&lt;/span&gt;    &lt;span class="n"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_repair_digits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;_DOSE_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strength &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;strength&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt; does not parse as a strength &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(looks like &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt; with a letter misread for a digit)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strength &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;strength&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt; does not parse as a strength&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Four misses went to zero. A wrong dose and a wrong drug are the same amount of wrong.&lt;/p&gt;
&lt;h3&gt;
  
  
  The false alarm I shipped, and how it surfaced
&lt;/h3&gt;

&lt;p&gt;A word on false alarms, because a gate that cries wolf is a gate people click through. Three lines that the eval scores as safe still get sent to review: one where the model refused outright, one where it put &lt;code&gt;x 30 days&lt;/code&gt; in the sig field, and the deliberately-illegible control case. I read all three. None is a correct answer being wrongly held back — they are the gate doing its job on a field my eval was not scoring.&lt;/p&gt;

&lt;p&gt;Then, while reading the demo output above, I found one that &lt;strong&gt;was&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The strength check was flagging the &lt;code&gt;GP2&lt;/code&gt; in &lt;code&gt;Glycomet GP2&lt;/code&gt; as unparseable. &lt;code&gt;GP2&lt;/code&gt; is a combination marker — metformin plus glimepiride — and my regex only accepted digits with an optional unit. It was rejecting a correct answer, and &lt;strong&gt;no test caught it, because every test I had written encoded the same wrong assumption the code did.&lt;/strong&gt; It surfaced because a human read the output.&lt;/p&gt;

&lt;p&gt;That is now &lt;code&gt;scripts/selfcheck.py&lt;/code&gt;: 53 checks covering the gate, the formulary, the counselling text and the end-to-end path, runnable with plain &lt;code&gt;python3&lt;/code&gt;, no model and nothing to install. Two of those checks exist purely because of how the fix went wrong the first time — &lt;code&gt;1O units&lt;/code&gt; repaired to &lt;code&gt;10unit5&lt;/code&gt; (the &lt;code&gt;s&lt;/code&gt; in "units" was being read as a 5), and &lt;code&gt;xyz&lt;/code&gt; acquired a confident repair hint for no reason at all. A wrong suggestion on a dosage is worse than no suggestion.&lt;/p&gt;

&lt;p&gt;The same wrong assumption turned out to be sitting in the hosted demo's fixture too, a day later: the fixture had &lt;code&gt;Glycomet GP2&lt;/code&gt; with a null strength, so the gate reported "tablet with no strength" on a line whose strength was right there in the name. The real extractor returns &lt;code&gt;GP2&lt;/code&gt; in the strength field. The fixture was less realistic than the model it was standing in for, and it took reading the deployed page to notice.&lt;/p&gt;
&lt;h3&gt;
  
  
  Where it landed
&lt;/h3&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;base model&lt;/th&gt;
&lt;th&gt;with the shop's own correction history&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;wrong answers&lt;/td&gt;
&lt;td&gt;9/20 (45%)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6/20 (30%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;td&gt;9/20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13/20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;filled with no human looking&lt;/td&gt;
&lt;td&gt;7/20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11/20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wrong answers reaching the counter&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0/20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0/20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model is still wrong a third of the time. None of it reaches the patient, because the gate does not accept the model's word for anything it can check itself.&lt;/p&gt;
&lt;h3&gt;
  
  
  The correction loop, and a result I did not expect
&lt;/h3&gt;

&lt;p&gt;Every line the pharmacist corrects is written to a local SQLite table on the shop's machine. Those corrections are the shop's own vocabulary: &lt;em&gt;in this shop, this handwriting, "Telrna" is Telma.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I fed 18 of them back into the prompt as context and re-ran the eval. It improved things: 45% wrong down to 30%, and four more lines filled without a human.&lt;/p&gt;

&lt;p&gt;But it also introduced &lt;strong&gt;three regressions&lt;/strong&gt; I would have missed if I had only looked at the totals:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;glycomet-g-to-6     silent_wrong -&amp;gt; echoed_garbage   ("Glycomet 6P2" -&amp;gt; "Glycomet", GP2 dropped)
telmikind-h         repaired     -&amp;gt; echoed_garbage   ("Telmikind-H"  -&amp;gt; "Telmikind", -H dropped)
montck-dropped-e    silent_wrong -&amp;gt; echoed_garbage   ("Montck LC"    -&amp;gt; "Montck LC")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Given the shop's vocabulary, the model starts &lt;strong&gt;snapping to the nearest familiar name&lt;/strong&gt; and silently drops suffixes that change what the medicine is. Telmikind-H contains a diuretic. Telmikind does not.&lt;/p&gt;

&lt;p&gt;So the correction history is not a free win. It is a trade: better recovery of corrupted strengths, at the cost of a new way to be confidently wrong. The gate catches all three anyway, which is the point of having a gate that does not care how confident the model sounds.&lt;/p&gt;

&lt;p&gt;This is also the honest reason the corrections matter. A hosted API cannot learn that &lt;em&gt;this&lt;/em&gt; shop's handwriting turns Telma into Telrna, because the corrections are the most private thing in the building. They stay on the machine, and they are what a fine-tune would later be built from — via Tinker, so the shop never needs a GPU.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Three reasons, in the order they actually matter to the shopkeeper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The data cannot leave.&lt;/strong&gt; A prescription carries a patient's name, age, diagnosis and medicines. That is the most sensitive record the shop holds. The whole point is that the model runs where the data already is. Shipping it to a hosted database to prove the model is local would give away the thing being protected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The vocabulary is local knowledge.&lt;/strong&gt; Knowing that this shop stocks forty specific brands, and that in this handwriting &lt;code&gt;Telrna&lt;/code&gt; means Telma, is not general knowledge. It is not in anyone's training data. It exists only in that shop, and it is the difference between a system that works and a system that is confidently wrong. Owning the model is what makes the correction loop worth building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume makes per-call pricing cruel.&lt;/strong&gt; This is not a chat app. It is a shop doing a couple of hundred pages a day, forever. A per-page API bill is a recurring cost that grows with the shop's success. A local model costs electricity and a laptop the shop already owns — and the slowest thing in the pipeline is the part I did not pay for.&lt;/p&gt;

&lt;p&gt;There is a fourth reason I did not expect. Running locally meant I could measure the model's failures on the shop's real material, as many times as I wanted, for free. The 20-line eval that this whole post is about cost nothing to run eleven times. That is not really possible when every run is a billed API call.&lt;/p&gt;

&lt;p&gt;And a fifth, which is the one this post is really about. Running locally meant I could &lt;strong&gt;read the raw output&lt;/strong&gt;. The generic-for-brand substitution at 0.95 confidence is not visible in an aggregate score. Neither was the false alarm I had shipped in my own gate. Both were found by looking at what actually came back, one line at a time, on a machine I owned.&lt;/p&gt;

&lt;p&gt;The hosted demo makes the same point from the other side. Every capability I had to switch off to make it fit on a free container — the model itself, the forecaster, the voice — is a capability that works when the software sits next to the data and the patient.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;The bug I am least proud of, and the one that taught me the most: the false positive above was invisible to every test I had, because I had written the tests from the same wrong assumption as the code. Here is the session where reading the real output caught it.&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;Finding a false positive in a prescription safety gate&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;Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.&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;I'd just refactored the formulary to share a tolerant matcher between the food-timing table and the chronic list. My test only re-ran the cases I had changed, which passed. Let me re-run the whole thing instead, on every entry.&lt;/p&gt;

&lt;p&gt;It immediately blew up. &lt;code&gt;food_timing("Glycomet GP2")&lt;/code&gt; raised KeyError. The shared matcher normalises hyphens to spaces and returns that normalised key, but &lt;code&gt;FOOD_TIMING&lt;/code&gt; was still keyed &lt;code&gt;"glycomet-gp2"&lt;/code&gt; with a hyphen. So it matched, then failed to index the table. Every hyphenated entry was broken.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;.venv/bin/python -c "from app.formulary import food_timing; food_timing('Glyc...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ .venv/bin/python -c "from app.formulary import food_timing; food_timing('Glycomet GP2')"&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;KeyError: 'glycomet gp2'

-&amp;gt; root cause: match_key returns a NORMALISED key and indexes the table with it, but the table keys were not normalised&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Fix: make the table keys normalised, and add an import-time guard so this fails loudly at import instead of at 9pm at the counter.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;app/formulary.py - rewrite FOOD_TIMING with normalised keys ("glycomet gp2", ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;formulary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rewrite&lt;/span&gt; &lt;span class="n"&gt;FOOD_TIMING&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;normalised&lt;/span&gt; &lt;span class="nf"&gt;keys &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;glycomet gp2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taxim o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dolo 650&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;raises&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;already&lt;/span&gt; &lt;span class="n"&gt;normalised&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Edit&lt;/span&gt; &lt;span class="n"&gt;applied&lt;/span&gt; &lt;span class="n"&gt;successfully&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;.venv/bin/python -c "from app.formulary import food_timing, is_chronic; ..." ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ .venv/bin/python -c "from app.formulary import food_timing, is_chronic; ..."  # every entry, not just the changed ones&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;tables: 55 food-timing entries, 31 chronic
Glycomet GP2       food=with food      chronic=True
Pan 40             food=before food    chronic=False
Panadol            food=None           chronic=False
Taxim-O 200        food=after food     chronic=False
Mixtard 30/70      food=as the doctor has set out, with the injection

all reachable, and Panadol correctly gets no rule&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Lesson worth writing down: re-running only the cases you touched is how a refactor ships a KeyError. The full sweep found it in one run.&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;Next I ran the actual demo page through the whole pipeline to get real output for the writeup, and read it line by line. That is where the more interesting bug was.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;.venv/bin/python scripts/run_page.py&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ .venv/bin/python scripts/run_page.py&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- OCR (1.78s wall, 1.64s engine, 11 observations -&amp;gt; 9 lines) ---
&lt;/span&gt;  [conf 1.00] Ox: HTN, T2OM
  [conf 1.00] 2. Tab Glycomet GP2 1-0-1 x 30 days
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;--- reconciled prescription (57.1s wall) ---
&lt;/span&gt;  diagnosis: HTN, T2DM
  [1] Glycomet GP2 Tab sig=1-0-1 days=30 conf=0.70
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;--- gate: review ---
&lt;/span&gt;  - line 2 (Glycomet): model confidence 0.70 is below 0.80; strength 'GP2' does not parse as a strength
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;total: 58.9s (OCR 1.8s + model 57.1s)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The gate is flagging &lt;code&gt;GP2&lt;/code&gt; as an unparseable strength. But GP2 is not a number at all -- Glycomet GP2 is metformin plus glimepiride, and the GP2 is the entire point of the prescription. The extraction prompt even lists "GP2" as a valid example of a strength. My regex only accepted digits plus an optional unit, so it rejected a correct answer.&lt;/p&gt;

&lt;p&gt;This is the exact failure mode gate.py's own docstring warns about: a gate that cries wolf is a gate people click past, and then it protects nobody. And nothing in the repo caught it. It surfaced because a human read the output.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;app/gate.py&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_STRENGTH_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^\d+(\.\d+)?(mg|mcg|mgs|g|gm|ml|iu|units|unit|%)?$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# A strength is digits, optionally a decimal, optionally a unit. Nothing else.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Fix: two branches. A dose -- digits, optional decimal, optional unit -- or an Indian combination suffix, one to three letters then digits. It costs nothing in detection, because every real corruption this check catches puts a letter where a digit should be ("7S", "5Omcg"), and the suffix branch accepts none of those.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;.venv/bin/python -c "from app.gate import _strength_problems; ..."&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ .venv/bin/python -c "from app.gate import _strength_problems; ..."&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;ACCEPTED: 40mg 500mg 2.5mg 10 units 75mcg GP2 gp2
FLAGGED:  7S -&amp;gt; looks like '75'
          5Omcg -&amp;gt; looks like '50mcg'
          2.S -&amp;gt; looks like '2.5'
          1O units -&amp;gt; does not parse
          6P2 -&amp;gt; does not parse&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;But two message-quality bugs fell out of the same reading:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;1O units&lt;/code&gt; lost its repair hint. The letter-to-digit table maps "s" to 5, and "units" ends in s, so the repair produced "10unit5". Nonsense, and it threw away a hint that was correct.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;xyz&lt;/code&gt; gained a hint. The map turned it into "xy2", which satisfies the new suffix shape and means nothing. A wrong suggestion on a dosage is worse than no suggestion.&lt;/li&gt;
&lt;/ol&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;app/gate.py - split a trailing unit off before repairing digits; offer the re...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;trailing&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="n"&gt;repairing&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;offer&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;repair&lt;/span&gt; &lt;span class="n"&gt;hint&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;lands&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="n"&gt;dose&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Edit&lt;/span&gt; &lt;span class="n"&gt;applied&lt;/span&gt; &lt;span class="n"&gt;successfully&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;.venv/bin/python -c "..." ; .venv/bin/python scripts/gate_recheck.py eval_bas...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ .venv/bin/python -c "..." ; .venv/bin/python scripts/gate_recheck.py eval_base_v2.json&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;1O units -&amp;gt; looks like '10units'
xyz -&amp;gt; (no hint)

model dangerous  9/20
gate caught      9/9
gate MISSED      0/9
correct answers now over-flagged  0
END-TO-END dangerous rate 0/20 (0%)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;So the fix removed a false positive without weakening detection: still 9/9 caught, 0 missed, 0 correct answers held back.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;scripts/selfcheck.py - 47 checks, plain python3, no pytest, no model, no setup&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;scripts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;selfcheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;47&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="n"&gt;python3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight python"&gt;&lt;code&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt;
&lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formulary&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="nb"&gt;all&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;selfcheck.py encodes every case that was wrong at some point, including this false positive, plus the formulary KeyError. It needs nothing installed, because a claim a judge cannot cheaply verify is not much of a claim.&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;
        3 of 16 messages
          · some content was hidden by the author
    &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 Gemma.&lt;/strong&gt; The understanding layer is Google's &lt;code&gt;gemma3:4b&lt;/code&gt;, served locally through Ollama, running on the CPU of an eight-core laptop with no GPU. It is the only reason the "generic becomes brand" substitution is visible at all: the model reads &lt;code&gt;Glycomet 6P2&lt;/code&gt; and reaches for the only metformin brand it has ever heard of. Every safety check in the system exists because Gemma is useful and unreliable in a specific, measurable way — and because it runs locally, I could measure exactly how unreliable, eleven times over, for nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of TabPFN.&lt;/strong&gt; The shop's second problem is knowing what to reorder, and it is a tabular forecasting problem of exactly the kind a chemist cannot answer from memory: a dozen medicines, three months of dispense counts, and spiky demand around festivals. &lt;code&gt;app/tabpfn_engine.py&lt;/code&gt; builds a weekly series per medicine and fits Prior Labs' TabPFN walk-forward — never training on a week it is about to predict — then ranks by urgency, weighted for the chronic medicines that must never run out.&lt;/p&gt;

&lt;p&gt;It forecasts &lt;em&gt;demand per medicine&lt;/em&gt;, not refills per patient. That is deliberate: the &lt;code&gt;dispenses&lt;/code&gt; table has no patient column, because it is the only table that could leave the machine without carrying anyone's medical history with it. The richer signal would have cost the privacy claim the whole project rests on.&lt;/p&gt;

&lt;p&gt;One detail I would keep in any system like this: without &lt;code&gt;requirements-ml.txt&lt;/code&gt; the forecaster silently falls back to a damped moving average, so every row carries the method that produced it — &lt;code&gt;"method": "tabpfn (53 shop rows)"&lt;/code&gt;. A fallback can never be mistaken for a forecast. That field is also how I discovered that tabpfn was not installed at all while the code claimed it was, which is a lesson about the difference between integrating a library and running one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of ElevenLabs.&lt;/strong&gt; The last mile of a prescription is the part the pharmacist has already said fifty times before lunch: what to take, when to take it, for how long, and what to do if something goes wrong. That explanation is spoken, not read. Many of the people this shop serves would rather hear it than read a label, and by the fiftieth time the pharmacist is no longer saying all of it.&lt;/p&gt;

&lt;p&gt;ElevenLabs' &lt;code&gt;eleven_multilingual_v2&lt;/code&gt; renders that script in Hindi from &lt;code&gt;prompts/counsel.txt&lt;/code&gt;, and the parts he drops are exactly the parts written into the prompt: never take two doses at once to catch up on a missed one, come back to the shop with the strip if anything is unclear, and see a doctor immediately for a rash, swelling or difficulty breathing. It is a real render — 485 characters in, 39 seconds of Hindi out, at one credit per character — and it is checked into the repo as &lt;code&gt;demo/counselling-hindi.mp3&lt;/code&gt; so it can be listened to rather than taken on trust.&lt;/p&gt;

&lt;p&gt;One thing I got wrong first and would have shipped: I originally rendered it with a default English premade voice. &lt;code&gt;eleven_multilingual_v2&lt;/code&gt; will happily make an American voice read Devanagari script, and it sounds exactly as wrong as that suggests — a patient being told their dosage in a foreign accent. Auditioning the Hindi voices in the shared library and switching to a native one cost 240 credits and one afternoon. Worth saying out loud, because "the multilingual model speaks Hindi" and "the voice sounds like it speaks Hindi" are two different claims, and only the first one is the model's job.&lt;/p&gt;

&lt;p&gt;The design decision I would defend is that the audio is opt-in per request and off by default. It is the only step in the pipeline that sends anything off the machine, and what it sends necessarily contains the medicine names — a patient cannot be told to take something the sentence refuses to name. So the text stays local and the pharmacist asks for the speech. The hosted demo has no key at all, for the same reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Render.&lt;/strong&gt; The hosted demo above is deployed from a Render blueprint — &lt;code&gt;render.yaml&lt;/code&gt; lives in the repository, so the service is described in version control rather than clicked together in a dashboard, and a stranger can reproduce the deploy from the repo alone.&lt;/p&gt;

&lt;p&gt;The interesting part was making a free-tier container honest. There is no GPU and 512MB of RAM, so instead of pretending, the deployment was designed around the constraint: &lt;code&gt;requirements.txt&lt;/code&gt; and &lt;code&gt;requirements-ml.txt&lt;/code&gt; are separate files so the deployable dependency set stays small enough to build without dragging in torch, &lt;code&gt;app/main.py&lt;/code&gt; never imports the macOS Vision wrapper (&lt;code&gt;app/ocr.py&lt;/code&gt;) so the Linux container boots, and &lt;code&gt;DEMO_MODE=true&lt;/code&gt; replays the real recorded OCR transcript while the gate, the forecaster and the counselling text all execute for real. The page tells you which parts are fixtures and why, which is the only reason I would put a demo link in a post about not trusting things.&lt;/p&gt;




&lt;p&gt;Thanks to &lt;a href="https://dev.to/sohakanu"&gt;sohakanu&lt;/a&gt;, &lt;a href="https://dev.to/michael_maurice"&gt;michael_maurice&lt;/a&gt;, &lt;a href="https://dev.to/raju_dandigam"&gt;raju_dandigam&lt;/a&gt; and &lt;a href="https://dev.to/hexisteme"&gt;hexisteme&lt;/a&gt; — this project would have taken a much longer route to a worse place without their write-ups.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All names, prescriptions and corrections in this post are synthetic. No real patient data was used, and no page left the machine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>ConcreteOasis: Screenless Urban Microclimate &amp; Heat-Island Guide Powered by Gemma 4 &amp; TabPFN</title>
      <dc:creator>Sankalp Kotewar</dc:creator>
      <pubDate>Tue, 06 Oct 2026 18:52:07 +0000</pubDate>
      <link>https://dev.to/sankalpkotewar/my-hacktoberfest-open-source-ai-challenge-submission-touch-grass-project-5m</link>
      <guid>https://dev.to/sankalpkotewar/my-hacktoberfest-open-source-ai-challenge-submission-touch-grass-project-5m</guid>
      <description>&lt;p&gt;&lt;em&gt;A zero-touch, screenless ambient walking guide that turns urban concrete jungles into mindful green escapes using Gemma 4, TabPFN, and ElevenLabs.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Paradox: Touching Grass in a Concrete Jungle
&lt;/h2&gt;

&lt;p&gt;Over four billion people live in dense urban centers. When hackathons announce themes like "Touch Grass" or outdoor navigation, the default assumption is that you live a 15-minute drive away from an alpine hiking trail or national park.&lt;/p&gt;

&lt;p&gt;For most of us working tech jobs in bustling cities, that is not reality. Our outdoors is the concrete jungle: sun-baked asphalt roads, narrow colony lanes, flyovers, and scattered municipal green pockets.&lt;/p&gt;

&lt;p&gt;Worse yet, the moment we step outside to decompress, existing fitness and trail apps do the exact opposite of helping us disconnect: they vibrate constantly, demand screen taps, display noisy dashboards, and force us to look down at glass instead of up at our surroundings.&lt;/p&gt;

&lt;p&gt;We built &lt;strong&gt;ConcreteOasis&lt;/strong&gt; to solve this. It is an ambient, zero-touch walking companion that runs silently from your pocket. By combining &lt;strong&gt;Gemma 4&lt;/strong&gt;, Prior Labs' &lt;strong&gt;TabPFN&lt;/strong&gt;, and &lt;strong&gt;ElevenLabs&lt;/strong&gt;, ConcreteOasis predicts microclimatic heat islands and pavement hazards on everyday city streets—whispering terse, mindful cues into your earphones only when conditions shift.&lt;/p&gt;

&lt;p&gt;Total screen time required during a 35-minute urban walk: &lt;strong&gt;0 seconds.&lt;/strong&gt;&lt;/p&gt;




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

&lt;p&gt;ConcreteOasis turns any smartphone and basic earphones into an autonomous, screenless urban nature guide:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Touch Pocket Mode:&lt;/strong&gt; You tap "Start Walk" at your front door, and the web client engages the Screen Wake Lock API over a pitch-black OLED canvas (&lt;code&gt;#000000&lt;/code&gt;). Your phone stays safely stowed in your pocket with zero battery burn and zero risk of mobile operating systems suspending background GPS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microclimate &amp;amp; Urban Heat-Island Prediction:&lt;/strong&gt; Standard weather forecasts give a single, blanket city temperature. But asphalt radiates up to 8°C higher than ambient air, while a dense Neem or Peepal canopy provides an immediate micro-cooling pocket. Prior Labs' &lt;strong&gt;TabPFN&lt;/strong&gt; computes hyper-local surface heat deltas and pavement slickness probabilities in real time from tabular environmental features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terse Conversational Filtering via Gemma 4:&lt;/strong&gt; Instead of robotic GPS chimes or raw sensor telemetry, &lt;strong&gt;Gemma 4&lt;/strong&gt; acts as an intelligent conversational filter. If conditions are unchanged, it enforces silence. When you enter a shaded pocket or hazardous stretch, it synthesizes a grounding audio cue in under 15 words.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hands-Free Ambient Audio via ElevenLabs:&lt;/strong&gt; The synthesized cue streams directly to your Bluetooth earbuds as a calming natural voice.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Why Open-Source AI Matters
&lt;/h2&gt;

&lt;p&gt;Every prompt question for the Hacktoberfest Open-Source AI Challenge guided our architectural decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Sovereignty &amp;amp; Privacy:&lt;/strong&gt; Personal location telemetry, stride cadence, and movement logs are deeply sensitive. With open-weight models like &lt;strong&gt;Gemma 4&lt;/strong&gt; and self-hosted agents, your daily routes and health habits are never fed into proprietary cloud advertising datasets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specialized Tabular Foundation Models Over Monolithic LLMs:&lt;/strong&gt; LLMs notoriously hallucinate when asked to perform arithmetic on continuous multi-variable physics data. By using Prior Labs' &lt;strong&gt;TabPFN&lt;/strong&gt;, we perform zero-shot Bayesian tabular inference on microclimate metrics (soil moisture, canyon aspect ratio, solar elevation) with mathematical precision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freedom from API Tolls:&lt;/strong&gt; Gemma 4's Apache 2.0 licensing and ultra-efficient edge footprint (E2B / E4B with Per-Layer Embeddings) prove that personal, ambient AI can run cost-free on everyday developer hardware without recurring API subscription barriers.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Architecture &amp;amp; How It Works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[IN POCKET: PHONE / WATCH]
Smartphone (OLED Screen Wake Lock, Background GPS Polling)
  │
  │  HTTP POST /api/telemetry { lat, lon, speed, heart_rate }
  ▼
[RENDER CLOUD HOST: FastAPI Gateway]
  │
  ├── 1. Context Aggregator:
  │      Pulls Open-Meteo microclimate metrics &amp;amp; OpenStreetMap street canyon attributes.
  │
  ├── 2. TabPFN Inference (Prior Labs):
  │      Predicts [heat_island_delta_celsius, pavement_slip_risk] from tabular vector.
  │
  ├── 3. Gemma 4 Agent (Ollama / Local Endpoint):
  │      Synthesizes situational context into a &amp;lt;=15 word spoken whisper (or returns SILENCE).
  │
  ├── 4. ElevenLabs Voice API:
  │      Converts text cue into streaming audio buffer sent to earphones.
  │
  └── 5. Sentry Agent Tracing:
         Distributed spans track telemetry ingestion, TabPFN latency, and token generation.
  │
  ▼
[EARPHONES / AIRPODS]
Ambient audio cue plays automatically over Bluetooth.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Technical Highlights &amp;amp; Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. TabPFN Microclimate Prediction (&lt;code&gt;tabpfn_engine.py&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tabpfn&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TabPFNRegressor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TabPFNClassifier&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UrbanMicroclimateEngine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;training_csv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;training_csv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;surface_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solar_angle_deg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;canopy_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rain_48h_mm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;canyon_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heat_regressor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TabPFNRegressor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heat_regressor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heat_island_delta_celsius&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slip_classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TabPFNClassifier&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slip_classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pavement_slip_hazard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate_location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;X_eval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;heat_delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heat_regressor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_eval&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;slip_prob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slip_classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict_proba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_eval&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heat_delta&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="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slip_prob&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Gemma 4 Conversational Synthesis Prompt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System: You are ConcreteOasis, an ambient walking companion for city streets.
Synthesize tabular microclimate metrics and urban trees into an ultra-concise spoken whisper.
Rules:
- Maximum 15 words.
- Calm, observant, natural tone.
- If neither thermal relief nor significant greenery nor slip risk is present, output exactly: SILENCE.
- Never output markdown, explanations, or filler.

Input: {"canopy": 0.85, "tree": "Neem", "heat_delta": -3.4, "slip_risk": 0.12, "cadence": "steady"}
Output: Stepping under dense Neem canopy. Ambient heat drops three degrees. Ease your stride and breathe.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Distributed Observability with Sentry Agent Tracing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk.integrations.fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastApiIntegration&lt;/span&gt;

&lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SENTRY_DSN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;traces_sample_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;integrations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;FastApiIntegration&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/telemetry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_telemetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TelemetryPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent.cycle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UrbanStrideCycle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tabpfn.inference&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Predict microclimate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;heat_delta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slip_risk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tabpfn_engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate_location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma.synthesis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gemma 4 text synthesis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;cue_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;gemma_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_ambient_cue&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cue_text&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SILENCE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;elevenlabs.tts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stream voice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;audio_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;audio_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synthesize_speech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cue_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Field Test: Taking ConcreteOasis Outside
&lt;/h2&gt;

&lt;p&gt;To prove that the screen was truly the shortest part of the experience, we took ConcreteOasis out for a 35-minute evening walk through an urban neighborhood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Setup:&lt;/strong&gt; AirPods in ears, tapped "Start Walk" on the phone, and stowed the phone into a pocket.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Experience:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Crossing an open asphalt intersection: complete silence. The app didn't bombard us with meaningless step counters.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Turning into a shaded residential avenue lined with mature trees: The earphones chimed softly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Entering dense shade cover. Temperature drops three degrees. Keep this easy stride."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Approaching an alleyway with broken, wet paver stones: TabPFN flagged an 81% slip probability, prompting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Uneven, damp paving stones ahead from morning runoff. Watch your footing on the bend."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Verdict:&lt;/strong&gt; Zero glances at a screen for the entire 35 minutes. We returned home relaxed, having engaged with our surroundings rather than a glass display.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Built with GitHub Copilot in VS Code
&lt;/h2&gt;

&lt;p&gt;We scaffolded and developed ConcreteOasis entirely in &lt;strong&gt;VS Code using GitHub Copilot&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent Scaffolding:&lt;/strong&gt; Used Copilot Chat to design the Pydantic schemas and tie the asynchronous FastAPI pipeline together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geofencing &amp;amp; Tabular Matrices:&lt;/strong&gt; Copilot generated the feature matrix normalizers and synthetic training distribution for TabPFN.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client Audio Buffer:&lt;/strong&gt; Copilot wrote the Web Audio context handler that allows uninterrupted background Bluetooth audio playback on mobile browsers.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma ($200):&lt;/strong&gt; Gemma 4 serves as the core natural language reasoning engine, filtering alerts and generating concise spoken wisdom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of TabPFN ($200):&lt;/strong&gt; Prior Labs' TabPFN tabular foundation model predicts continuous urban heat-island deltas and pavement slip classification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render ($200):&lt;/strong&gt; The FastAPI edge backend and PWA client are deployed on Render.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of GitHub Copilot ($100):&lt;/strong&gt; Scaffolded end-to-end inside VS Code using Copilot Chat and agent mode for geofencing math and tool schemas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of ElevenLabs ($100):&lt;/strong&gt; Natural streaming audio delivery directly into wireless earphones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Sentry Agent Tracing ($100):&lt;/strong&gt; Instrumented end-to-end spans showing TabPFN inference time, Gemma token latency, and audio delivery roundtrips.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo &amp;amp; Repository
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Demo:&lt;/strong&gt; TBD&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository:&lt;/strong&gt; TBD&lt;/li&gt;
&lt;/ul&gt;

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