<?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: Mauricio Andrade</title>
    <description>The latest articles on DEV Community by Mauricio Andrade (@mauricioandrade).</description>
    <link>https://dev.to/mauricioandrade</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%2F4164493%2F31f3a6d2-2291-4e11-8d7f-e70c282dec5a.jpg</url>
      <title>DEV Community: Mauricio Andrade</title>
      <link>https://dev.to/mauricioandrade</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mauricioandrade"/>
    <language>en</language>
    <item>
      <title>Janela — a local Gemma tells you when to go outside (and why)</title>
      <dc:creator>Mauricio Andrade</dc:creator>
      <pubDate>Wed, 07 Oct 2026 22:28:05 +0000</pubDate>
      <link>https://dev.to/mauricioandrade/janela-a-local-gemma-tells-you-when-to-go-outside-and-why-2j37</link>
      <guid>https://dev.to/mauricioandrade/janela-a-local-gemma-tells-you-when-to-go-outside-and-why-2j37</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 2026 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;In most of Brazil, "I'll go for a run later" isn't about frost or snow. It's about dodging a &lt;strong&gt;UV index of 11 at noon&lt;/strong&gt;, a feels-like of 38 °C at 2 PM, and the &lt;strong&gt;thunderstorm that shows up at 5 PM&lt;/strong&gt; almost every summer afternoon. The weather app has all of that data. It just doesn't tell you &lt;em&gt;when to go&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Hina from &lt;em&gt;Weathering With You&lt;/em&gt; could pray for a sunny window. The rest of us have to read the forecast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Janela&lt;/strong&gt; (Portuguese for &lt;em&gt;window&lt;/em&gt;) does that for you. You give it a city, an activity (run, walk, bike, picnic, gardening, or an outdoor workout at the square's gym), how long you want to be out, and how many days ahead to look. It answers with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;the best time window&lt;/strong&gt;, plus two alternatives, each scored 0–100 with its feels-like temperature, UV, rain chance and wind;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;each day's comfort drawn as a tide&lt;/strong&gt;, so you see at a glance when the day opens up and when it closes;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a short explanation written by Gemma running on my own laptop&lt;/strong&gt;, saying &lt;em&gt;why&lt;/em&gt; that window wins, plus a tiny "touch grass" challenge for when you're out there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It works in Portuguese and English (°F and mph in English), and searches are shareable links.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every activity, in anime terms
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🏃 Run&lt;/th&gt;
&lt;th&gt;🚶 Walk&lt;/th&gt;
&lt;th&gt;🚴 Bike&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flcsakh902ighbsg0mu0s.gif" alt="Chubby Naruto doing the Naruto run, captioned " width="480" height="359"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx5tlcsqep8w2zs4lc688.gif" alt="Zoro from One Piece pausing in an alley, captioned " width="480" height="258"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpihtdi8bhbn6lx264hnh.gif" alt="The Sohoku cycling team from Yowamushi Pedal racing around a curve" width="480" height="270"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;10–22 °C feels-like. Form not included.&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Janela finds the window. Finding the park is on you.&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Pick the cool hour, then draft behind your friends.&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🧺 Picnic&lt;/th&gt;
&lt;th&gt;🌱 Gardening&lt;/th&gt;
&lt;th&gt;💪 Square gym&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx79oju9scnd8c4asryiv.gif" alt="Young Goku from Dragon Ball happily eating from a huge pile of food" width="480" height="360"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3tehubjwa8pe9vgfn5s7.gif" alt="Two hands joining in a hand sign from Jujutsu Kaisen" width="480" height="270"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj31mzdbn6gfncadd3gbm.gif" alt="Rock Lee and Might Guy striking the same pose against the sky" width="480" height="356"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;18–28 °C feels-like. Bring enough for Goku.&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Domain Expansion: Vegetable Garden.&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;The springtime of youth starts at 06:00.&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lain lived in the Wired. Janela is for the rest of us, who occasionally need to log off.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fekmk6pla568yiu5m4zjc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fekmk6pla568yiu5m4zjc.gif" alt="Typing Recife, picking it from the suggestions, choosing Run and 3 days, then the answer: Thursday 06:00–07:00 with Gemma's explanation, the comfort tide for Wednesday to Friday and the table of three windows" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;city + activity ──▶ Spring Boot API ──▶ Open-Meteo (geocoding + hourly forecast)
                         │
                         ▼
                  WindowScorer (plain Java, unit-tested)
                         │  top 3 windows, already ranked
                         ▼
               Ollama up? ── yes ──▶ Gemma writes the explanation
                         └── no ───▶ template writes it instead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Java computes, Gemma explains
&lt;/h3&gt;

&lt;p&gt;This was the one rule I set before writing any code: &lt;strong&gt;the model never picks a time and never does math.&lt;/strong&gt; Java is Senku here: it does the science. Gemma just presents the results.&lt;/p&gt;

&lt;p&gt;Small models (I'm using &lt;code&gt;gemma3:4b&lt;/code&gt;) are great at turning data into friendly sentences and bad at arithmetic. If I asked Gemma "when should I run in Recife tomorrow?" with a raw forecast, it would answer confidently, and sometimes wrongly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqm22qr9uikr8cma6lrmy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqm22qr9uikr8cma6lrmy.gif" alt="Aqua from Konosuba leaning in with a cheerful, overconfident grin" width="546" height="640"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A 4B model doing arithmetic: full confidence, questionable results.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So all the deciding happens in a deterministic domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every daylight hour starts at 100 and loses points for rain chance, feels-like temperature outside the activity's range, and UV or wind above its limits (a run wants 10–22 °C feels-like, a picnic 18–28 °C);&lt;/li&gt;
&lt;li&gt;a window is the best run of consecutive hours covering your duration, and any hour below 40 rules the whole window out;&lt;/li&gt;
&lt;li&gt;the best window of each day is picked before repeating a day, and once the sun has set, "3 days" starts tomorrow instead of wasting today.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that is covered by JUnit tests (118 on the backend), with an injected &lt;code&gt;Clock&lt;/code&gt; so "drop the hours that already passed" is testable too.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pre-digesting the payload for a 4B model
&lt;/h3&gt;

&lt;p&gt;The interesting part was learning what a small model needs to &lt;em&gt;not&lt;/em&gt; make things up. My first prompts sent ISO timestamps and raw numbers, and Gemma would echo &lt;code&gt;2026-10-07T17:00&lt;/code&gt;, call UV 0.6 "high", and praise window #2.&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%2Fjr0ut8e8eiky9vyypeyx.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%2Fjr0ut8e8eiky9vyypeyx.jpg" alt="The " width="676" height="454"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;gemma3:4b looking at UV 0.6, before I pre-digested the payload.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What fixed it was doing the interpreting in Java and letting the model only reword:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"best"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"great"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"day"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Thursday, Oct 8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"06:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"07:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;83&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"comfort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"uvIndex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"uvLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rainChancePercent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rainLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"wind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reasons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"before the day's heat peaks at 13:00"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alternative"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fair"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"day"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Friday, Oct 9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"17:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"18:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;day names and times are &lt;strong&gt;already formatted&lt;/strong&gt; in the reader's language;&lt;/li&gt;
&lt;li&gt;numbers are &lt;strong&gt;rounded and converted&lt;/strong&gt; (°F/mph in English) the way the UI shows them;&lt;/li&gt;
&lt;li&gt;the window's quality, temperature, UV and rain come &lt;strong&gt;with words&lt;/strong&gt; (&lt;code&gt;rating&lt;/code&gt; is the same word the screen shows next to the score, plus &lt;code&gt;comfort&lt;/code&gt;, the WHO UV category and &lt;code&gt;rainLevel&lt;/code&gt;), so Gemma never decides what "hot", "low chance" or "ideal" means;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reasons&lt;/code&gt; are the window's advantages &lt;strong&gt;already worked out in Java&lt;/strong&gt; from the day's outlook; the prompt tells Gemma to reword those and draw no conclusions of its own;&lt;/li&gt;
&lt;li&gt;the score is left out on purpose, because the model kept quoting it;&lt;/li&gt;
&lt;li&gt;there are no ranks: the best window and one alternative go &lt;strong&gt;by name&lt;/strong&gt;, because with a ranked list of three, Gemma mixed up the order, praised the wrong one and wrote "Rank 2" into the text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The prompt then forbids the specific failures I saw in testing: inventing a beach or a park, saying "today" about a window two days out, writing JSON field names into the text, or describing a 33 °C afternoon as "pleasant".&lt;/p&gt;

&lt;p&gt;A prompt is a request, not a guarantee, so &lt;strong&gt;Java also reads the reply&lt;/strong&gt; before showing it. Any time it wasn't given, a 12-hour clock, a field name, an invented beach, "pleasant" for a hot hour, "ideal" for a fair window or a missing 🌿 line gets the reply rejected, and Gemma gets one more try with the mistake named. It usually fixes it, and on my laptop the retry costs 2–4 seconds.&lt;/p&gt;

&lt;p&gt;If the second try still breaks a rule, or Ollama is off or slow (30 s timeout), a template writes the text and the UI labels it as such instead of pretending.&lt;/p&gt;
&lt;h3&gt;
  
  
  Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Java 21, Spring Boot 4, Spring AI 2.0 with the Ollama starter, Clean Architecture (the use case is framework-free), API versioning with Spring Framework 7, Caffeine caches, OpenAPI docs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;code&gt;gemma3:4b&lt;/code&gt; via &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, swappable with one env var&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weather:&lt;/strong&gt; &lt;a href="https://open-meteo.com" rel="noopener noreferrer"&gt;Open-Meteo&lt;/a&gt; forecast and geocoding (open data, no key)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React 19, TypeScript, Vite, TanStack Query, Tailwind 4, shadcn/ui on Base UI, a hand-drawn SVG tide chart&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why open innovation matters here
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It runs on a laptop.&lt;/strong&gt; No API key, no account, no cost per question. Anyone can clone it and have it working in three commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your routine stays with you.&lt;/strong&gt; Where you live and when you go out are exactly the kind of data I don't want to send to an AI cloud. With Gemma local, they don't leave the machine. To be honest about it: the forecast comes from Open-Meteo's open API, so &lt;em&gt;that&lt;/em&gt; request does go out (just coordinates, no profile).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swap the model with one variable.&lt;/strong&gt; &lt;code&gt;OLLAMA_MODEL=gemma3:12b&lt;/code&gt; and you're done. Open weights mean the app isn't tied to one vendor's pricing or deprecation schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works without AI.&lt;/strong&gt; Because the AI only explains and never decides, turning it off loses the nice sentences, not the answer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 3 (4B) runs locally through Ollama and writes every recommendation, in Portuguese or English. The project is built around what a small open model is good at (language) and keeps it away from what it isn't (math and decisions).&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/mauricioandrade" rel="noopener noreferrer"&gt;
        mauricioandrade
      &lt;/a&gt; / &lt;a href="https://github.com/mauricioandrade/janela" rel="noopener noreferrer"&gt;
        janela
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🌿 Find your window to touch grass. Picks the best outdoor time for running, walking or cycling based on heat, UV and rain — explained by a local open-weight model (Gemma via Ollama). Java 21 + Spring AI. Built for Hacktoberfest 2026.
    &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;🌿 Janela&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Find your window to touch grass.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Janela (Portuguese for &lt;em&gt;window&lt;/em&gt;) finds the best time windows in the next few days for an outdoor activity —
running, walking, cycling, a picnic, some gardening, a workout at the square's outdoor gym — based on what actually matters in a tropical climate
heat, UV index, afternoon storms and wind.&lt;/p&gt;
&lt;p&gt;Then a &lt;strong&gt;local open-weight model (Gemma, via Ollama)&lt;/strong&gt; turns those numbers into a short, human recommendation
and a tiny "touch grass" challenge. No API keys, no per-query costs, and it still works if the model is offline.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05" rel="nofollow"&gt;Hacktoberfest 2026 Open-Source AI Challenge: Week 1 — "Touch Grass"&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/mauricioandrade/janela/docs/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmauricioandrade%2Fjanela%2FHEAD%2Fdocs%2Fdemo.gif" alt="Demo: typing Recife, picking Run and 3 days, then the answer — Thursday 06:00–07:00 — with Gemma's explanation, each day's comfort tide and the table of windows" width="94%"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/mauricioandrade/janela/docs/screenshot-light.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmauricioandrade%2Fjanela%2FHEAD%2Fdocs%2Fscreenshot-light.png" alt="Janela in English on desktop: a run in Recife on Thursday 06:00–07:00, score 72 Great, 83°F feels-like, with Gemma's explanation below" width="62%"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a rel="noopener noreferrer" href="https://github.com/mauricioandrade/janela/docs/screenshot-dark-mobile.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmauricioandrade%2Fjanela%2FHEAD%2Fdocs%2Fscreenshot-dark-mobile.png" alt="Janela in Portuguese on mobile, dark theme: picnic windows in Rio de Janeiro" width="30%"&gt;&lt;/a&gt;
&lt;/p&gt;

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

&lt;p&gt;In much of Brazil, "go for a run" isn't about frost. It's about dodging a UV index of 11 at noon
and the thunderstorm that shows up at 5 PM. Weather apps give you the data; Janela tells you…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mauricioandrade/janela" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Weather data by &lt;a href="https://open-meteo.com" rel="noopener noreferrer"&gt;Open-Meteo&lt;/a&gt; (CC BY 4.0) · &lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma&lt;/a&gt; by Google · &lt;a href="https://spring.io/projects/spring-ai" rel="noopener noreferrer"&gt;Spring AI&lt;/a&gt; and &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; · Archivo typeface by Omnibus-Type · country flags from &lt;a href="https://gitlab.com/catamphetamine/country-flag-icons" rel="noopener noreferrer"&gt;country-flag-icons&lt;/a&gt;, Brazilian state flags from Wikimedia Commons. Anime GIFs belong to their respective creators and studios.&lt;/p&gt;

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