<?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: Harsh Satyajit Thakur</title>
    <description>The latest articles on DEV Community by Harsh Satyajit Thakur (@ottodeviant1904).</description>
    <link>https://dev.to/ottodeviant1904</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%2F3814810%2F401ecd95-0e56-4412-87ce-f029a792b7b9.jpeg</url>
      <title>DEV Community: Harsh Satyajit Thakur</title>
      <link>https://dev.to/ottodeviant1904</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ottodeviant1904"/>
    <language>en</language>
    <item>
      <title>Gemma 4 reads, code decides: offline NDIS invoicing that caught 20 of 22 restraints</title>
      <dc:creator>Harsh Satyajit Thakur</dc:creator>
      <pubDate>Mon, 19 Oct 2026 03:16:14 +0000</pubDate>
      <link>https://dev.to/ottodeviant1904/gemma-4-reads-code-decides-offline-ndis-invoicing-that-caught-20-of-22-restraints-4kj8</link>
      <guid>https://dev.to/ottodeviant1904/gemma-4-reads-code-decides-offline-ndis-invoicing-that-caught-20-of-22-restraints-4kj8</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;Sunday night in Melbourne. Mel has four participants and a phone full of notes like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tues (Cup day!!) - Jake 3 till 8:30ish. Footy training then tea at his place.
Locked the pantry so he would stop snacking before dinner, he was pretty upset about it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy645oa6oz65z0jui8rxw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy645oa6oz65z0jui8rxw.jpg" alt="Mel, a stick-figure support worker, sits bolt upright on her couch at night, phone in hand, a red flag popping up above her head" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mel is not a real person; she is a composite from published rate guides and NDIS Commission material. BUt the job is real: an independent NDIS support worker on an ABN, unpaid for the hours it takes to turn that diary into invoices. That line holds two traps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The price.&lt;/strong&gt; Melbourne Cup Day is a public holiday in Melbourne and most of Victoria, and in no other state, so all 5.5 hours go on the public-holiday item at $163.46 an hour, not the weekday $73.58. On an ordinary Tuesday the shift crosses 8pm and the whole support takes the higher rate; splitting at 8pm underclaims.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbp4a1ekh8mzrdhz4kw9m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbp4a1ekh8mzrdhz4kw9m.jpg" alt="A desk calendar that shows Tue 3 Nov, Cup Day, Victorian public holiday; 5.5 h at $163.46/h = $899.03" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pantry.&lt;/strong&gt; Locking a pantry to stop someone snacking is an environmental restraint under the NDIS (Restrictive Practices and Behaviour Support) Rules 2018. For a registered provider, unauthorised use is a reportable incident: 5 business days to notify the NDIS Commission, 24 hours if it caused harm. An unregistered sole trader like Mel may not use one at all, and the Commission asks for it to be raised with them. Either way it reads like an ordinary note; Mel would have invoiced it and moved on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu4gtvv6rvvvrrniqyhly.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu4gtvv6rvvvrrniqyhly.jpg" alt="Two paths from a padlocked pantry: registered provider, notify the NDIS Commission within 5 business days (24 h if harm); unregistered, must not use" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Arvo does the Sunday-night job and asks the awkward question. Type, photograph or dictate the week; get back invoice lines a plan manager will accept, a printable invoice per participant, a CSV, and a red flag quoting the exact sentence with a notify-by date. Gemma 4 runs through Ollama on the worker's laptop; nothing is sent anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who pays.&lt;/strong&gt; Independent ABN support workers, at a proposed $12 a month or $99 a year. The platforms that already do NDIS invoicing are priced for agencies: &lt;a href="https://shiftcare.com/pricing" rel="noopener noreferrer"&gt;ShiftCare&lt;/a&gt; charges per user and says "All plans start at a minimum of five staff". Her alternative is a spreadsheet and an unbilled hour.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

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

&lt;p&gt;Live demo page (a saved real run, no install): &lt;a href="https://otto-deviant1904.github.io/arvo/" rel="noopener noreferrer"&gt;https://otto-deviant1904.github.io/arvo/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79imuuuvqabk2bbjnguv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79imuuuvqabk2bbjnguv.gif" alt="Screen recording: diary in, button pressed, red flag panel, shifts and invoice lines out" width="720" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;13.6 seconds from button to invoices (the wait is sped up; the button's counter is real).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The page is &lt;code&gt;docs/demo/run.json&lt;/code&gt; rendered: &lt;code&gt;gemma4:e4b&lt;/code&gt; on &lt;code&gt;docs/demo/diary.txt&lt;/code&gt;, 7 shifts, 10 invoice lines, 3 flags, 13.3 seconds on a laptop RTX 4050 (6 GB). Your own diary: &lt;code&gt;ollama pull gemma4:e4b&lt;/code&gt; (6.6 GB), clone, &lt;code&gt;python -m arvo serve&lt;/code&gt;. Python 3.12 standard library, nothing else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fko6ec3hgxiv4b2aurj84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fko6ec3hgxiv4b2aurj84.png" alt="The flag panel: the pantry note as an unauthorised environmental restraint, registered-provider notify-by date Tue 10 November 2026; PRN and bed rails flagged as authorised, record only" width="800" height="1073"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That week is all right, and I had seen it while building; the held-out numbers are below.&lt;/p&gt;

&lt;p&gt;Then I tried it on real handwriting. I copied one of the test weeks onto lined paper in my own writing, photographed it with my phone and ran it once. Gemma 4 did far worse than on the rendered test photos: 0 of 7 shifts fully right. It got every date and most times, but it read my "Jake" as "Jane" and my "Tom" as "7om", so 6 of 7 shifts matched nobody on the roster; my "km" reads as "pm", so every distance was lost; and "Thurs - nothing on" became an empty shift. What held: every one of those shifts came back with a check, the unmatched names were never priced, and the garage note was still flagged as seclusion. On my handwriting Arvo is a first draft for a human to fix, not an invoice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff4twc0gxznf9d2dkyp7t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff4twc0gxznf9d2dkyp7t.jpg" alt="Left, a phone photo of my handwritten diary for the week ending 27/12; right, what Gemma read: Jake read as Jane, six shifts not matched to the roster, every one with a check warning" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And from voice memos recorded in the car. Gemma 4 hears the audio itself, no separate speech model. Here it read "half 11" as 10:30, and the check after the fix put it back to 11:30 and told the worker why.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjjo8ogw3y35hxrsf9a3j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjjo8ogw3y35hxrsf9a3j.jpg" alt="What Gemma heard from three voice memos, and the shifts it read, with a check: end set to 11:30 from 'morning 8 till half 11' (model read 10:30)" width="792" height="910"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Otto-Deviant1904" rel="noopener noreferrer"&gt;
        Otto-Deviant1904
      &lt;/a&gt; / &lt;a href="https://github.com/Otto-Deviant1904/arvo" rel="noopener noreferrer"&gt;
        arvo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Offline NDIS shift diary -&amp;gt; invoice lines + restrictive-practice flags, on local Gemma 4. Model reads, code decides.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Arvo&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Sunday-night invoicing for NDIS support workers, on a laptop, with nothing sent anywhere.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An independent (ABN) NDIS support worker in Victoria types, photographs or dictates their week of shifts the way they actually write them:&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;Tues (Cup day!!) - Jake 3 till 8:30ish. Footy training then tea at his place
Locked the pantry so he would stop snacking before dinner, he was pretty upset about it.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Arvo gives back:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Invoice lines a plan manager will accept.&lt;/strong&gt; Support item number, date, hours, rate and total for every shift, priced by code against the official NDIS Pricing Schedule 2026-27 and the Victorian public-holiday calendar. That shift is on Melbourne Cup Day, so the whole 5.5 hours goes on &lt;code&gt;04_102_0125_6_1&lt;/code&gt; at $163.46/h. The same shift on an ordinary Tuesday crosses 8pm, so all of it is claimed at the evening rate, not split (Schedule s2.4(c)).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A red flag for the sentence that&lt;/strong&gt;…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Otto-Deviant1904/arvo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;MIT. One shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;diary text ─┐
photo ──────┼─&amp;gt; Gemma 4 reads ─&amp;gt; shifts (who, date, start, end, km) ─&amp;gt; code checks fields against the diary
voice memo ─┘                                                                      │
        invoice lines, printable invoice, CSV &amp;lt;─ pricing from the official JSON &amp;lt;──┘
                                                 (band, Vic holiday, s2.4(c), item number)

each note ─&amp;gt; Gemma 4 reads ─&amp;gt; possible restrictive practice ─&amp;gt; quote must be in the note, or dropped ─&amp;gt; notify-by date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;arvo/extract.py&lt;/code&gt; and &lt;code&gt;arvo/rp.py&lt;/code&gt; make the two model calls; &lt;code&gt;pricing.py&lt;/code&gt;, &lt;code&gt;calendar_au.py&lt;/code&gt; and &lt;code&gt;prices.py&lt;/code&gt; are the rules, fed by &lt;code&gt;data/ndis_prices_2026_27.json&lt;/code&gt;, where every figure cites a document and page; &lt;code&gt;voice.py&lt;/code&gt; hands audio to Gemma 4 directly; &lt;code&gt;server.py&lt;/code&gt; plus one HTML file, on 127.0.0.1; &lt;code&gt;eval/&lt;/code&gt; holds every fixture, gold label and result, frozen under SHA-256 sums.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The model reads, code decides
&lt;/h3&gt;

&lt;p&gt;Gemma turns messy text into shifts and never picks a price, an item number or a deadline. For Nonna's Thursday evening its entire contribution is &lt;code&gt;{"participant_id": "nonna", "date": "2026-11-05", "start": "19:00", "end": "21:30"}&lt;/code&gt;. The band, the item number and the $202.68 are code reading the Schedule. The model never saw a dollar sign.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1sf3begsej4bx0ul7epa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1sf3begsej4bx0ul7epa.png" alt="What Gemma read from the demo diary, with the span each time came from, above the invoice lines code priced" width="800" height="1366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rule that matters most is s2.4(c) of the NDIS Pricing Schedule 2026-27 (p5): "Where a support a support crosses between different times of day or days of week and is delivered by a single worker, the higher of the amounts applies to the whole support." (The doubled words are in the official PDF.) In Arvo it is a test, not a prompt:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx61sbq2bagdki66xsw4e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx61sbq2bagdki66xsw4e.jpg" alt="A 3pm to 8:30pm shift bar fully coloured as evening rate, whole shift, with crossed-out scissors at 8pm: not split" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_crosses_8pm_whole_shift_at_evening_not_split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;roster&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;roster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priya&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;2026-10-14&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;15:00&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;20:30&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 5.5 x 81.07 = 445.885 -&amp;gt; ROUND_HALF_UP 445.89 (float round() would give 445.88)
&lt;/span&gt;    &lt;span class="nf"&gt;assert &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;item_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekday_evening&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mf"&gt;5.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;81.07&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;445.89&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Crosses 8pm: whole shift at Weekday Evening rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s2.4(c)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flags get the same split. Gemma must return its evidence as a sentence copied from the note, and &lt;code&gt;find_verbatim&lt;/code&gt; checks it is there; if not, the flag is dropped with a warning. A model that paraphrases is a model that can invent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synthetic data, held-out tests
&lt;/h3&gt;

&lt;p&gt;No real participant data anywhere. 24 template weeks come from &lt;code&gt;eval/gen_fixtures.py&lt;/code&gt;, gold from the generator, never a model. 8 "wild" weeks were written by another AI agent imitating tired people (run-on prose, unpunctuated dictation, strike-throughs, "odo 45213 to 45231"); a second model labelled them blind and agreed on 58 of 58 shifts. 42 restrictive-practice notes came from another agent; a test fails if any 5-word run of one appears in the prompt. Voice memos are Piper TTS. Prompts and code were tuned on 8 dev weeks only; everything else ran once on code frozen at tag &lt;code&gt;eval-freeze-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp6k3sfc8t15rq65qh0jn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp6k3sfc8t15rq65qh0jn.jpg" alt="Held-out score cards: test split 93%, wild diaries 76% against code-only 40%, restraints 20 of 22, photos 90%, voice 6 of 10" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Held-out set&lt;/th&gt;
&lt;th&gt;Arvo (Gemma 4 e4b reads, code prices)&lt;/th&gt;
&lt;th&gt;Baselines&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test split: 16 template weeks, 119 shifts&lt;/td&gt;
&lt;td&gt;93.3% shifts exact; 10/16 weeks right to the cent&lt;/td&gt;
&lt;td&gt;code-only parser 91.6%, 9/16; Gemma pricing the week itself 0/16, line recall 49.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wild diaries: 8 weeks, 58 shifts&lt;/td&gt;
&lt;td&gt;75.9% exact; $121 mean error per week; 2/8 to the cent&lt;/td&gt;
&lt;td&gt;code-only 39.7%, $1,207 per week, 0/8; Gemma alone $366 per week, 0/8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restrictive-practice notes: 42 (22 restraints, 20 innocent with trap words)&lt;/td&gt;
&lt;td&gt;detection F1 88.9% (precision 87.0%, recall 90.9%); 1/20 innocent flagged&lt;/td&gt;
&lt;td&gt;keyword list F1 45.7%, 5/20 flagged; &lt;code&gt;gemma4:e2b&lt;/code&gt; F1 41.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rendered photos of a paper diary: 4 weeks&lt;/td&gt;
&lt;td&gt;90.0% exact; 2/4 to the cent&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gemma4:e2b&lt;/code&gt; 76.7%, 1/4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;My own handwriting: 1 week, 7 shifts, one run&lt;/td&gt;
&lt;td&gt;0/7 exact; dates 7/7, start 6/7, end 5/7, names 1/7, distances lost; all 7 misses flagged&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice memos: 10, voice never used in development&lt;/td&gt;
&lt;td&gt;6/10 every field right; participant, date, start 10/10; word error rate 12.0%&lt;/td&gt;
&lt;td&gt;(memos I tuned on: 11/12)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first row embarrassed me: a hand-written parser with no model nearly matches Gemma on the template weeks (91.6% against 93.3%) with lower dollar error ($132 a week against $195). The templates were too regular to prove anything. The wild row is where the model earns its keep, and the notes are where size matters: e2b reads shifts almost as well as e4b (89.1% against 93.3%) and misses 16 of 22 restraints. Size buys judgement, not transcription.&lt;/p&gt;

&lt;h3&gt;
  
  
  My guardrails overruled right answers
&lt;/h3&gt;

&lt;p&gt;Arvo got 14 of 58 wild shifts wrong. Read one by one, 7 were my own code overwriting a value Gemma had right, and code invented 2 phantom shifts out of the worker's corrections (&lt;code&gt;~~12~~ 16km&lt;/code&gt; read as a 12-16 shift; &lt;code&gt;10-2. actually 10-1&lt;/code&gt; split in two). What it said, against what was true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tom 9 30 till 1 30&lt;/code&gt; (wild week 2, dictated). Gemma: 09:30-13:30, right. My parser took "9" and "1" as the hours and wrote 09:00-13:00 over it: &lt;code&gt;start set to 09:00 from '9 30 till 1 30' (model said 09:30)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;twenty two k&lt;/code&gt; (same week). Gemma: 22 km, right. Code found no "22" in the diary and zeroed it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;odo 61044 to 61058&lt;/code&gt; (week 7). Gemma: 10 km. True: 14. Code zeroed that too.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pri 9:30 to 12:15pm Sundy&lt;/code&gt; (test week 18). Gemma: Sunday 31 January, right. Code did not know "Sundy", fell back to the heading &lt;code&gt;29/1 - Priya&lt;/code&gt; and moved the shift to Friday, which changes the rate.&lt;/li&gt;
&lt;li&gt;"Gave Jake his usual night meds at 8 off the chart". Gemma: chemical restraint. True: innocent; "off the chart" means read off the medication chart.&lt;/li&gt;
&lt;li&gt;"I left her out on the back verandah with the screen door latched for an hour". Gemma: environmental restraint. True: a restraint, but seclusion.&lt;/li&gt;
&lt;li&gt;Same 42 notes, &lt;code&gt;gemma4:e2b&lt;/code&gt;: 6 of 22 restraints found, 16 missed.&lt;/li&gt;
&lt;li&gt;Held-out voice, end times spoken as "half 11", "half 4" and "half 5": heard right in the transcript, end time wrong on all three. A car-noise memo came out as "Massdenic Copper".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wacd540vxwcxrlwk0o8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wacd540vxwcxrlwk0o8.jpg" alt="Gemma, a small gem character, holds up 09:30 while a guardrail character holds a check sign instead of stamping over it; wild 76% to 95% after the fix, not held out" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The guardrails exist because a model can invent a time or a km figure; tuned on 8 template weeks, they were overconfident. The fix is a principle: code overrules the model only when its own reading is unambiguous and the model's value is impossible; otherwise the model's value stands and the disagreement becomes a warning the worker sees.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Frozen (held out)&lt;/th&gt;
&lt;th&gt;After the fix (not held out)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test split, exact shifts&lt;/td&gt;
&lt;td&gt;93.3%&lt;/td&gt;
&lt;td&gt;95.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test split, weeks right to the cent&lt;/td&gt;
&lt;td&gt;10/16&lt;/td&gt;
&lt;td&gt;11/16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wild, exact shifts&lt;/td&gt;
&lt;td&gt;75.9%&lt;/td&gt;
&lt;td&gt;94.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wild, weeks right to the cent&lt;/td&gt;
&lt;td&gt;2/8&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wild, mean $ error per week&lt;/td&gt;
&lt;td&gt;$121&lt;/td&gt;
&lt;td&gt;$68&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice memos with every field right&lt;/td&gt;
&lt;td&gt;6/10&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The right-hand column is not held out: I fixed the code while looking at those exact diaries, so the wild number is the one I trust least. Two things got worse, the cost of the rule (&lt;code&gt;eval/results/postfix/&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  What the check can't see
&lt;/h3&gt;

&lt;p&gt;The verbatim-quote check proves the sentence exists in the note, nothing more. It cannot tell that the screen door was seclusion, not environmental restraint, or that "off the chart" is no restraint at all. On the 42 held-out notes it dropped 0 flags: a floor against invention, not evidence of judgement. Pricing is the same: code prices to the cent, but only the shift it is given. Move Priya's Sunday to Friday and the arithmetic is perfect and the invoice is wrong. So the worker sees every shift Gemma read, with its span, before any invoice.&lt;/p&gt;

&lt;h3&gt;
  
  
  The document that did not exist
&lt;/h3&gt;

&lt;p&gt;I went looking for the 2026-27 "Pricing Arrangements and Price Limits", the PAPL, where the time-band and travel rules have always lived. There is none: for 2026-27 the NDIA published a prices-only Pricing Schedule, and ndis.gov.au still lists the 2025-26 PAPL v1.1 as current guidance. So the boundary rule comes from the Schedule (s2.4(c), p5), the km rules from the 2025-26 PAPL, and the JSON records the document and page for each figure. If a plan manager disputes a line, the argument is about a page, not about what a model was thinking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits.&lt;/strong&gt; Victoria, non-remote, standard-intensity self-care and community-access items only; 42 notes is a small set. The 5-business-day duty belongs to registered providers; an unregistered sole trader must not use regulated restrictive practices at all, and no official source gives her a deadline, so Arvo labels the date as the registered provider's and gives her the Commission's contact details. It flags; it does not decide. Not legal or compliance advice.&lt;/p&gt;

&lt;p&gt;Data and credits: NDIS Pricing Schedule 2026-27 and NDIS Commission material; Business Victoria public holidays; Gemma 4 via Ollama; Piper voices for the synthetic memos; Comic Neue for the synthetic diary photos.&lt;/p&gt;

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

&lt;p&gt;Shift notes are health information, "sensitive information" under &lt;a href="https://www.legislation.gov.au/C2004A03712/latest/text" rel="noopener noreferrer"&gt;s 6 of the Privacy Act&lt;/a&gt;, and the &lt;a href="https://www.oaic.gov.au/privacy/privacy-guidance-for-organisations-and-government-agencies/guidance-on-privacy-and-the-use-of-commercially-available-ai-products" rel="noopener noreferrer"&gt;OAIC recommends&lt;/a&gt; not entering personal information into publicly available generative AI tools. A worker who pastes "Gave PRN lorazepam 0.5mg" and a name into a hosted chatbot has a problem before she has an invoice. With open weights the model runs on her laptop, the server binds to 127.0.0.1, and nothing leaves. That is the product, not a feature of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmzjperboxq0phsjsfgru.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmzjperboxq0phsjsfgru.jpg" alt="Mel in her car in a dark car park, laptop on the wheel, no signal and a crossed-out cloud: nothing leaves the laptop, $0 a week, 6 GB laptop GPU" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where a closed model would win: a frontier model would likely read "9 30 till 1 30" and "half 4" better than a 4B model, and might tell seclusion from environmental restraint. It would still need the same pricing code, because right-to-the-cent is a rules job, and it would send disability health notes to someone else's server. That is the trade; for this job I take the local side.&lt;/p&gt;

&lt;p&gt;Open also means offline, where the memo gets recorded; $0 a week in tokens behind a $12 subscription; and a model swap that is one flag, which is how I learned e2b reads nearly as well and judges much worse.&lt;/p&gt;

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

&lt;p&gt;I built Arvo with Claude Code as my coding agent. It wrote most of the code and the first draft of this post; I set the brief, directed the video, wrote and photographed the handwriting test, and edited the write-up. The decision log is in &lt;code&gt;JOURNEY.md&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 4 (&lt;code&gt;gemma4:e4b&lt;/code&gt; and &lt;code&gt;gemma4:e2b&lt;/code&gt;, open weights) through Ollama on the worker's machine, for typed text, photos of a paper diary (vision) and voice memos (audio in, no separate speech model). The held-out eval above is the case for it, including where it loses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of GitHub Copilot.&lt;/strong&gt; The category text says "automate your project with GitHub Actions". &lt;code&gt;ci.yml&lt;/code&gt; runs the tests on every push; &lt;code&gt;gemma-smoke.yml&lt;/code&gt; installs Ollama on a CPU-only runner, pulls &lt;code&gt;gemma4:e2b&lt;/code&gt; and runs the whole pipeline and a one-week eval with no GPU and no key: &lt;a href="https://github.com/Otto-Deviant1904/arvo/actions/runs/38347559687" rel="noopener noreferrer"&gt;run 38347559687&lt;/a&gt;. I did not use the Copilot coding agent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flsr076w02522k8jl47ph.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flsr076w02522k8jl47ph.jpg" alt="GitHub Actions: gemma smoke test run #2, status Success, total duration 10m 27s" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Prove It Outside</title>
      <dc:creator>Harsh Satyajit Thakur</dc:creator>
      <pubDate>Mon, 12 Oct 2026 07:00:04 +0000</pubDate>
      <link>https://dev.to/ottodeviant1904/prove-it-outside-281a</link>
      <guid>https://dev.to/ottodeviant1904/prove-it-outside-281a</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;AI models will tell you things about places they have never been. The only way to check one of those claims is to go there. &lt;strong&gt;Prove It Outside&lt;/strong&gt; makes that check the whole point.&lt;/p&gt;

&lt;p&gt;An open-weight model must commit to a falsifiable prediction about the physical world: &lt;em&gt;which of two places is quieter?&lt;/em&gt; The prediction and the model's confidence are locked first. Then someone goes out, records sound at both places, and the tool scores each model with a Brier score. If the measurements are too noisy to tell the two places apart, the tool says &lt;strong&gt;inconclusive&lt;/strong&gt; and refuses to score anyone.&lt;/p&gt;

&lt;p&gt;It is for anyone curious whether an AI's confident guess about a place survives a microphone, and it works with any open model you can ask a question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I tested for this entry:&lt;/strong&gt; a street in the city versus a McDonald's restaurant. I recorded the sound with my phone's voice recorder, on the street near my house and the nearest Maccas around 3 PM in the evening, and analyzed the files with the tool. The tool also has a live mode that reads the phone microphone directly; I did not have time for a live session, so this entry uses recordings. The tool labels this on screen, in the export and in the score.&lt;/p&gt;

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

&lt;p&gt;Try it: &lt;a href="https://otto-deviant1904.github.io/prove-it-outside/" rel="noopener noreferrer"&gt;https://otto-deviant1904.github.io/prove-it-outside/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter two places, add each model's prediction, lock them, then measure live with your phone's microphone or analyze recordings. Nothing is uploaded; everything runs in your browser.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/Otto-Deviant1904/prove-it-outside" rel="noopener noreferrer"&gt;https://github.com/Otto-Deviant1904/prove-it-outside&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index.html&lt;/code&gt;: the whole tool, one file, no build step, no network calls.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run_models.py&lt;/code&gt;: asks local Ollama models the same prompt several times and prints predictions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;analyze_recordings.py&lt;/code&gt;: offline cross-check of the recording analysis.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;predictions.json&lt;/code&gt; and &lt;code&gt;predictions_command.txt&lt;/code&gt;: the exact predictions and the command that made them, committed in git (&lt;code&gt;bc63e42&lt;/code&gt;) before the recordings were analyzed with the tool.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open models:&lt;/strong&gt; &lt;code&gt;qwen3:4b&lt;/code&gt; and &lt;code&gt;llama3.2:3b&lt;/code&gt;, both open-weight, run locally with Ollama. Each got the same one-line description of each place (A: "A street in the city." B: "A McDonald's restaurant."), seven samples at temperature 0.7. A model's pick is its majority vote; its confidence is the mean implied probability that the pick is right across all samples, so dissenting samples pull it down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measurement:&lt;/strong&gt; the level of a run is the energy-averaged level in dBFS, which matches the question the models were asked ("lower average sound level"). Live mode reads the microphone for 30 s with echo cancellation, noise suppression and auto-gain off. Recording mode decodes a file and analyzes 30 s or 15 s segments; the source label is required and saved with the results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoring:&lt;/strong&gt; each place needs at least two runs. If the spread between runs at one place is as large as the gap between places, the verdict is inconclusive. Otherwise each model gets a Brier score, &lt;code&gt;(confidence - correct)^2&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Predictions, locked before the analysis&lt;/strong&gt; (&lt;code&gt;predictions.json&lt;/code&gt;, 7 samples each): &lt;code&gt;qwen3:4b&lt;/code&gt; picked the McDonald's as quieter in 7 of 7 samples (confidence 0.83). &lt;code&gt;llama3.2:3b&lt;/code&gt; picked the street in 5 of 7 (confidence 0.66). The two models disagree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest notes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My first predictions were for other place descriptions that did not match the recordings I ended up with. I discarded them and reran with matching descriptions. The earlier ones are in &lt;code&gt;history/&lt;/code&gt; and in the git history.&lt;/li&gt;
&lt;li&gt;I had looked at the rough loudness of the recordings before the models were rerun. The models never see audio and I did not change the prompt after seeing it, but this is not blind.&lt;/li&gt;
&lt;li&gt;Each recording is about 31 seconds, so I used 15 second segments, two per file. Segments of one recording are not independent visits, so the repeatability check is weaker than with separate visits.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;A claim about the physical world is only worth something if someone else can test it. With open-weight models anyone can run the same prompt on their own machine, see the raw samples, and go outside and check. That mattered here: on an earlier draft of the place descriptions, &lt;code&gt;llama3.2:3b&lt;/code&gt; picked one place in a first run and the other in a second run with more samples. A hosted model behind an API would hide that instability. Here I could see every sample, so the tool reports a confidence that reflects the disagreement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One uncalibrated phone microphone, relative dBFS, not dB(A). Recordings made with different gain or equipment are not truly comparable.&lt;/li&gt;
&lt;li&gt;Wind, traffic and time of day are not controlled.&lt;/li&gt;
&lt;li&gt;One experiment is one data point. Two models and one pair of places cannot say which model is better.&lt;/li&gt;
&lt;/ul&gt;

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