<?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: Arnulfo</title>
    <description>The latest articles on DEV Community by Arnulfo (@arnulfo_07).</description>
    <link>https://dev.to/arnulfo_07</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%2F96018%2Fcc30ba29-dddc-4a48-90d7-8cee700edb2f.JPG</url>
      <title>DEV Community: Arnulfo</title>
      <link>https://dev.to/arnulfo_07</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arnulfo_07"/>
    <language>en</language>
    <item>
      <title>Modo Tortuga: a local Gemma that turns a 30-item to-do list into three tiny first steps</title>
      <dc:creator>Arnulfo</dc:creator>
      <pubDate>Mon, 26 Oct 2026 03:35:40 +0000</pubDate>
      <link>https://dev.to/arnulfo_07/modo-tortuga-a-local-gemma-that-turns-a-30-item-to-do-list-into-three-tiny-first-steps-3j57</link>
      <guid>https://dev.to/arnulfo_07/modo-tortuga-a-local-gemma-that-turns-a-30-item-to-do-list-into-three-tiny-first-steps-3j57</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge: Week 3&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Modo Tortuga&lt;/strong&gt; ("Turtle Mode") is for the days when the list is so long that the laziest option is to not start at all.&lt;/p&gt;

&lt;p&gt;You give it a plain text file with up to 30 to-dos, one per line, written however you like. A Gemma model running on your laptop picks &lt;strong&gt;at most three&lt;/strong&gt; and gives you a first step for each: something you can do in 15 minutes or less that leaves a small, visible result. Then it stops. It doesn't reschedule anything, send anything, or touch your calendar. It just points.&lt;/p&gt;

&lt;p&gt;The effort it saves is the decision itself: reading the whole list, weighing dates against each other, and figuring out what "start" even means for a vague item like "prepare a presentation". Sloths don't plan the whole tree. They reach for the next branch.&lt;/p&gt;

&lt;p&gt;I wrote it in Spanish because that's how I write my lists (I'm in Panama), so the output below is in Spanish with a translation.&lt;/p&gt;

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

&lt;p&gt;A fictional list (no personal data), with optional &lt;code&gt;[YYYY-MM-DD]&lt;/code&gt; deadlines:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2026-10-24] Revisar lista de materiales para el taller del sábado
[2026-10-25] Preparar una presentación breve para el club de lectura
Ordenar fotos duplicadas de un viaje ficticio
Comparar tres recetas para una cena sencilla
[2026-10-23] Confirmar el horario del salón comunitario
Clasificar notas sueltas de un proyecto de jardinería
Escribir una lista de preguntas para la próxima reunión
Comprar cartulina para una actividad escolar ficticia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Real output from &lt;code&gt;gemma4:e2b&lt;/code&gt; on an Apple M3 laptop with 8 GB of RAM, in &lt;strong&gt;32 seconds&lt;/strong&gt;, offline:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Modo Tortuga · empieza por esto (propuesta; tú decides):
1. Confirmar el horario del salón comunitario · vence 2026-10-23
   Primer paso: Buscar el horario del salón comunitario en línea
   Motivo: Confirma la disponibilidad del espacio.
2. Revisar lista de materiales para el taller del sábado · vence 2026-10-24
   Primer paso: Abrir la lista de materiales para el taller del sábado
   Motivo: Desbloquea la revisión de la lista.
3. Preparar una presentación breve para el club de lectura · vence 2026-10-25
   Primer paso: Abrir un documento y redactar el título de la presentación
   Motivo: Inicia la preparación de la presentación.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;In English: &lt;em&gt;"Turtle Mode · start with this (a suggestion; you decide): 1. Confirm the community hall schedule, due Oct 23. First step: look up the hall's hours online. 2. Review the materials list for Saturday's workshop, due Oct 24. First step: open the materials list. 3. Prepare a short talk for the book club, due Oct 25. First step: open a document and write the talk's title."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It picked the three dated items, in deadline order, and left the five undated ones for another day. That's the right call for this list. The first steps vary in quality, though. "Write the title" is a real, tiny start. "Open the materials list" is a step, but barely more than the task.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/yosef7" rel="noopener noreferrer"&gt;
        yosef7
      &lt;/a&gt; / &lt;a href="https://github.com/yosef7/modo-tortuga" rel="noopener noreferrer"&gt;
        modo-tortuga
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Local first-step planner: Gemma in Ollama turns a long to-do list into three tiny first steps (Hacktoberfest 2026 DEV Challenge, Week 3)
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Modo Tortuga&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;English:&lt;/strong&gt; a local first-step planner. Give it up to 30 to-dos and a Gemma model running in Ollama on your laptop picks at most three, each with a first step of 15 minutes or less. Nothing leaves &lt;code&gt;127.0.0.1&lt;/code&gt;. Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19" rel="nofollow"&gt;Hacktoberfest Open-Source AI Challenge: Week 3&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Un prototipo local para esos días en que una lista de pendientes es demasiado larga para empezar. Lee hasta 30 líneas de texto y le pide a un modelo abierto que elija &lt;strong&gt;hasta tres primeros pasos&lt;/strong&gt;. Muestra una propuesta; no cambia tareas, archivos ni calendarios.&lt;/p&gt;

&lt;p&gt;Creado durante la ventana del &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19" rel="nofollow"&gt;Hacktoberfest Open-Source AI Challenge: Week 3&lt;/a&gt;, cuyo tema es &lt;em&gt;Slow and Steady&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Código publicado bajo licencia MIT (ver &lt;code&gt;LICENSE&lt;/code&gt;).&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Probar&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Requiere Python 3.10+, &lt;a href="https://ollama.com/" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt; y un modelo compatible. No tiene dependencias Python de terceros.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;ollama serve                         &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; en otra terminal, si no está activo&lt;/span&gt;
ollama&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yosef7/modo-tortuga" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;One Python file of 138 lines, three unit tests, MIT license, and nothing to install from PyPI. It only uses the standard library.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ollama pull gemma4:e2b&lt;br&gt;
python3 tortuga.py ejemplo.txt&lt;br&gt;
python3 -m unittest -v test_tortuga.py&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;The pipeline is short on purpose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parse.&lt;/strong&gt; Each line becomes &lt;code&gt;{"id": "T3", "due": "2026-10-23", "text": "..."}&lt;/code&gt;. Dates only count if they're written explicitly. Comments (&lt;code&gt;#&lt;/code&gt;) are skipped, lines are capped at 300 characters, and lists at 30 items.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask Gemma.&lt;/strong&gt; The list goes to Ollama's &lt;code&gt;/api/chat&lt;/code&gt; at &lt;code&gt;127.0.0.1:11434&lt;/code&gt; with &lt;code&gt;temperature: 0&lt;/code&gt;, &lt;code&gt;think: false&lt;/code&gt;, and a &lt;strong&gt;JSON Schema in the &lt;code&gt;format&lt;/code&gt; field&lt;/strong&gt;. Ollama constrains decoding to that schema, so the model can only answer with 1 to 3 objects of &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;first_step&lt;/code&gt;, &lt;code&gt;why_now&lt;/code&gt;. The prompt asks for steps under 15 minutes that produce something checkable, and gives one example, because without it the small model tended to just repeat the task title.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't trust it.&lt;/strong&gt; A schema can force the &lt;em&gt;shape&lt;/em&gt; but not the &lt;em&gt;content&lt;/em&gt;. So &lt;code&gt;validate()&lt;/code&gt; rejects any &lt;code&gt;id&lt;/code&gt; that isn't in the input, any duplicate, and empty or overlong text. The model never gets to invent a task, and the task text and date shown to you come from your file, not from the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show, don't act.&lt;/strong&gt; The result is printed and that's it. The prompt also tells the model not to suggest sending, paying, deleting or publishing anything on its own.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the rule I ended up with: the model interprets the messy free-form list, and plain code checks every claim it makes about that list.&lt;/p&gt;

&lt;p&gt;I chose &lt;code&gt;gemma4:e2b&lt;/code&gt; because it runs on a base 8 GB laptop alongside everything else I have open. A bigger model would probably write sharper first steps, but then it wouldn't run on the machine I actually use.&lt;/p&gt;

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

&lt;p&gt;A to-do list is one of the most personal files you have. Mine has health appointments, money, and names of people I owe a reply. I'm not going to paste that into a cloud chat every morning. That's why this works better open than closed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It stays on my machine.&lt;/strong&gt; The code refuses any host other than &lt;code&gt;127.0.0.1:11434&lt;/code&gt;. The only internet use is the one-time model download.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works offline&lt;/strong&gt;, which means it works on a bus or with bad Wi-Fi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing per run.&lt;/strong&gt; No API key, no quota. A tool you're supposed to use when you're tired can't have a "your credits ran out" moment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It can be checked.&lt;/strong&gt; Open weights and an open runtime let me pin the model and set temperature 0. With the same list, I get the same answer, which is what I want from something that tells me what to do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gemma's open weights and Ollama's open-source runtime are what make this a 138-line script instead of a service with an account, a privacy policy, and a bill.&lt;/p&gt;

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

&lt;p&gt;Honest version: I haven't timed it against picking tasks by hand, so I won't claim a number of minutes saved. What I can measure is that it takes &lt;strong&gt;32 seconds&lt;/strong&gt; on my laptop and turns an 8-item list into 3 actions, and the first of those can be done in under 15 minutes. The real payoff is psychological: the time it saves is the time I'd spend staring at the list &lt;em&gt;not&lt;/em&gt; starting.&lt;/p&gt;

&lt;p&gt;The next step, which I didn't get to this week, is to log for a few days how long it takes me to start with and without it.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; The whole project is &lt;code&gt;gemma4:e2b&lt;/code&gt; running locally through Ollama, with schema-constrained JSON output and validation on top.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>python</category>
    </item>
    <item>
      <title>Cuadre: turning Panama's paper receipts into a tax-ready spreadsheet, with Gemma on my laptop</title>
      <dc:creator>Arnulfo</dc:creator>
      <pubDate>Mon, 19 Oct 2026 16:01:37 +0000</pubDate>
      <link>https://dev.to/arnulfo_07/cuadre-turning-panamas-paper-receipts-into-a-tax-ready-spreadsheet-with-gemma-on-my-laptop-31b8</link>
      <guid>https://dev.to/arnulfo_07/cuadre-turning-panamas-paper-receipts-into-a-tax-ready-spreadsheet-with-gemma-on-my-laptop-31b8</guid>
      <description>&lt;p&gt;&lt;em&gt;I started Cuadre for the Hacktoberfest Open-Source AI Challenge (Week 2: Side Hustle), but I didn't publish it before the deadline. It isn't a challenge entry. I'm sharing it anyway because what I learned building it is worth writing down.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;In Panama, almost every purchase a freelancer or a small business makes comes with a paper receipt. It has the shop's tax ID (the &lt;strong&gt;RUC&lt;/strong&gt;) and a line for &lt;strong&gt;ITBMS&lt;/strong&gt;, our sales tax: 7% on most things, 10% on hotels, 15% on tobacco. The ITBMS you pay on work purchases can often be credited against the ITBMS you owe each month (your accountant decides which purchases qualify). To get it, someone has to sit down at the end of the month and type every receipt into a spreadsheet. A shoebox of thermal paper becomes rows of merchant, RUC, date, subtotal, tax and total.&lt;/p&gt;

&lt;p&gt;That someone is usually the freelancer at 11 p.m., or their accountant, who gets the receipts as blurry WhatsApp photos from 30 different clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cuadre&lt;/strong&gt; (from &lt;em&gt;cuadrar&lt;/em&gt;, "to make the books balance") does the typing. You put the receipt photos in a folder and run one command. You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;recibos.csv&lt;/code&gt;, one row per receipt, which opens cleanly in Excel;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resumen.md&lt;/code&gt;, with totals by month and category and the ITBMS paid;&lt;/li&gt;
&lt;li&gt;a short list of the receipts a human should double-check, each with the reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The photos are read by &lt;strong&gt;Gemma&lt;/strong&gt;, an open-weight model, running on my own laptop. They never leave the machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who it's for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accountants&lt;/strong&gt; with small-business clients. This is the strongest case: they transcribe receipts by hand every month, and they can't send their clients' data to a cloud service they don't control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freelancers&lt;/strong&gt; who file ITBMS every month and want back the tax they paid on their own purchases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small shops&lt;/strong&gt;, like a &lt;em&gt;fonda&lt;/em&gt; (a small home-style restaurant), a hardware store or a repair shop, that keep receipts in a box until the accountant asks for them.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Six made-up Panamanian receipts, each one with a trap: a supermarket ticket with tax-exempt groceries, a &lt;em&gt;fonda&lt;/em&gt; lunch where the tax rounds, a stationery store, a hotel taxed at 10%, &lt;strong&gt;the same stationery receipt photographed twice&lt;/strong&gt;, and a handwritten kiosk receipt with no RUC and no tax line. Every merchant, RUC and amount below is fictional.&lt;/p&gt;

&lt;p&gt;This is the actual output from Gemma on my laptop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────────────────┐
│ Cuadre · 6 recibos                                                     │
│ Compras: B/. 146.43 · ITBMS pagado: B/. 10.98                          │
│ Listos: 2 · Para revisar: 4                                            │
│   ! 01-super.jpg: El ITBMS es 3,5 % del subtotal (lo normal: 7 %);     │
│     puede haber productos exentos                                      │
│   ! 04-hotel.jpg: El RUC «155612233-2» no tiene forma de RUC panameño  │
│   ! 05-papeleria-otra-foto.jpg: Posible duplicado de 03-papeleria.jpg: │
│     no se suma                                                         │
│   ! 06-kiosko.jpg: Sin ITBMS desglosado                                │
│ Leyó: gemma4:e2b, en este equipo                                       │
└────────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interface is in Spanish because the people it's for speak Spanish. In short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ITBMS paid: B/. 10.98, exactly right&lt;/strong&gt; (0.83 + 0.60 + 1.05 + 8.50). The &lt;em&gt;balboa&lt;/em&gt; (B/.) is pegged one-to-one to the US dollar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supermarket:&lt;/strong&gt; the tax is only 3.5% of the subtotal. That's not an error, because some groceries are exempt, but it's worth a look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hotel:&lt;/strong&gt; Gemma cut off the end of the RUC. The code noticed that the result doesn't look like a Panamanian RUC and flagged it. I left this one in on purpose: it shows the checks doing their job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate photo:&lt;/strong&gt; detected and &lt;strong&gt;not counted twice&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kiosk:&lt;/strong&gt; no tax line and no RUC, as expected for a handwritten receipt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this is the CSV an accountant would receive (first rows):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;archivo,fecha,comercio,ruc,dv,factura,categoria,subtotal,itbms,total,estado,avisos
02-fonda.jpg,2026-10-05,FONDA DONA MERY,8-745-1290,12,000231,alimentación,8.50,0.60,9.10,ok,
03-papeleria.jpg,2026-10-07,PAPELERIA CALIDONIA,2556-112-251344,08,0012877,oficina,15.00,1.05,16.05,ok,
04-hotel.jpg,2026-10-09,HOTEL BRISAS DE BOQUETE,155612233-2,57,H-00981,hospedaje,85.00,8.50,93.50,revisar,El RUC «155612233-2» no tiene forma de RUC panameño
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Python standard library only at runtime, 42 tests, and a cache keyed by the photo's hash, so when you add next week's receipts and run it again, only the new photos are read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule: the model transcribes, the code calculates.&lt;/strong&gt; A small vision model reads printed text well, but it's not reliable at arithmetic, and a tax report can't contain invented numbers. So Gemma has exactly one job, which is to &lt;strong&gt;copy what is printed&lt;/strong&gt;. Everything else is plain code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Who does it&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read the photo&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma 4 E2B&lt;/a&gt;, locally through &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;The only step that needs a vision model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shape of the answer&lt;/td&gt;
&lt;td&gt;A JSON Schema in Ollama's &lt;code&gt;format&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Gemma can't return extra fields or invent a category&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amounts&lt;/td&gt;
&lt;td&gt;&lt;code&gt;monto_de_linea()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemma copies the whole line; the code separates the rate (7%) from the amount (0.83)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dates&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fecha()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Day first, the way Panama writes them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sum, rate, RUC, date, duplicates&lt;/td&gt;
&lt;td&gt;&lt;code&gt;validar.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed rules. A doubtful receipt goes to "review" instead of being added silently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Totals&lt;/td&gt;
&lt;td&gt;&lt;code&gt;informe.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Decimal&lt;/code&gt; math, so no rounding drift; duplicates are excluded&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Gemma runs with &lt;code&gt;temperature: 0&lt;/code&gt; and thinking turned off: transcription doesn't need reasoning, and the same photo always gets the same reading. On a laptop with 16 GB of RAM and &lt;strong&gt;no GPU&lt;/strong&gt;, a receipt takes between 6 and 27 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What testing taught me.&lt;/strong&gt; The first version looked fine on paper. Then I ran it on real images, and Gemma taught me three things:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I saw&lt;/th&gt;
&lt;th&gt;What I changed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;In the &lt;code&gt;itbms&lt;/code&gt; field, Gemma copied the &lt;strong&gt;label&lt;/strong&gt; &lt;code&gt;"ITBMS 7%"&lt;/code&gt; instead of the amount, on all six receipts. The code doesn't make up numbers, so all six came out "doesn't balance"&lt;/td&gt;
&lt;td&gt;Ask Gemma for the &lt;strong&gt;complete printed line&lt;/strong&gt; (&lt;code&gt;ITBMS 7%   0.83&lt;/code&gt;) and let the code pull out the last amount, ignoring percentages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On the hotel receipt, Gemma dropped the last part of the RUC&lt;/td&gt;
&lt;td&gt;Nothing to fix in the model: the RUC shape check caught it. That's the point of having checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On the handwritten kiosk receipt, it put the &lt;strong&gt;date&lt;/strong&gt; &lt;code&gt;10/10/26&lt;/code&gt; in the subtotal field, and the code read it as B/. 26.00&lt;/td&gt;
&lt;td&gt;A line shaped like a date is never an amount. If the date is missing and turns up in another field, the code recovers it from there&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After those changes, the run is stable: the exact ITBMS total, the duplicate excluded, the exempt items detected, and every doubtful receipt marked for review.&lt;/p&gt;

&lt;p&gt;One limit I kept on purpose: Cuadre checks the &lt;em&gt;shape&lt;/em&gt; of a RUC, not its check digit, because I haven't verified the tax authority's algorithm. It says so instead of pretending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Models Matter Here
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A receipt is personal data.&lt;/strong&gt; It tells you where someone shopped, what they bought, when and how much. A freelancer's receipts also reveal who they work for. For an accountant, uploading those photos to a cloud API means handing their &lt;em&gt;clients'&lt;/em&gt; data to a third party. With Gemma running in Ollama, the only connection Cuadre makes is to &lt;code&gt;localhost:11434&lt;/code&gt;. The code doesn't accept any other address. Nothing leaves the office.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The business only works if a receipt costs close to nothing.&lt;/strong&gt; An accountant with 30 clients and about 100 receipts per client reads around &lt;strong&gt;3,000 receipts a month&lt;/strong&gt;. With a local model, that costs the laptop's electricity. There's no per-image fee that grows with every new client and no quota that runs out at the end of the month, which is exactly when everyone files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I can't be locked out.&lt;/strong&gt; If a provider raises prices or retires a model, Cuadre keeps working: the model is on the disk, and swapping it is one flag (&lt;code&gt;--modelo gemma4:e4b&lt;/code&gt; on a machine with more memory).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I could shape the model around the job.&lt;/strong&gt; Because the runtime is open, I could constrain the output with a schema, turn off thinking, fix the temperature, and see exactly how Gemma failed. Then I could build a check around each failure. With open weights, the next step is possible too: fine-tuning Gemma on anonymized Panamanian receipts so it cuts fewer RUCs.&lt;/p&gt;

&lt;p&gt;To be honest about the trade-off: a large hosted model would probably make fewer reading mistakes. But for this job, privacy and a near-zero cost per receipt matter more, and the small model's mistakes are the kind the code can catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;So far I've only tested Cuadre on the six fictional receipts above. The next steps are running it on real receipts (without publishing them), putting it in front of an accountant to see whether they'd actually pay for it, and opening the repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI assistance disclosure:&lt;/strong&gt; I built Cuadre with Claude Code (Claude Opus 5.5) as my coding agent. It implemented the code, tests and documentation, generated the demo receipts, and drafted this post from the recorded test runs. I chose the idea and the Panamanian tax rules to check, and reviewed the results.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Ventana seca: finding the dry hour to get outside in Panama's rainy season, with Gemma on my laptop</title>
      <dc:creator>Arnulfo</dc:creator>
      <pubDate>Wed, 07 Oct 2026 02:08:46 +0000</pubDate>
      <link>https://dev.to/arnulfo_07/ventana-seca-finding-the-dry-hour-to-get-outside-in-panamas-rainy-season-with-gemma-on-my-laptop-44fd</link>
      <guid>https://dev.to/arnulfo_07/ventana-seca-finding-the-dry-hour-to-get-outside-in-panamas-rainy-season-with-gemma-on-my-laptop-44fd</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;October is the heart of the rainy season in Panama City. Most afternoons, the sky opens up somewhere between 1 and 5 p.m. A weather app gives me a percentage for every hour and leaves the decision to me, so I end up checking the screen over and over, or I just stay inside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ventana seca&lt;/strong&gt; ("dry window") answers one question: &lt;em&gt;when today can I be outside without getting soaked or cooked?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I don't have a free weekday morning for a trail. My chance to be outside is my daily route: on foot near Plaza 5 de Mayo at 7:30 a.m., arriving in Costa del Este at 8:00, lunch at noon, heading out at 4:00. So the main mode takes that route, checks the forecast for each stop, and tells me which stop is worth spending time outdoors, plus what to carry for the whole day. On weekends, a second mode looks for the best window at a park like the Parque Natural Metropolitano or the Cinta Costera.&lt;/p&gt;

&lt;p&gt;You run one command in the morning, read one card, and optionally save a calendar event that alerts you 30 minutes before. Then the phone goes back in your pocket. The screen should be the shortest part of the experience.&lt;/p&gt;

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

&lt;p&gt;This is my real route for Tuesday, October 6, with the real forecast and Gemma choosing on my laptop:&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%2F4wxw9x88kl3t1027zeq9.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%2F4wxw9x88kl3t1027zeq9.png" alt="Ventana seca route card: Plaza 5 de Mayo at 7:30 a.m., Costa del Este at 8:00 a.m., 12:00 p.m. and 4:00 p.m.; Gemma picks 8:00 a.m." width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 7:30 and 8:00 stops look dry (20% and 18%). Noon brings a 66% chance of rain with a 39 °C heat index, and 4:00 p.m. is at 88%. Gemma picks 8:00 a.m. in Costa del Este, and the raincoat goes on the list from the morning because of the afternoon.&lt;/p&gt;

&lt;p&gt;Here is the same route when I write, in my own words, that I want to walk during lunch:&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%2Fmb6dtcyc9oyu2c61z2h4.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%2Fmb6dtcyc9oyu2c61z2h4.png" alt="Same route with a lunch preference: Gemma picks 12:00 p.m. and warns about rain and heat; the code adds an " width="799" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gemma respects the time I asked for and warns me about the rain and heat. The red &lt;strong&gt;Ojo&lt;/strong&gt; ("heads up") line is not written by the model. The code adds it whenever the chosen window scores poorly. I'll explain why below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yosef7/ventana-seca/blob/main/demo/ventana-seca-demo.mp4" rel="noopener noreferrer"&gt;Watch the 31-second demo video&lt;/a&gt;. It has no narration and shows the command and the resulting card for three scenes. The interface is in Spanish, because the people I built it for speak Spanish.&lt;/p&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/yosef7" rel="noopener noreferrer"&gt;
        yosef7
      &lt;/a&gt; / &lt;a href="https://github.com/yosef7/ventana-seca" rel="noopener noreferrer"&gt;
        ventana-seca
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Find the rain-free window to go outside in Panama City, chosen by Gemma running locally. DEV Hacktoberfest 2026 · Week 1: Touch Grass
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Ventana seca&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;En octubre llueve en Ciudad de Panamá casi todas las tardes. &lt;strong&gt;Ventana seca&lt;/strong&gt; mira el pronóstico por hora de los próximos días, calcula los tramos de luz con menos lluvia, calor y sol fuerte para salir a caminar o correr, y deja que &lt;strong&gt;Gemma&lt;/strong&gt;, un modelo de pesos abiertos que corre en tu propio equipo con Ollama, escoja uno según lo que tú le pidas. El resultado es una tarjeta de un vistazo y un recordatorio en el calendario que avisa media hora antes, para que guardes el teléfono y salgas.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In English.&lt;/strong&gt; &lt;em&gt;Ventana seca&lt;/em&gt; ("dry window") finds the best rain-free window to go outside in Panama City during the rainy season. It scores hourly forecasts from Open-Meteo, lets Google's open-weight &lt;strong&gt;Gemma&lt;/strong&gt; model, running locally through Ollama, pick one of those windows based on what you ask for in plain words, and writes a one-glance card plus…&lt;/p&gt;
&lt;/blockquote&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yosef7/ventana-seca" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT license, Python standard library only (plus &lt;code&gt;pytest&lt;/code&gt; for the 25 tests). Setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/yosef7/ventana-seca.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ventana-seca
uv &lt;span class="nb"&gt;sync
&lt;/span&gt;ollama pull gemma4:e2b
uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; ventana_seca &lt;span class="nt"&gt;--ruta&lt;/span&gt; &lt;span class="s2"&gt;"7:30 5 de mayo, 8:00 costa del este, 12 pm costa del este, 4 pm costa del este"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The route is saved, so the next mornings it's just &lt;code&gt;uv run python -m ventana_seca --mi-ruta&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The split: code calculates, the model interprets.&lt;/strong&gt; Numbers are where a small model is most likely to slip, so the code handles all of them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Forecast.&lt;/strong&gt; Ventana seca asks &lt;a href="https://open-meteo.com/" rel="noopener noreferrer"&gt;Open-Meteo&lt;/a&gt; for hourly rain probability, millimeters, heat index, UV and daylight. It asks for the coordinates of the &lt;em&gt;place&lt;/em&gt; (a park, a plaza), never mine, and keeps a local copy so it still works without signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoring, no AI.&lt;/strong&gt; Each window starts at 100 points and loses points for rain probability, expected millimeters, heat index above 32 °C and UV above 7. The code keeps up to five daylight windows, at most two per day and at least three hours apart, so there are real alternatives (morning vs. late afternoon). In route mode, each stop of my day is a candidate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choice, with Gemma.&lt;/strong&gt; &lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma 4 E2B&lt;/a&gt; runs locally through &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; (the 4.6 GB Q4_K_M build, on a MacBook with 8 GB of RAM). It receives the candidates with their forecast and whatever I wrote: &lt;em&gt;"quiero caminar en la hora del almuerzo"&lt;/em&gt;, &lt;em&gt;"voy con mi hija de 6 años, que no aguanta el calor"&lt;/em&gt;. Its reply is constrained with Ollama's structured output: a JSON Schema whose &lt;code&gt;ventana&lt;/code&gt; field only accepts the labels of the computed windows, and whose &lt;code&gt;llevar&lt;/code&gt; (things to carry) field only accepts items from a closed list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation and fallback.&lt;/strong&gt; If Ollama isn't running, or the answer is invalid, a fixed rule picks the highest score. The tool always answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-negotiables in code.&lt;/strong&gt; Water always; raincoat at 30% rain or more; cap and sunscreen at UV 6+; repellent on forest trails; a flashlight if the window touches darkness. In route mode they're computed for the whole day.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What testing with the real forecast taught me.&lt;/strong&gt; The first version worked, and then the real runs showed me five problems. Each one changed the design:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I saw&lt;/th&gt;
&lt;th&gt;What I changed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;With Gemma 4's thinking mode on, a choice took &lt;strong&gt;38 to 114 seconds&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Picking among five options doesn't need long reasoning: &lt;code&gt;think: false&lt;/code&gt; brought it down to &lt;strong&gt;7–9 seconds&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma called a &lt;strong&gt;53% chance of rain&lt;/strong&gt; "poca probabilidad" (low probability)&lt;/td&gt;
&lt;td&gt;The code now adds the &lt;strong&gt;Ojo&lt;/strong&gt; line for any window scoring under 60, whatever the model says&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma wrote "la opción B" in its explanation, a letter the card never shows&lt;/td&gt;
&lt;td&gt;Gemma now chooses among readable labels like &lt;em&gt;"mar 6 oct, 12:00 p. m. en Costa del Este"&lt;/em&gt;, so there's no internal code to leak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asked for lunch, it picked 8:00 a.m. without saying why it rejected noon&lt;/td&gt;
&lt;td&gt;New instruction: if the requested time exists, honor it and warn clearly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;It offered 7:00–8:00 and 8:00–9:00 as different options&lt;/td&gt;
&lt;td&gt;Windows on the same day must be three hours apart&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern I ended up with: &lt;strong&gt;the model is good at understanding what I want, and the code is in charge of the facts and the safety warnings.&lt;/strong&gt; Every number on the card comes from the forecast, never from the model.&lt;/p&gt;

&lt;p&gt;The full list of decisions is in &lt;a href="https://github.com/yosef7/ventana-seca/blob/main/docs/arquitectura.md" rel="noopener noreferrer"&gt;docs/arquitectura.md&lt;/a&gt;, and every test run is in &lt;a href="https://github.com/yosef7/ventana-seca/blob/main/docs/validacion.md" rel="noopener noreferrer"&gt;docs/validacion.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking It Outside
&lt;/h2&gt;

&lt;p&gt;On Tuesday, October 6, I followed my usual route with the card from the demo above. Here is the forecast next to what actually happened:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stop&lt;/th&gt;
&lt;th&gt;Forecast&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;7:30 a.m. · Plaza 5 de Mayo&lt;/td&gt;
&lt;td&gt;20% · 31 °C · dry&lt;/td&gt;
&lt;td&gt;Dry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:00 a.m. · Costa del Este&lt;/td&gt;
&lt;td&gt;18% · 31 °C · dry&lt;/td&gt;
&lt;td&gt;Dry under a gray sky&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:00 p.m. · Costa del Este&lt;/td&gt;
&lt;td&gt;66% · 39 °C · rain and heat&lt;/td&gt;
&lt;td&gt;It rained&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:00 p.m. · Costa del Este&lt;/td&gt;
&lt;td&gt;88% · 31 °C · rain&lt;/td&gt;
&lt;td&gt;It rained&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The day went the way the card said it would. The stop Gemma picked, 8:00 a.m. in Costa del Este, was dry. I took this photo at 8:15:&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%2Fev0py4zpaxhcdz961pf3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fev0py4zpaxhcdz961pf3.jpg" alt="Costa del Este at 8:15 a.m. on October 6: overcast sky, palm trees and dry pavement" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then noon arrived as forecast. At 12:43 p.m., through a window in Costa del Este, the rain was streaking the glass and the hills in the distance had disappeared:&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%2F53wbzuo53kea3dqbm2hx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F53wbzuo53kea3dqbm2hx.jpg" alt="Costa del Este at 12:43 p.m. on October 6, seen through a window: rain on the glass and a hazy horizon" width="800" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The raincoat, which the card put on the list in the morning because of the afternoon, was needed twice. At 5:52 p.m. the pavement was still wet:&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%2F51jvu165cepbxqyvs3le.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F51jvu165cepbxqyvs3le.jpg" alt="Costa del Este at 5:52 p.m. on October 6: wet pavement, traffic and a heavy gray sky" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One honest detail: when I checked Open-Meteo's hourly record for the same coordinates that night, it showed the storm between 11 a.m. and 1 p.m. (8.3 mm at noon) and 0 mm at 4 p.m., even though I saw rain in the afternoon. The record is a model estimate for a grid cell several kilometers wide, and a local afternoon shower can fall outside it. The full comparison is in &lt;a href="https://github.com/yosef7/ventana-seca/blob/main/docs/validacion.md#3-prueba-afuera-ruta-del-martes-6-oct-2026" rel="noopener noreferrer"&gt;docs/validacion.md&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;What I write about my day stays on my laptop.&lt;/strong&gt; The preference text can be personal: when you leave work, who you're going out with. The saved route is literally my daily location pattern. With Gemma running locally, none of that goes to a server. The only request that leaves the computer is the forecast, and it carries the coordinates of a public place, not my GPS, with no account and no API key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It costs nothing to run every morning.&lt;/strong&gt; No per-request fee, no quota, no subscription. That matters for a tool you're supposed to use daily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I could see and change how the model behaves.&lt;/strong&gt; Because the model and the runtime are open, I measured the thinking mode, turned it off, and constrained the output with a schema. When the model softened a 53% rain chance, I could build a guardrail around its exact behavior instead of hoping a hosted API wouldn't change under me. The model is also swappable with &lt;code&gt;--modelo&lt;/code&gt;, for example &lt;code&gt;gemma4:e4b&lt;/code&gt; on a machine with more memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It works where the signal doesn't.&lt;/strong&gt; Gemma doesn't need the internet, and the forecast is cached. On a trail with no coverage, the card still comes from the last forecast and says when it was saved.&lt;/p&gt;

&lt;p&gt;To be honest about the trade-off: a large hosted model would probably write nicer explanations. But this job needed control and privacy more than eloquence, and the small model's mistakes are exactly the kind the code can catch.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 4 E2B, running locally with Ollama, is the decision-maker at the core of Ventana seca. It interprets a free-text preference, chooses among computed windows through a constrained JSON Schema, and writes the explanation on the card. Its behavior shaped the design: thinking mode off for speed, readable labels instead of letters, and a code-side warning when it understates risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI assistance disclosure:&lt;/strong&gt; I built Ventana seca with Claude Code (Claude Opus 5.5) as my coding agent: it implemented the code, tests and documentation, generated the demo, and drafted this post from my route and the recorded test results. I picked the idea, supplied my real route, reviewed the results and tested it on my own commute.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>hacktoberfest</category>
      <category>ai</category>
    </item>
    <item>
      <title>Sendero: a local catechesis record with open AI for a chapel community in San Miguelito, Panama</title>
      <dc:creator>Arnulfo</dc:creator>
      <pubDate>Sun, 04 Oct 2026 14:51:26 +0000</pubDate>
      <link>https://dev.to/arnulfo_07/sendero-helping-noris-prepare-each-childs-next-step-with-local-open-ai-4gl3</link>
      <guid>https://dev.to/arnulfo_07/sendero-helping-noris-prepare-each-childs-next-step-with-local-open-ai-4gl3</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Sendero&lt;/strong&gt; is a catechesis record built for the community of the &lt;strong&gt;Capilla Nuestra Señora de Lourdes&lt;/strong&gt; in Valle de Urraca, San Miguelito, Panama. The chapel's catechists accompany children through several stages of formation, and they need one reliable place to record each child's journey: enrollment, attendance, the formation received, the requirements met, and each change of stage.&lt;/p&gt;

&lt;p&gt;Sendero turns that record into preparation for the next meeting. Its Spanish interface brings together periods, groups, participant profiles, multiple responsible adults, attendance, observations, stage requirements and a chronological history.&lt;/p&gt;

&lt;p&gt;The open AI feature is at the start of each child's page: &lt;strong&gt;Preparar acompañamiento&lt;/strong&gt;. An open-weight model selects and prioritizes two or three reviewed preparation actions and a question for the child's responsible adult, using the current requirement states and attendance counts. The proposal is saved with its model and evidence snapshot. If the record changes, Sendero marks it outdated.&lt;/p&gt;

&lt;p&gt;The catechesis team can create a dated period and its groups, enroll a participant with multiple responsible adults, record attendance and class topics under that enrollment, and consult the complete history. Existing profiles survive the schema upgrade; new enrollments do not duplicate the participant. A catechist can mark a current AI proposal as reviewed, which records the review without changing requirements or stages.&lt;/p&gt;

&lt;p&gt;The goal is deliberately modest: help the team arrive at the next meeting knowing what to review, while every decision about a child's progress remains with the catechists.&lt;/p&gt;

&lt;p&gt;Sendero is intended to be handed over to the chapel community. So far I have tested the working prototype with fictional records only. Reviewing the actual stage names and requirements with the catechesis team is the next acceptance step; I do not yet have their feedback to report.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/yosef7/sendero-catequesis/blob/main/demo/sendero-demo.mp4" rel="noopener noreferrer"&gt;Watch the 49-second demonstration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The recording shows the complete workflow: login with a fictional demo code, period and group creation, a participant with two responsible adults, attendance and a class topic, an observation, a real local model proposal, human review, and persistence after reload. All displayed records are fictional. The application is in Spanish; the video has no narration, and the wait while the local model generates its proposal is shown in real time.&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%2Fnlufdgyoyfqhkoqthdl9.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%2Fnlufdgyoyfqhkoqthdl9.png" alt="Sendero periods and groups" width="800" height="500"&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%2Fkgfy2l1dmv0utefck0oh.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%2Fkgfy2l1dmv0utefck0oh.png" alt="Sendero's local AI preparation proposal, with attendance and requirement counts" width="800" height="1304"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/yosef7/sendero-catequesis" rel="noopener noreferrer"&gt;Source code, setup instructions and MIT license&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The model has its own license: the &lt;a href="https://huggingface.co/Qwen/Qwen2.5-Coder-3B-Instruct" rel="noopener noreferrer"&gt;Qwen2.5-Coder-3B-Instruct model card&lt;/a&gt; identifies it as &lt;code&gt;qwen-research&lt;/code&gt;. The application's MIT license does not change those model terms.&lt;/p&gt;

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

&lt;p&gt;The application uses Python, Flask, SQLite, plain JavaScript and Ollama. I chose the available local &lt;code&gt;qwen2.5-coder:3b&lt;/code&gt; model for the prototype and kept the adapter configurable through &lt;code&gt;OLLAMA_MODEL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The modules separate the interface, HTTP API, formation rules, persistence and local inference. SQLite foreign keys and transactions keep the records consistent; stage advancement also checks the expected current stage to reject a repeated transition. Versioned migrations add saved AI proposals, periods, groups, enrollments, responsible adults and human review without replacing existing child records. An invalid initial enrollment rolls back the entire new profile. Attendance must belong to the participant and fall within the enrollment period.&lt;/p&gt;

&lt;p&gt;The model receives a small context: numbered requirements and their completion states, recorded-class and attendance counts, the current stage identifier and whether another stage exists. Names, contacts, class topics and free-text observations stay out of the model request.&lt;/p&gt;

&lt;p&gt;My first free-text generation was structurally valid but suggested details not supported by the record. I changed the design: the JSON Schema supplies an allowed catalog of actions and questions, and the server validates membership and rejects duplicate actions before saving. The model can choose and prioritize within that catalog; it cannot introduce a new requirement through its response.&lt;/p&gt;

&lt;p&gt;That is a deliberate limit on generative freedom. Sendero never treats AI output as proof that a requirement is complete, and the model has no tools to write to the formation record.&lt;/p&gt;

&lt;p&gt;The expanded workflow passes &lt;strong&gt;26 automated tests&lt;/strong&gt;. The final Chromium run completed login, period and group creation, enrollment with two responsible adults, attendance and topic entry, local inference, review, reload and a mobile contact edit with zero JavaScript errors. Four views at both 390 and 320 pixels had no horizontal overflow. Those are emulated mobile checks, not a test on a physical phone.&lt;/p&gt;

&lt;p&gt;In the recorded run, the real Ollama request returned a valid proposal in &lt;strong&gt;17.79 seconds&lt;/strong&gt;, with the model not yet loaded in memory; an earlier run with the model already loaded took 3.34 seconds. These observations describe this machine and those requests, not a performance guarantee. The &lt;a href="https://github.com/yosef7/sendero-catequesis/blob/main/demo/validacion-v1.json" rel="noopener noreferrer"&gt;browser validation&lt;/a&gt; records the final checks. The previous &lt;a href="https://github.com/yosef7/sendero-catequesis/blob/main/demo/evaluacion-ia.json" rel="noopener noreferrer"&gt;three-case evaluation&lt;/a&gt; remains available as evidence from the earlier prototype.&lt;/p&gt;

&lt;p&gt;The implementation follows &lt;a href="https://docs.ollama.com/capabilities/structured-outputs" rel="noopener noreferrer"&gt;Ollama’s structured-output contract&lt;/a&gt;. The earlier structural-validation approach was informed by &lt;a href="https://dev.to/devshakib/structured-output-from-llms-a-retry-repair-loop-your-parser-never-sees-through-3b0b"&gt;DevShakib's article on structured output&lt;/a&gt; and &lt;a href="https://dev.to/mukundakatta/rule-based-llm-output-validation-reject-bad-responses-before-they-reach-your-users-if0"&gt;Mukunda Katta's explanation of rule-based output validation&lt;/a&gt;. A valid response is still a proposal for the catechists to review, not a guarantee that its priorities are useful.&lt;/p&gt;

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

&lt;p&gt;This project concerns children, so local operation is a practical design choice. Once the dependencies and model are downloaded, recording and inference can run without an external inference service, a subscription or a paid API key. A parish community can keep its own records on its own computer, and the AI request carries only numerical progress information that never leaves that machine.&lt;/p&gt;

&lt;p&gt;Open local inference also let me inspect the request, change the response contract after an unsatisfactory result, and repeat the experiment. The community can try a different local model through the same adapter, respecting its license, without rewriting the registration and formation modules. I have not fine-tuned the model or claimed that this is impossible with every closed API.&lt;/p&gt;

&lt;p&gt;The current version runs on one trusted computer with a single shared access code. It includes local access protection, a downloadable SQLite backup and printable records. Individual accounts for each catechist, retention and consent procedures, testing on a physical phone and acceptance testing with the catechesis team remain future work. The demonstration uses fictional data throughout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI assistance disclosure:&lt;/strong&gt; I used Codex to help implement, test and document Sendero, and to draft this article from the stated need and recorded validation results. I used Claude Code to revise the article and documentation for the community-focused version and to re-record the demonstration.&lt;/p&gt;

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