<?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: Vivek Shah</title>
    <description>The latest articles on DEV Community by Vivek Shah (@viveknshah).</description>
    <link>https://dev.to/viveknshah</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%2F735412%2Fd1fbaadb-44d3-40d1-b54d-aac9b249221e.jpeg</url>
      <title>DEV Community: Vivek Shah</title>
      <link>https://dev.to/viveknshah</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/viveknshah"/>
    <language>en</language>
    <item>
      <title>The tea stall granny next to my office runs on UPI, so I built a payment announcer that speaks Hindi</title>
      <dc:creator>Vivek Shah</dc:creator>
      <pubDate>Tue, 20 Oct 2026 11:08:03 +0000</pubDate>
      <link>https://dev.to/viveknshah/the-tea-stall-granny-next-to-my-office-runs-on-upi-so-i-built-a-payment-announcer-that-speaks-hindi-4fm0</link>
      <guid>https://dev.to/viveknshah/the-tea-stall-granny-next-to-my-office-runs-on-upi-so-i-built-a-payment-announcer-that-speaks-hindi-4fm0</guid>
      <description>&lt;p&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission 🦥&lt;/p&gt;

&lt;p&gt;This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/p&gt;

&lt;h2&gt;
  
  
  Amma's rush hour
&lt;/h2&gt;

&lt;p&gt;Next to my office there is a small tea stall. We call the woman who runs it Amma (Mother). By my rough count, on a normal day she sells about 100 cups of tea at ₹15 a glass, about 150 pieces of murukku at ₹5, about 100 bananas at ₹10, and about 75 samosas at ₹15 each - somewhere around ₹4,400 a day, across a counter the size of a desk.&lt;/p&gt;

&lt;p&gt;Almost all of it is UPI now - about 95%. She prefers it that way: few customers carry exact change these days, and she does not keep change to give back.&lt;/p&gt;

&lt;p&gt;The hard part is the checking. She is not very hands-on with her phone, and when you are serving tea and snacks all day, your hands are not clean. Every time a payment should have arrived, she has to stop, wipe her hands, pick up the phone, and read the SMS list to see whether the money actually came - and whether it came in the order she expects, so she can match payments to the people standing in front of her. At rush hour there are five to ten people at the counter at once. That is when it breaks.&lt;/p&gt;

&lt;p&gt;Shops solve this with a soundbox, the little speaker that announces every payment out loud. A commercial one costs more than a stall this size can justify. So she wipes her hands and checks the phone.&lt;/p&gt;

&lt;p&gt;This week's theme is Slow and Steady: build something that does the work so you don't have to. So I built the part she actually needs.&lt;/p&gt;

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

&lt;p&gt;A payment announcer that started on a laptop (as a POC) and now also runs as an Android app, speaking Hindi:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It takes payment SMS as they arrive and parses them with strict rules - amount, sender, reference, time. Only a genuine success credit counts. Refunds, failed transactions, payment requests, outgoing debits and OTPs are not counted as sales.&lt;/li&gt;
&lt;li&gt;The moment a real payment lands, it says so out loud in Hindi. A ₹250 credit is announced as "₹250 प्राप्त हुए" ("₹250 received"), and when the SMS names a payer it says "Suresh से ₹5,000 प्राप्त हुए". The voice is Piper, an open-source voice that runs offline. She never touches the phone.&lt;/li&gt;
&lt;li&gt;At closing time, one button produces a spoken end-of-day summary in Hindi plus a bills and due-dates list. The summary is written by Gemma 3 (4B) running locally through Ollama. If the model is not running, a plain template steps in, so the demo can never die on stage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Judges cannot stand at her stall, so the demo looks like her phone: a phone-sized screen in the browser where SMS drop in like notifications, the payment shows as a big card, and one button feeds in a fake SMS - including the nasty ones. A spoofed sender, a refund, an OTP message: you can watch each one get rejected.&lt;/p&gt;

&lt;p&gt;The safeguards are the point, not a footnote:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only senders on her allowlist are even read. An unknown sender is not counted and the app says why.&lt;/li&gt;
&lt;li&gt;OTPs, links and bank balances are never spoken or shown. The balance in an SMS is parsed for the record and kept out of everything else.&lt;/li&gt;
&lt;li&gt;Repeat SMS are deduplicated by reference number. When a bank format has no reference, a match on amount plus time is flagged for review instead of announced as fact.&lt;/li&gt;
&lt;li&gt;Large amounts (₹2,000 and up) are announced with "verify in your bank app".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real numbers from my test runs: the demo feeds 16 sample SMS through the parser. 8 are counted, 4 are rejected outright (a payment request, a debit, a failed payment and an OTP), 1 refund is recorded separately, 1 spoofed sender is flagged for review, and 2 duplicates are flagged without being counted a second time. The counted total is ₹5,716, and the ₹5,000 payment gets the large-amount warning. All 41 Python unit tests pass; the Android port now adds 71 Kotlin tests.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Video walkthrough: &lt;a href="https://youtu.be/rqhXEc9zLs8" rel="noopener noreferrer"&gt;https://youtu.be/rqhXEc9zLs8&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Screenshots: the phone frame with a ₹5,000 payment announced, and a failed payment shown as not counted.&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%2Foj0wia7ib5yeqim42n0e.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%2Foj0wia7ib5yeqim42n0e.png" alt="Phone frame with a large payment announced" width="760" height="1876"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv4v5o8bvbdrldktlflvw.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%2Fv4v5o8bvbdrldktlflvw.png" alt="A failed payment is shown as not counted" width="760" height="1846"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Run it yourself:&lt;/p&gt;

&lt;p&gt;git clone &lt;a href="https://github.com/viveknshah/payment-announcer" rel="noopener noreferrer"&gt;https://github.com/viveknshah/payment-announcer&lt;/a&gt;&lt;br&gt;
cd payment-announcer&lt;br&gt;
python app.py demo          # 16 sample SMS, 8 counted, spoken aloud&lt;br&gt;
python app.py serve         # phone-frame UI on &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt;&lt;br&gt;
python test_suite.py        # 41 unit tests&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Python 3.10 or newer. Nothing to pip install.&lt;/p&gt;

&lt;p&gt;No hosted demo, on purpose: it is built to run on one machine with nothing sent anywhere, so the demo is the repo plus the video.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/viveknshah/payment-announcer" rel="noopener noreferrer"&gt;https://github.com/viveknshah/payment-announcer&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Plain Python, boring on purpose. The standard library only, no pip install. Piper (voice) and Ollama (summary) are optional extras.&lt;/p&gt;

&lt;p&gt;The parser is rules, not a model. Bank SMS wording changes often enough that I trust code I can read over a model I cannot, and amounts are stored in paise as integers, so ₹15.00 never becomes 14.999. The rules follow two real Bank of Baroda SMS layouts - one UPI credit alert with a 12-digit reference, one plain credit with only an amount and a date. Every name, account number, reference and amount in the repo is fictional. No real message text is in it.&lt;/p&gt;

&lt;p&gt;The voice is Piper with the hi_IN-pratham-medium voice: open-source and offline, with the operating system voice or plain text as fallback. The voice files are not in the repo (about 60 MB).&lt;/p&gt;

&lt;p&gt;The end-of-day summary comes from a local Gemma model (Gemma 3, 4B) through Ollama's API. The model only gets the day's parsed numbers, never the raw SMS text. The division of labour is the same rule I used in my last project:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measured in code (facts)&lt;/th&gt;
&lt;th&gt;Judged by the model (words)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Today's total and payment count&lt;/td&gt;
&lt;td&gt;How the summary is phrased&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate and uncertain matches&lt;/td&gt;
&lt;td&gt;Which pending items to mention first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bill due dates from bills.json&lt;/td&gt;
&lt;td&gt;The reminder wording&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Small models drift on money, so the summary cannot lie about it. The model's text is spoken only if every rupee amount matches the ledger, the exact total appears in digits, and no English wrapper text survives. Otherwise the template is spoken. The bills line always comes from bills.json, never from the model.&lt;/p&gt;

&lt;p&gt;Languages are JSON packs. Hindi is complete. English and Kannada are stubs for now, and they run on templates so the demo still works.&lt;/p&gt;

&lt;p&gt;Python: 41 unit tests, all passing. Android v0.2: 71 Kotlin tests, all passing.&lt;/p&gt;

&lt;p&gt;Prior art, honestly: UPIPaymentAlert already parses Indian payment SMS and speaks them, and every payment company sells a soundbox. What this adds is the part after the beep: a summary that closes her day with the bills list, and language packs, so the next stall over can have Kannada without asking anyone's permission.&lt;/p&gt;

&lt;p&gt;Others are working the same street this week, which is a good sign: &lt;a href="https://dev.to/fahadyy/gazette-any-payment-notification-becomes-a-ledger-entirely-on-your-phone-33eb"&gt;Gazette&lt;/a&gt; turns any notification into a ledger with Gemma 1B on the phone, &lt;a href="https://dev.to/coffeeandcommits/kharcha-a-4b-model-that-reads-indian-bank-sms-so-the-money-stays-on-your-laptop-2n8j"&gt;Kharcha&lt;/a&gt; reads Indian bank SMS with a 4B model on a laptop, and &lt;a href="https://dev.to/aniruddhaadak/i-built-khata-for-my-sister-the-household-ledger-that-reads-your-payment-messages-22jc"&gt;khata&lt;/a&gt; keeps a household ledger. None of them speaks each payment out loud in Hindi or closes the day with the bills list; that is the gap this fills.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Her SMS are her income. The parser, the voice and the model are all designed to run on the device, and nothing is sent anywhere.&lt;/li&gt;
&lt;li&gt;The cost has to be zero. A stall that sells ₹15 tea cannot pay a monthly fee to hear its own money arrive. Open weights and an open-source voice make the running cost nothing.&lt;/li&gt;
&lt;li&gt;Nothing is locked. The model is one line in config.json, the bank formats are readable rules, and a new language is a JSON file. When a better open model fits the laptop, the upgrade is changing a string.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The full build session, including the failed Gemma 4 load and the guard that catches a wrong total:&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;Chai Stall Pay Announcer: Hindi voice, a 4B Gemma summary, and a total that can't lie&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;Finish the Week 3 Hacktoberfest project "Chai Stall Pay Announcer" (payment-announcer) and get the web demo running with Hindi voice and Gemma summaries for a clean recording. Fix defects as you go.&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;Unpacked the 16-file, stdlib-only app and verified it against its own README: 34 tests green, demo counted 8 payments for Rs 5,716, excluded the decoys, kept the refund separate, flagged the spoofed sender and the Rs 5,000 payment as LARGE.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;python test_suite.py; python app.py demo --no-speak&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;$ python test_suite.py; python app.py demo --no-speak&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;Ran 34 tests in 0.29s OK
8 CREDITED / 4 EXCLUDED / 1 REFUND / 1 REVIEW / 2 DUPLICATE
total 5716&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;First real defect: on Windows the console crashed with UnicodeEncodeError (cp1252) the moment a Devanagari announcement was printed. Added force_utf8_stdio() in app.py and server.py, then verified console, redirected output and the HTTP path. Also fixed stale UI state: the summary box now clears when the ledger changes, the last-payment card hides on undo, and /api/undo resets last_record and announces undo_done.&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.py: add force_utf8_stdio() and call it from main(); server.py: call it fr...&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"&gt;&lt;code&gt;app.py: add force_utf8_stdio() and call it from main(); server.py: call it from serve()&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;Devanagari now prints correctly to console, pipes and the web UI&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;Local model: gemma4:e4b did not exist (404 from /api/show), so I pinned the installed gemma4:latest. Gemma 4 is a thinking model, and without "think": false it burned the whole token budget on hidden reasoning and returned an empty response, which silently fell back to the template.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;run all the tests so it can be recorded for the demo at ease&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;Made the engine reporting honest instead of optimistic: ollama_available() now checks that the configured model actually appears in /api/tags, so the footer only says "Gemma via Ollama" when it is true. Tightened the summary too: English/meta lines stripped, rupee amounts checked against the ledger, model skipped when there are no payments. Tests went 34 to 38.&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;Windows only ships en-US SAPI voices, so Hindi was garbled. Installed Piper with hi_IN-pratham-medium.onnx and verified the app path returns mode "piper" (1.6 s of audio, real-time factor about 3 on this CPU).&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;piper/piper.exe --model voices/hi_IN-pratham-medium.onnx --output_file test.wav&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;$ piper/piper.exe --model voices/hi_IN-pratham-medium.onnx --output_file test.wav&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;mode: piper, 1.6s wav&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;the app is not keeping up with the voice commands ... I have stopped the recording as the demo got messed up ... clear out the screen&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Found the cause: every feed request spawned its own speech thread, so five quick clicks produced five voices at once and the demo fell behind. Replaced it with one narrator thread and a maxsize-1 queue where the newest line wins, plus a real speaking flag for the UI dot. Rapid clicking now drops stale announcements instead of queueing them.&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;server.py: DemoState speech queue (maxsize=1), _speech_worker, RLock, seq-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"&gt;&lt;code&gt;server.py: DemoState speech queue (maxsize=1), _speech_worker, RLock, seq-based pending state&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;One voice at a time; stale lines dropped; speaking reflects actual playback&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;when do I hit the record button?&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;While warming the model I hit a hard failure: gemma4:latest (7.5B) needs a 6,028,115,264-byte CUDA_Host buffer and this machine had ~5.5 GB free of 15.7 GB. Ollama returned HTTP 500 after hanging for 190 s, twice. Not something to gamble on during a take.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;curl -X POST localhost:11434/api/generate (gemma4:latest, num_predict 2)&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;$ curl -X POST localhost:11434/api/generate (gemma4:latest, num_predict 2)&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;500 after 190.2s: llama-server reported out-of-memory during startup: failed to allocate buffer of size 6028115264&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;Switched the demo to gemma3:4b (4.3B, 2.9 GB) with num_gpu 0 and keep_alive 30m so Ollama does not unload between takes. Cold load is 1-3 minutes; a summary takes about 40 s on CPU, which the UI covers with a waiting state. Updated config, README and the honesty notes: it is Gemma 3 4B, not Gemma 4 7B.&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;The smaller model exposed a bug worth more than the feature: it once said "पाँच हजार सात सौ सत्रह" (5,717) for a 5,716 total, which no digit check could catch, and once ignored the facts entirely and wrote a greeting. Fix: the answer must contain the exact total in digits, plus a one-line Hindi example in the prompt. Otherwise the deterministic template is spoken. Tests now 41.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;python app.py demo --no-speak (after prompt + guard change)&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;$ python app.py demo --no-speak (after prompt + guard change)&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;summary engine: gemma
आज 8 भुगतान मिले, कुल ₹5,716। दस बजे का समय सबसे व्यस्त था, अभी एक संदेश जाँचने बाकी।&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;check into github repository under the project account&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;Created and pushed the repository (public, 2 commits, 18 files). .gitignore excludes the 60 MB of Piper binaries and voices, runtime ledger, logs and &lt;strong&gt;pycache&lt;/strong&gt;, so the repo is source-only. Added a rendered README.md with the honest limits, and set topics. No credentials are stored in .git/config or in this transcript.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;git init -b main; git add -A; git commit; git push origin main&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;$ git init -b main; git add -A; git commit; git push origin main&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;new branch main -&amp;gt; main
8056691 Add rendered README for the GitHub landing page&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I am done recording. you can close gemma&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;Unloaded the model with keep_alive 0 (api/ps empty), stopped the demo server so port 8000 is closed, and confirmed the repo is clean at 8056691. Outstanding security item: rotate the credentials used during the session.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;curl -X POST localhost:11434/api/generate {model: gemma3:4b, keep_alive: 0}&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;$ curl -X POST localhost:11434/api/generate {model: gemma3:4b, keep_alive: 0}&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;unload: 200 in 0.006s
{"models":[]}&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;has the session made it to devrelay?&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;No — the session list showed only older, unrelated sessions. Nothing from this build existed on DEV, so this transcript is the first submission, created unpublished so it can be reviewed before sharing.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;devrelay sessions list&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;$ devrelay sessions list&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;Older sessions were present, but none from this build.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;
        19 of 19 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;She serves tea; the phone does the checking. No wiping hands, no scrolling through SMS with five people waiting, no mental arithmetic at closing time. The announcements just happen, and at the end of the day one button sums it up and lists the bills that are due.&lt;/p&gt;

&lt;p&gt;The soundbox companies built this for shops with margins. This is the sloth version for a stall that sells ₹5 murukku: it does the work, she does not have to, and it costs her nothing to keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update: the Android app is built (v0.2)
&lt;/h2&gt;

&lt;p&gt;The Week 3 entry was the laptop proof. Since publishing it, I finished the native Android port I said was next.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The parser and ledger are pure-JVM Kotlin modules, with a field-by-field parity test against the Python parser on the same 16-message corpus.&lt;/li&gt;
&lt;li&gt;Room stores the ledger. Daily stats, undo, and a 120-second cross-source dedupe window are in place, so a bank SMS and the matching GPay/PhonePe notification are counted once.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;NotificationListenerService&lt;/code&gt; handles payment-app notifications. The sideloaded &lt;code&gt;foss&lt;/code&gt; flavor adds &lt;code&gt;RECEIVE_SMS&lt;/code&gt;; the &lt;code&gt;play&lt;/code&gt; flavor stays notification-only.&lt;/li&gt;
&lt;li&gt;System Hindi TTS uses a single latest-wins queue, so announcements cannot overlap. If Hindi is missing, the app reports it instead of silently falling back to English.&lt;/li&gt;
&lt;li&gt;The app includes notification-access and SMS onboarding, a foreground watchdog, a boot receiver, a spoken day summary, and undo.&lt;/li&gt;
&lt;li&gt;71 JVM/Robolectric tests pass. The release builds are signed and neither APK requests the &lt;code&gt;INTERNET&lt;/code&gt; permission.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What is not done: I have only run it on an emulator. There, the SMS → ledger → TTS path worked for a bank SMS, and the TTS engine requested audio focus. I have not listened to the Hindi voice on a real phone, tested a real bank's live notification stream, or done a reboot/OEM battery soak. The notification listener and SMS receiver are a working implementation, not a certified one. A physical-phone pass is the next gate.&lt;/p&gt;

&lt;p&gt;One intake detail worth calling out: Google Messages often shows a bank display name such as "Bank of Baroda" rather than the SMS sender token. The app maps known display names to the parser's token, and ignores unrecognised personal conversations instead of turning them into review rows.&lt;/p&gt;

&lt;p&gt;Code and install notes: &lt;a href="https://github.com/viveknshah/payment-announcer/blob/main/android/README.md" rel="noopener noreferrer"&gt;android/README.md&lt;/a&gt; and &lt;a href="https://github.com/viveknshah/payment-announcer/blob/main/docs/install.md" rel="noopener noreferrer"&gt;docs/install.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's honestly rough
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This started as a laptop demo. The Android v0.2 app now exists (see the update above), but it has only been run on an emulator. I have not tested a real phone, a real bank's live notification stream, reboot behavior on an OEM build, or the Hindi voice through a phone speaker. The web page is still a simulator; the laptop's real input path is &lt;code&gt;python app.py watch&lt;/code&gt; fed by an SMS forwarder.&lt;/li&gt;
&lt;li&gt;The parser is tested on fictional fixtures plus two Bank of Baroda layouts with fake values. Other banks word their SMS differently and will need their own rules.&lt;/li&gt;
&lt;li&gt;A bigger model did not fit. gemma4:latest (7.5B) needs about 6 GB free and failed with an out-of-memory error on my 16 GB laptop, so I run gemma3:4b on CPU. A summary takes about 40 seconds, and the screen shows a waiting state.&lt;/li&gt;
&lt;li&gt;Gemma 3 4B drifted on money in my testing: once it said "5,717" for a 5,716 total, and once it ignored the facts entirely. That is why the guard above exists.&lt;/li&gt;
&lt;li&gt;Rapid taps used to start one speech thread per message, so the voices overlapped and the app fell behind. The demo now has a single narrator queue where only the newest line survives.&lt;/li&gt;
&lt;li&gt;Only Hindi has a full voice pack. English and Kannada are template stubs.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Entering the overall judging and Best Use of Gemma: a local Gemma model (Gemma 3, 4B, through Ollama) writes the end-of-day summary, checked by code against the ledger.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>I am competing in a Table Topics contest, so I built myself a coach that scores like a real judge</title>
      <dc:creator>Vivek Shah</dc:creator>
      <pubDate>Fri, 16 Oct 2026 18:37:01 +0000</pubDate>
      <link>https://dev.to/viveknshah/i-am-competing-in-a-table-topics-contest-so-i-built-myself-a-coach-that-scores-like-a-real-judge-594i</link>
      <guid>https://dev.to/viveknshah/i-am-competing-in-a-table-topics-contest-so-i-built-myself-a-coach-that-scores-like-a-real-judge-594i</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;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Table Topics is the impromptu round of Toastmasters: you get a prompt you've never seen, and you stand up and speak for one to two minutes. No notes, no prep.&lt;/p&gt;

&lt;p&gt;I'm competing soon, and my weakness is exactly that - I ramble, I overrun the time, and I can't hear my own filler words. A human coach costs money per session and can't sit with me at 11pm the week before a contest. The feedback tools that exist want my audio on their servers and charge per minute, which is exactly wrong for daily drills.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Confident Speaker Coach&lt;/strong&gt;: open a link, get a topic, speak, and get scored on the actual Toastmasters ballot - Content 55, Delivery 30, Language 15 - plus one audience-member's honest reaction, the single biggest fix, and one concrete upgrade. The coach reads the verdict back to me out loud.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Video walkthrough: &lt;a href="https://youtu.be/bNixwmELWNo" rel="noopener noreferrer"&gt;https://youtu.be/bNixwmELWNo&lt;/a&gt; - one full round: the topic 'What is overrated, and what is underrated?', a real 2:01 speech, and the coach's scored verdict.&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/viveknshah/confident-speaker-coach" rel="noopener noreferrer"&gt;https://github.com/viveknshah/confident-speaker-coach&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No hosted demo, on purpose: the whole point is that it runs on your machine, so the demo is the repo plus the video. It takes one command:&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/viveknshah/confident-speaker-coach
&lt;span class="nb"&gt;cd &lt;/span&gt;confident-speaker-coach
docker compose up        &lt;span class="c"&gt;# pulls qwen2.5:3b-instruct (~1.9 GB) on first run&lt;/span&gt;
&lt;span class="c"&gt;# open http://localhost:3000 - Chrome or Edge for the mic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No Docker? &lt;code&gt;ollama pull qwen2.5:3b-instruct &amp;amp;&amp;amp; node server.js&lt;/code&gt; - Node 18+, zero npm dependencies. Swap the model with &lt;code&gt;OLLAMA_MODEL&lt;/code&gt; (I used gemma3:4b for a while). Once the model is pulled, the coaching loop runs locally - note the typed fallback is the fully-offline input path; Chrome's live transcription uses the browser's speech service.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Gives you a Table Topics prompt.&lt;/li&gt;
&lt;li&gt;Runs a contest timer with the official 1:00-2:30 window, and transcribes your speech live in the browser (Web Speech API; typed fallback in other browsers).&lt;/li&gt;
&lt;li&gt;Scores the speech: the app measures length, words per minute and filler words in code - those are facts, and I don't trust a model to guess them. A local open-weight model judges the content, structure and language, and its ballot scores get clamped to the real 55/30/15 weights.&lt;/li&gt;
&lt;li&gt;Tells you the verdict the way my club's coaches do: length first, then the ballot, then how it landed for an audience member, then the one biggest fix and one concrete upgrade for your next attempt.&lt;/li&gt;
&lt;li&gt;Speaks a 3-sentence verdict back out loud, so practice feels like a person is in the room. It uses the browser's built-in voice by default; set an ElevenLabs key and it upgrades to a natural voice, with the key kept server-side.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  The open-source AI at the core
&lt;/h2&gt;

&lt;p&gt;The coach is an open-weight model - &lt;strong&gt;qwen2.5:3b-instruct&lt;/strong&gt; - running in &lt;strong&gt;Ollama&lt;/strong&gt; on the server. Remove it and the product is a kitchen timer. Every part of the feedback that requires judgment (ballot scoring, the audience reaction, the fix, the rewritten beat) comes from that model, constrained to strict JSON so the UI can render it directly.&lt;/p&gt;

&lt;p&gt;Why a 3B model: it's small enough for a cheap 4 GB CPU box, and it's reliable at structured output for its size, which matters because the UI renders the fields it returns. Don't like my choice? &lt;code&gt;OLLAMA_MODEL=gemma3:4b docker compose up&lt;/code&gt; - the whole coaching brain is one environment variable.&lt;/p&gt;

&lt;p&gt;The division of labour is deliberate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measured in code (facts)&lt;/th&gt;
&lt;th&gt;Judged by the model (opinion)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Speech length vs the 1:00-2:30 contest rule&lt;/td&gt;
&lt;td&gt;Content and structure quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Words per minute&lt;/td&gt;
&lt;td&gt;Language and word choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filler-word counts&lt;/td&gt;
&lt;td&gt;Audience reaction, biggest fix, one upgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Small models are generous graders. Mine can't inflate the numbers, because the numbers come from a stopwatch and a word counter.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why open innovation is what makes this work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost is why the business works.&lt;/strong&gt; Unlimited practice is the product. A club of 30 members running 10 drills a week each is thousands of model calls a month - free on open weights, a growing invoice on a per-token API. The subscription math only closes at zero marginal cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy is a feature, not a compliance line.&lt;/strong&gt; Table Topics speeches are personal - people talk about their jobs, their families, their failures. On a self-hosted club instance, the transcripts and scores never leave infrastructure the club controls. (One honest caveat: if you use the mic, Chrome's live transcription uses the browser's speech service - the typed fallback keeps everything on the box.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No lock-in.&lt;/strong&gt; The model, the prompt and the scoring rules are all in the repo. A club can fine-tune for its own contest format, swap models as better open weights land, and never renegotiate with me or anyone else.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Who pays
&lt;/h2&gt;

&lt;p&gt;Toastmasters has over 265,000 members across 13,800+ clubs, and every one of them practices Table Topics weekly with no good way to get feedback between meetings. Clubs pay a monthly subscription for the hosted version - shared practice, member progress, their own topics. Individuals self-host free; the whole thing is one &lt;code&gt;docker compose up&lt;/code&gt;, because the people who most need speaking practice are often the people least able to pay per session.&lt;/p&gt;

&lt;p&gt;First user: me, the night I built it. I gave it a real Table Topics attempt - "What is overrated, and what is underrated?" - and rambled for two minutes about movie hype. The coach scored me 28/100 and it was right: it clocked my 37-words-per-minute crawl (I was thinking, not speaking), called the speech disjointed, and its one fix was "define overrated vs underrated up front, then structure around it." That's exactly what a human evaluator would have told me, at 11pm, for free, with the audio never leaving my laptop.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;

&lt;p&gt;Zero-dependency Node 18 server (no npm install), static frontend, Docker compose with Ollama and the model pull baked in:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser (mic, live STT, timer) --&amp;gt; Node server --&amp;gt; Ollama (qwen2.5:3b-instruct, strict JSON)
                                    |
                                    +--&amp;gt; optional ElevenLabs TTS (browser voice by default)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;/api/topic&lt;/code&gt;, &lt;code&gt;/api/coach&lt;/code&gt;, &lt;code&gt;/api/speak&lt;/code&gt; (optional rate-limited ElevenLabs proxy; browser voice when unset). Tests cover the timing rules, filler measurement, score clamping and the API in mock mode.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's honestly rough
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A 3B model is a decent club coach, not a world champion judge. The measured facts keep it honest; the prose feedback is where bigger open models (or a fine-tune on real ballots) would help.&lt;/li&gt;
&lt;li&gt;Live transcription uses the browser's Web Speech API, so Chrome/Edge only for now - swapping in local Whisper is the obvious next step, and it's open-weight too.&lt;/li&gt;
&lt;li&gt;What broke in my real run: this laptop's Ollama GPU runner was broken - every coaching call failed with &lt;code&gt;CUDA error: shared object initialization failed&lt;/code&gt; - so the verified setup is a CPU-only Ollama server. Related: gemma3:4b on CPU managed ~1.3 tok/s (a verdict would take 6+ minutes), which is why the demo ships qwen2.5:3b-instruct (~3 tok/s warm, ~3 min per verdict - the video captions the thinking pause instead of editing it out).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&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;Confident Speaker Coach: CPU-only run and end-to-end verification&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;Run the Confident Speaker Coach runbook Part 1: run it on the laptop (repo &lt;a href="https://github.com/viveknshah/confident-speaker-coach" rel="noopener noreferrer"&gt;https://github.com/viveknshah/confident-speaker-coach&lt;/a&gt;).&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;Checked pre-reqs: Docker 29.7.2, Ollama 0.40.3, Node 24 available; installed Git 2.55 via winget, cloned the repo. Zero-dependency Node app, tests 4/4 pass.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;docker --version; ollama --version; node --version; git clone repo; node --te...&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;$ docker --version; ollama --version; node --version; git clone repo; node --test test/*.test.js&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;Docker 29.7.2, Ollama 0.40.3, Node v24.19.0; tests 4 pass, 0 fail&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;Started the coach with the already-pulled gemma3:4b. Every /api/coach call failed: Ollama 500 CUDA error shared object initialization failed. The desktop app's GPU runner is broken on this machine.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;OLLAMA_MODEL=gemma3:4b node server.js; POST /api/coach&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;$ OLLAMA_MODEL=gemma3:4b node server.js; POST /api/coach&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;502 Bad Gateway; server log: Ollama 500 CUDA error: shared object initialization failed&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;Quit the Ollama tray app, started a CPU-only server with OLLAMA_NUM_GPU=0 and pulled the repo-default qwen2.5:3b-instruct (1.9 GB) in the background. Gemma on CPU managed only 1.3 tok/s, too slow for a coaching verdict.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;OLLAMA_NUM_GPU=0 ollama serve; ollama pull qwen2.5:3b-instruct; chat speed pr...&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;$ OLLAMA_NUM_GPU=0 ollama serve; ollama pull qwen2.5:3b-instruct; chat speed probes&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;qwen pulled; gemma3:4b 1.3 tok/s on CPU, qwen ~3 tok/s warm&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;Full end-to-end coaching round through qwen: 301-word Table Topics speech, 85 s. Returned 61/100 (C35/D11/L15), honest mid-range scoring with code-measured facts (212 wpm fast, fillers mapped, delivery penalties applied). Took ~3 min warm, so the demo captions the thinking pause. Page verified in browser with zero console errors.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;POST /api/coach full transcript; browser snapshot + console check&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;$ POST /api/coach full transcript; browser snapshot + console check&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;scores C35/D11/L15 total 61; length 1:25; footer Model qwen2.5:3b-instruct; no JS errors&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;
        5 of 5 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



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

&lt;p&gt;Entering overall judging only. ElevenLabs stays in the code as an optional voice upgrade, but the demo ships on the browser voice - no partner category claim.&lt;/p&gt;

&lt;p&gt;Built with Ollama + qwen2.5.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>sidehustle</category>
    </item>
    <item>
      <title>TreeTrail: Bengaluru Tree Walks Curated by Local Gemma</title>
      <dc:creator>Vivek Shah</dc:creator>
      <pubDate>Wed, 07 Oct 2026 17:43:28 +0000</pubDate>
      <link>https://dev.to/viveknshah/treetrail-bengaluru-tree-walks-curated-by-local-gemma-52d7</link>
      <guid>https://dev.to/viveknshah/treetrail-bengaluru-tree-walks-curated-by-local-gemma-52d7</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;Most apps that tell you to go outside start with a goal:&lt;/p&gt;

&lt;p&gt;Walk 5,000 steps. Burn 300 calories. Hit your daily target.&lt;/p&gt;

&lt;p&gt;I wanted to try something different.&lt;/p&gt;

&lt;p&gt;What if the reason to walk was simply:&lt;/p&gt;

&lt;p&gt;Go find these five trees.&lt;/p&gt;

&lt;p&gt;So I built TreeTrail — a small walking app built around Bengaluru's public tree data.&lt;/p&gt;

&lt;p&gt;It takes a tree census, puts the trees on a map around you, lets you tap them for local-AI trivia, and turns a handful of trees into a short exploration loop you can save or export as GPX.&lt;/p&gt;

&lt;p&gt;No fitness score.&lt;br&gt;
No leaderboard.&lt;br&gt;
No gamification.&lt;/p&gt;

&lt;p&gt;Just a map full of trees and a reason to go look at them.&lt;/p&gt;

&lt;p&gt;The surprising part: there are a lot of trees&lt;/p&gt;

&lt;p&gt;The starting point is the BBMP tree census.&lt;/p&gt;

&lt;p&gt;For the current South Zone snapshot, I bundled the January 2025 public-domain dataset directly into the app. It contains 72,456 mapped trees across the dataset.&lt;/p&gt;

&lt;p&gt;When you open a small pocket around Basavanagudi, TreeTrail can find thousands of trees.&lt;/p&gt;

&lt;p&gt;For the initial view, I show the 450 nearest.&lt;/p&gt;

&lt;p&gt;That little counter — 450 shown / 6,850 found — ended up being one of my favorite parts of the demo.&lt;/p&gt;

&lt;p&gt;It makes the density of the city visible.&lt;/p&gt;

&lt;p&gt;These aren't abstract numbers in a spreadsheet anymore. They're dots you can actually go and look for.&lt;/p&gt;

&lt;p&gt;The basic loop&lt;/p&gt;

&lt;p&gt;The flow is deliberately simple:&lt;/p&gt;

&lt;p&gt;Pick a pocket → explore trees → choose a few → curate a loop → walk it.&lt;/p&gt;

&lt;p&gt;For example, start in Basavanagudi.&lt;/p&gt;

&lt;p&gt;The map fills with nearby trees.&lt;/p&gt;

&lt;p&gt;Tap one.&lt;/p&gt;

&lt;p&gt;Instead of getting a giant botanical database entry, you get a small piece of context generated locally by Gemma 3 4B running through Ollama.&lt;/p&gt;

&lt;p&gt;Then add another tree.&lt;/p&gt;

&lt;p&gt;And another.&lt;/p&gt;

&lt;p&gt;Maybe five is enough.&lt;/p&gt;

&lt;p&gt;Hit Curate loop.&lt;/p&gt;

&lt;p&gt;The model orders your selected stops into a compact exploration loop.&lt;/p&gt;

&lt;p&gt;Save it.&lt;/p&gt;

&lt;p&gt;Or export the GPX and take it with you.&lt;/p&gt;

&lt;p&gt;The point isn't to optimize the perfect route.&lt;/p&gt;

&lt;p&gt;It's to give you a reason to leave your screen.&lt;/p&gt;

&lt;p&gt;A walk with something to notice&lt;/p&gt;

&lt;p&gt;There's another small feature that matters more than I expected.&lt;/p&gt;

&lt;p&gt;At each stop, TreeTrail gives you an observation prompt.&lt;/p&gt;

&lt;p&gt;Look at the leaves.&lt;/p&gt;

&lt;p&gt;Compare the bark.&lt;/p&gt;

&lt;p&gt;Notice the shape of the canopy.&lt;/p&gt;

&lt;p&gt;Look at the tree next to it.&lt;/p&gt;

&lt;p&gt;That changes the nature of the walk.&lt;/p&gt;

&lt;p&gt;You're not just accumulating distance.&lt;/p&gt;

&lt;p&gt;You're paying attention.&lt;/p&gt;

&lt;p&gt;A 20-minute walk suddenly has a little mission attached to it:&lt;/p&gt;

&lt;p&gt;Can you find the five trees?&lt;/p&gt;

&lt;p&gt;And when you get there, you actually have something to look at.&lt;/p&gt;

&lt;p&gt;The AI is intentionally local&lt;/p&gt;

&lt;p&gt;One of the reasons I built this this way was to see how much of the experience could happen locally.&lt;/p&gt;

&lt;p&gt;The tree trivia and trail curation run on my laptop using Gemma 3 4B.&lt;/p&gt;

&lt;p&gt;There are no hosted inference calls and no per-query AI bill.&lt;/p&gt;

&lt;p&gt;That also makes the architecture a little more interesting.&lt;/p&gt;

&lt;p&gt;The tree census is local data.&lt;/p&gt;

&lt;p&gt;The model is local.&lt;/p&gt;

&lt;p&gt;The app can work from the bundled dataset without an API key.&lt;/p&gt;

&lt;p&gt;There is still an internet dependency: map tiles come from the internet, and the optional OpenStreetMap mode queries Overpass for live community-mapped data.&lt;/p&gt;

&lt;p&gt;I think being explicit about that distinction matters.&lt;/p&gt;

&lt;p&gt;"Local AI" shouldn't quietly become "the whole application is offline."&lt;/p&gt;

&lt;p&gt;Then I added OpenStreetMap&lt;/p&gt;

&lt;p&gt;TreeTrail also has an optional live OpenStreetMap mode for places like Lalbagh, Cubbon Park and Indiranagar.&lt;/p&gt;

&lt;p&gt;That gives the app a second, very different kind of open data source.&lt;/p&gt;

&lt;p&gt;The BBMP dataset is a civic snapshot.&lt;/p&gt;

&lt;p&gt;OpenStreetMap is a continuously editable community map.&lt;/p&gt;

&lt;p&gt;And the app labels the source of each tree so you're not left wondering where a particular point came from.&lt;/p&gt;

&lt;p&gt;The Lalbagh pocket is particularly fun because the OSM data there is surprisingly rich.&lt;/p&gt;

&lt;p&gt;It turns the app into something slightly bigger than a viewer for one government dataset.&lt;/p&gt;

&lt;p&gt;You can compare what different open datasets know about the same city.&lt;/p&gt;

&lt;p&gt;The data story is the point&lt;/p&gt;

&lt;p&gt;I deliberately didn't build an API layer for this prototype.&lt;/p&gt;

&lt;p&gt;The BBMP tree census is bundled directly into a Python file.&lt;/p&gt;

&lt;p&gt;No API key.&lt;/p&gt;

&lt;p&gt;No complicated ingestion pipeline.&lt;/p&gt;

&lt;p&gt;No mysterious backend.&lt;/p&gt;

&lt;p&gt;You can inspect the source data and see where the trees came from.&lt;/p&gt;

&lt;p&gt;That makes the project useful beyond the demo.&lt;/p&gt;

&lt;p&gt;BBMP did the work of counting and mapping these trees.&lt;/p&gt;

&lt;p&gt;TreeTrail is basically an experiment in asking:&lt;/p&gt;

&lt;p&gt;What else can we do with that public data once we make it human-scale?&lt;/p&gt;

&lt;p&gt;A spreadsheet can tell you that a tree exists.&lt;/p&gt;

&lt;p&gt;A map can show you where it is.&lt;/p&gt;

&lt;p&gt;A walking trail can give you a reason to go see it.&lt;/p&gt;

&lt;p&gt;The honest limitations&lt;/p&gt;

&lt;p&gt;There are a few things I don't want the demo to imply.&lt;/p&gt;

&lt;p&gt;First: the generated loop is not walking directions.&lt;/p&gt;

&lt;p&gt;It's a straight-line exploration loop.&lt;/p&gt;

&lt;p&gt;The dashed lines are a guide for the order of exploration, not turn-by-turn navigation.&lt;/p&gt;

&lt;p&gt;Second: the AI's source notes are species-level knowledge phrased by the model.&lt;/p&gt;

&lt;p&gt;They're not live research about that individual tree.&lt;/p&gt;

&lt;p&gt;If TreeTrail tells you something interesting about a rain tree, that doesn't mean the model has inspected that particular rain tree.&lt;/p&gt;

&lt;p&gt;Third: the BBMP data is a January 2025 South Zone snapshot.&lt;/p&gt;

&lt;p&gt;It's not a live, citywide inventory.&lt;/p&gt;

&lt;p&gt;And some mapped trees may be on private property.&lt;/p&gt;

&lt;p&gt;That last one is particularly important.&lt;/p&gt;

&lt;p&gt;A point on a map doesn't automatically mean you can walk up to it.&lt;/p&gt;

&lt;p&gt;Which brings me to the part I'm most interested in.&lt;/p&gt;

&lt;p&gt;I want people to try to break the map&lt;/p&gt;

&lt;p&gt;The best test isn't whether the demo works on my laptop.&lt;/p&gt;

&lt;p&gt;It's whether someone takes a TreeTrail walk and discovers something the dataset got wrong.&lt;/p&gt;

&lt;p&gt;Maybe the "tree" is inside someone's compound.&lt;/p&gt;

&lt;p&gt;Maybe it's gone.&lt;/p&gt;

&lt;p&gt;Maybe the species note doesn't match what you see.&lt;/p&gt;

&lt;p&gt;Maybe the point is technically correct but completely inaccessible.&lt;/p&gt;

&lt;p&gt;Or maybe you find an incredible tree you would have walked past a hundred times.&lt;/p&gt;

&lt;p&gt;That's the experiment.&lt;/p&gt;

&lt;p&gt;Take one walk.&lt;/p&gt;

&lt;p&gt;Pick a handful of trees.&lt;/p&gt;

&lt;p&gt;Take one photo.&lt;/p&gt;

&lt;p&gt;Tell me what TreeTrail got right — and what it got wrong.&lt;/p&gt;

&lt;p&gt;Because that's where an open-data project becomes interesting: when the map stops being an artifact on a screen and starts getting checked against the real world.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gitlab.com/viveknshah/treetrail/-/blob/main/Demo/treetrail-demo.mp4" rel="noopener noreferrer"&gt;Video&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;[Code Repo]&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://gitlab.com/viveknshah/treetrail" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgitlab.com%2Fassets%2Ftwitter_card-570ddb06edf56a2312253c5872489847a0f385112ddbcd71ccfa1570febab5d2.jpg" height="800" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://gitlab.com/viveknshah/treetrail" rel="noopener noreferrer" class="c-link"&gt;
            Vivek Shah / TreeTrail · GitLab
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            GitLab.com
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgitlab.com%2Fassets%2Ffavicon-72a2cad5025aa931d6ea56c3201d1f18e68a8cd39788c7c80d5b2b82aa5143ef.png" width="32" height="32"&gt;
          gitlab.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;TreeTrail is built around a simple idea: use local AI to turn open civic data into something you can experience on foot.&lt;/p&gt;

&lt;p&gt;The core dataset is the BBMP tree census — a public-domain snapshot of 72,456 trees from the January 2025 South Zone census. I bundled the data directly into the Python application, so the project doesn't depend on a tree-data API or API keys.&lt;/p&gt;

&lt;p&gt;For the AI layer, I use Gemma 3 4B through Ollama, running locally on my laptop.&lt;/p&gt;

&lt;p&gt;The model has two small but useful jobs:&lt;/p&gt;

&lt;p&gt;Tree trivia — when I tap a mapped tree, the model generates a short species-level note that gives me something to notice when I reach the tree.&lt;br&gt;
Trail curation — after selecting a handful of trees, the model helps order them into a compact exploration loop.&lt;/p&gt;

&lt;p&gt;The rest of the application is deliberately straightforward: a map, nearby-tree filtering, stop selection, loop creation, saving, and GPX export.&lt;/p&gt;

&lt;p&gt;There's also an optional OpenStreetMap/Overpass mode for places such as Lalbagh, Cubbon Park and Indiranagar. This provides live community-mapped data alongside the bundled BBMP snapshot, with the source clearly identified.&lt;/p&gt;

&lt;p&gt;The important part is that the AI isn't trying to replace the map or the data.&lt;/p&gt;

&lt;p&gt;The data tells me where to look. The local model gives me something to look for. The walk is where I find out whether any of it is actually true.&lt;/p&gt;

&lt;p&gt;And because the inference happens locally, there is no hosted AI endpoint or per-query inference cost sitting behind every tap.&lt;/p&gt;

&lt;p&gt;There are still internet dependencies — map tiles load online, and OSM mode queries Overpass — but the core AI experience can run on my own machine.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;TreeTrail exists because several pieces of open technology can be connected in ways that weren't possible — or weren't nearly as interesting — as isolated datasets.&lt;/p&gt;

&lt;p&gt;The BBMP has already done the hard civic work of counting and mapping trees.&lt;/p&gt;

&lt;p&gt;OpenStreetMap provides another layer of community-maintained geographic data.&lt;/p&gt;

&lt;p&gt;Ollama makes it practical to run an open-weight model such as Gemma locally.&lt;/p&gt;

&lt;p&gt;And open-source mapping and Python tooling make it possible to glue these pieces together without building the project around a proprietary AI platform.&lt;/p&gt;

&lt;p&gt;A closed AI API could certainly generate tree trivia and suggest an order for a list of stops.&lt;/p&gt;

&lt;p&gt;But the open approach changes the economics and the relationship with the data.&lt;/p&gt;

&lt;p&gt;There is no per-query AI bill for every tree someone taps. The model runs on my machine. The tree census can be inspected and bundled with the application. The OSM data can be explored and improved by its community.&lt;/p&gt;

&lt;p&gt;More importantly, the pieces remain replaceable.&lt;/p&gt;

&lt;p&gt;The dataset can change.&lt;/p&gt;

&lt;p&gt;The local model can change.&lt;/p&gt;

&lt;p&gt;The mapping source can change.&lt;/p&gt;

&lt;p&gt;Someone else can fork the project, use a different city tree census, swap in another open-weight model, or build an entirely different experience on top of the same civic data.&lt;/p&gt;

&lt;p&gt;That's what open innovation makes possible here: the project doesn't have to end at the prototype I built.&lt;/p&gt;

&lt;p&gt;The BBMP tree census was created as civic data. TreeTrail is one experiment in making that data useful at street level.&lt;/p&gt;

&lt;p&gt;A closed API might have helped me build the demo.&lt;/p&gt;

&lt;p&gt;Open data and open AI make it possible for someone else to take the idea somewhere I haven't thought of yet.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&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;TreeTrail local setup and verification&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;Follow instructions in README — run the TreeTrail local tree-walk app.&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;Checked environment: no Python on PATH, so installed Python 3.12 via winget and installed the single dependency with pip install requests. Verified treetrail.py compiles (py_compile).&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;winget install Python.Python.3.12; pip install requests; py_compile treetrail.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;$ winget install Python.Python.3.12; pip install requests; py_compile treetrail.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"&gt;&lt;code&gt;Python 3.12.10, requests 2.34.2 installed, compile ok&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;Confirmed Ollama 0.35.1 already running with gemma3:4b pulled, so no pull needed. Copied treetrail.py into the workspace and started the server on port 8000.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;ollama list; python treetrail.py --port 8000&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;$ ollama list; python treetrail.py --port 8000&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;gemma3:4b present; server up: health ok, census_records 72456&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;Verified the census API against README claims: Basavanagudi default returns 6850 total, nearest 450 shown, first record Pongam. Matches the documented South Zone snapshot.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;GET /api/trees?lat=12.9433&amp;amp;lon=77.5681&amp;amp;source=census&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;$ GET /api/trees?lat=12.9433&amp;amp;lon=77.5681&amp;amp;source=census&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;census total 6850, shown 450; first bbmp:GN7530NC6PQH001 Pongam, distance 30m&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;Ran one live trivia call and one curated loop with the real gemma3:4b model (README noted this was untested). Trivia returned local-AI phrasing with sources; trail correctly fell back to nearest-neighbor ordering per the ID/detour validator.&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-shell"&gt;shell&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;POST /api/trivia; POST /api/trail (5 stops, 20 min)&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;$ POST /api/trivia; POST /api/trail (5 stops, 20 min)&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;trivia mode local AI; trail mode nearest-neighbor fallback, 258m / 14min estimate&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&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;Opened &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt; in the browser: title TreeTrail Bengaluru, status shows 6850 mapped records / nearest 450 shown, no console errors. App ready for the 2-minute demo: tap a dot, Pick 5 nearby, Curate loop, Save, export JSON/GPX.&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;
        6 of 6 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Table Topics Champion Buddy</title>
      <dc:creator>Vivek Shah</dc:creator>
      <pubDate>Sun, 04 Oct 2026 14:35:43 +0000</pubDate>
      <link>https://dev.to/viveknshah/table-topics-champion-buddy-ece</link>
      <guid>https://dev.to/viveknshah/table-topics-champion-buddy-ece</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built a local Table Topics practice partner for all my fellow club members (Rishi specifically) to prepare for the upcoming contest season. It give you a Random topic for your impromptu speaking, speak your answer, get a ballot-style scorecard and one concrete recommendation. Open models only, nothing leaves your machine.&lt;/p&gt;

&lt;p&gt;Ballot weights: Content 55, Delivery 30, Language 15. Criteria wording is a paraphrase, not official Toastmasters text. Delivery is judged from the transcript only.&lt;/p&gt;

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

&lt;p&gt;Demo Run &lt;a href="https://gitlab.com/viveknshah/topicPal/-/blob/main/outputs/topicpal_demo_info.md" rel="noopener noreferrer"&gt;Output&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Here is the documented &lt;a href="https://gitlab.com/viveknshah/topicPal" rel="noopener noreferrer"&gt;code&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Everything runs locally through Ollama. An open-weight Gemma model generates a fresh Table Topic and later scores the transcript against the ballot weights (Content 55 / Delivery 30 / Language 15). Open-source Whisper (faster-whisper) handles transcription. No API keys, no cloud calls - a speech you're practicing for a contest stays on your machine, which matters when the material is personal.&lt;/p&gt;

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

&lt;p&gt;A closed API would make every practice session someone else's data and someone else's meter running. Open models mean any Toastmasters club anywhere can run this offline and free - and because the prompts are plain text, a club can tune the scoring to its own ballot without asking anyone's permission.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://gitlab.com/viveknshah/topicPal/-/blob/main/outputs/agentsession..md" rel="noopener noreferrer"&gt;Agent Session&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Best Use of Gemma.&lt;/p&gt;

&lt;h2&gt;
  
  
  Friend's response
&lt;/h2&gt;

&lt;p&gt;Rishi : "Bro, Integrate this with whatsapp! it would make it go viral across the world! If I skip the initial setup, its a gem after that! "&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Using MindsDB to Predict Microsoft Stock Prices</title>
      <dc:creator>Vivek Shah</dc:creator>
      <pubDate>Mon, 31 Oct 2022 21:01:30 +0000</pubDate>
      <link>https://dev.to/viveknshah/using-mindsdb-to-predict-microsoft-stock-prices-3l79</link>
      <guid>https://dev.to/viveknshah/using-mindsdb-to-predict-microsoft-stock-prices-3l79</guid>
      <description>&lt;p&gt;Ever wanted to be the Oracle like the Delphic one and use it to predict stocks in your portfolio? Can't take to the past but with MindsDB you could take a shot at predicting the future via this simple tutorial. &lt;/p&gt;

&lt;p&gt;What is &lt;a href="https://mindsdb.com/"&gt;MindsDB&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;MindsDB is a SQL-based tool that can be used to create and train time-series machine learning models directly from your database. With MindsDB, data teams and data analysts can use their data to build, train, optimize, and deploy machine learning models that provide meaningful insights for real-world problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oAMABdjk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z9ge7jx8g5n4z1kh0c0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oAMABdjk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z9ge7jx8g5n4z1kh0c0w.png" alt="Delphi Oracle" width="474" height="465"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://brewminate.com/wp-content/uploads/2018/04/041118-37-Greek-Greece-Oracle-Delphi-History.jpg"&gt;img source&lt;/a&gt; &lt;br&gt;
&lt;strong&gt;Setup&lt;/strong&gt;&lt;br&gt;
Start off with you using the MindsDB Cloud Editor (create your free MindsDB Cloud account &lt;a href="https://cloud.mindsdb.com/register"&gt;here&lt;/a&gt;) to create and train your time series prediction model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get your Dataset&lt;/strong&gt;&lt;br&gt;
Download &lt;a href="https://www.kaggle.com/datasets/berkayalan/microsoft-stock-data-between-2015-and-2022?resource=download"&gt;data set&lt;/a&gt; from Kaggle for our demo to work with Microsoft stock prices : &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VY4wUAkY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdq15e9mb2xs2vflplsq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VY4wUAkY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdq15e9mb2xs2vflplsq.png" alt="Kaggle Data set" width="880" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upload your file into MindsDB&lt;/strong&gt;&lt;br&gt;
You can either load from a database or a file. We'll go with file in this demo. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4sseIts5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfh55i5pqnv30fgj8xrr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4sseIts5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfh55i5pqnv30fgj8xrr.png" alt="MindsDB Data Load" width="880" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check your data&lt;/strong&gt;&lt;br&gt;
Sanity check your data loads up fine with a simple select query. You can use limit to have only a few records load.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5-nVAP14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xn3rskvbgnpolubxspq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5-nVAP14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xn3rskvbgnpolubxspq.png" alt="Select data" width="880" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a Predictor Model&lt;/strong&gt;&lt;br&gt;
Tap in the following syntax to get MindsDB to kick off the predictor model. Depending on the volume of data, this could take some time and you might see a "training" error if you query earlier than it is completed. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xekQ-eit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tuqdszbc1bysnhxkl1xu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xekQ-eit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tuqdszbc1bysnhxkl1xu.png" alt="Predictor Kicked off" width="719" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Insights&lt;/strong&gt;&lt;br&gt;
You can also quickly glean at the data insights generated visually to give you a quick look into the trends for the data sets you have pulled in. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iy77yBFN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkv164mft1aql58c755c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iy77yBFN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkv164mft1aql58c755c.png" alt="Data Insights" width="880" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run a Query for an extrapolated Dates to see the magic&lt;/strong&gt;&lt;br&gt;
Since our data set is till July 2022, let's try a more recent date like 30th October 2022&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z6cH3ymu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ps2sdnyjiehqf5c8vqgw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z6cH3ymu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ps2sdnyjiehqf5c8vqgw.png" alt="Final results" width="710" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that last step, we have successfully created and trained Using historical data, we can easily try to predict the future. Hopefully my stock broker ain't reading this. &lt;/p&gt;

&lt;p&gt;MindsDB makes machine learning simple, intuitive, and extremely effective! Do you have something interesting you’d like to forecast? Head to MindsDB and start training your model today!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
