<?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: chintey</title>
    <description>The latest articles on DEV Community by chintey (@chintey).</description>
    <link>https://dev.to/chintey</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%2F4165828%2F8927e5ec-5b5d-4ae3-a0e4-5b37076af2d5.png</url>
      <title>DEV Community: chintey</title>
      <link>https://dev.to/chintey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chintey"/>
    <language>en</language>
    <item>
      <title>There's no fall foliage in Phnom Penh, so I built an offline AI that sends me out to flip buckets</title>
      <dc:creator>chintey</dc:creator>
      <pubDate>Fri, 09 Oct 2026 01:25:12 +0000</pubDate>
      <link>https://dev.to/chintey/theres-no-fall-foliage-in-phnom-penh-so-i-built-an-offline-ai-that-sends-me-out-to-flip-buckets-m22</link>
      <guid>https://dev.to/chintey/theres-no-fall-foliage-in-phnom-penh-so-i-built-an-offline-ai-that-sends-me-out-to-flip-buckets-m22</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;Pick any October afternoon in Phnom Penh. Around four or five o'clock the sky dumps what feels like a month of rain in twenty minutes, then stops like nothing happened. In a lot of Touch Grass entries this is where you'd go outside and look at leaves turning orange.&lt;/p&gt;

&lt;p&gt;We don't have that. I'm a student in Phnom Penh and we have two seasons: hot, and hot with a side of dengue. After every one of those downpours, every bucket, flower-pot saucer, old tire and coconut shell on my street turns into a mosquito daycare with free rent. &lt;em&gt;Aedes&lt;/em&gt; mosquitoes lay their eggs in exactly that kind of small, still, clean-ish water, and the eggs glue themselves to the container walls.&lt;/p&gt;

&lt;p&gt;The official advice is boring and correct: after it rains, &lt;strong&gt;tip it, scrub it, cover it, toss it&lt;/strong&gt;. Everyone in Cambodia has heard it. Almost nobody actually walks around the house doing it.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;MusPatrol&lt;/strong&gt; (មូស Patrol; &lt;em&gt;mus&lt;/em&gt; is Khmer for mosquito). It's an installable web app for a &lt;strong&gt;10-minute walk after the rain&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The home screen checks Open-Meteo: did it rain in the last 3 hours, and how long until sunset? That's your patrol window.&lt;/li&gt;
&lt;li&gt;You walk your yard, balcony, or soi and snap a water-holding thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLIP, running inside your browser&lt;/strong&gt;, suggests the top three container types. You tap the right one, then tap &lt;strong&gt;"Water inside?"&lt;/strong&gt; yes or no.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;TypeScript rules table&lt;/strong&gt; (not the AI) prints the order: &lt;strong&gt;TIP / SCRUB / COVER / TOSS / CHANGE / REPORT&lt;/strong&gt;, in English plus a short Khmer line.&lt;/li&gt;
&lt;li&gt;You do the thing. Phone back in the pocket. Next container.&lt;/li&gt;
&lt;li&gt;At the end you get a tally and a cheeky four-line patrol report. An open-weight LLM (Gemma 4 E2B, on-device) is &lt;em&gt;allowed&lt;/em&gt; to write that report if your browser can run it. Otherwise a template writes it. Either way, the model never decides the health action.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's for families, kids who like tallies, balcony people, and anyone who has nodded at "tip, toss, cover" for ten rainy seasons and still has a saucer of green water under the chilli plant.&lt;/p&gt;

&lt;p&gt;The whole point is that &lt;strong&gt;the screen is the shortest part&lt;/strong&gt;: snap, confirm, read one big word, pocket it. A screen-on timer runs during the patrol so the app keeps itself honest about that.&lt;/p&gt;

&lt;p&gt;And here's the honest part up front, because judges will ask: &lt;strong&gt;I have not done the outdoor field test yet.&lt;/strong&gt; Everything below was built and measured on a cloud VM, not on a wet street. So the demo uses ten openly licensed photos from Wikimedia Commons, and you can run the full loop with them in under a minute.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live app: &lt;a href="https://muspatrol.vercel.app" rel="noopener noreferrer"&gt;https://muspatrol.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No video this time, so here's the fastest way in: open the link, tap &lt;strong&gt;Try with sample photos&lt;/strong&gt;, and pick a picture. No camera, no account, no API key. It works on desktop too.&lt;/p&gt;

&lt;p&gt;What happens on your first tap: your browser downloads the CLIP vision tower (about &lt;strong&gt;53.3 MB&lt;/strong&gt;) plus the ONNX Runtime WASM (about &lt;strong&gt;25.6 MiB&lt;/strong&gt;), then classifies the photo locally. After that, the browser cache keeps them.&lt;/p&gt;

&lt;p&gt;The loop: home with the patrol window → CLIP's top three guesses → the order card.&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%2Fweugrp69lroqrr7djdnj.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%2Fweugrp69lroqrr7djdnj.png" alt="Three MusPatrol screens side by side: the home screen with the Phnom Penh patrol window, the " width="799" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Judge mode and the report: ten Commons photos, then a report that only mentions what you actually logged.&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%2Fqhn0ot9c46z16hranq4h.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%2Fqhn0ot9c46z16hranq4h.png" alt="Two MusPatrol screens: a grid of Wikimedia Commons sample photos (a hand-washing bucket, a washbasin, a recycled pot, tires used as planters), and the patrol report with tally and the optional Gemma 4 E2B card" width="800" height="877"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Things to try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;basin.jpg&lt;/code&gt; and &lt;code&gt;bowl.jpg&lt;/code&gt;&lt;/strong&gt; are the two photos CLIP gets wrong on its first guess. The right answer is still in the top three, so tap it and watch the rules table take over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;grass.jpg&lt;/code&gt;&lt;/strong&gt; gets the only order the challenge really wants: &lt;em&gt;"Nice. Touch that grass."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;About&lt;/strong&gt; page lists every photo's author and license.&lt;/li&gt;
&lt;/ul&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/Chinteyley" rel="noopener noreferrer"&gt;
        Chinteyley
      &lt;/a&gt; / &lt;a href="https://github.com/Chinteyley/muspatrol" rel="noopener noreferrer"&gt;
        muspatrol
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Offline after-the-rain dengue patrol: open models (CLIP + Gemma 4) in your browser. DEV Hacktoberfest 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;MusPatrol — មូស Patrol&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;There is no fall foliage in Phnom Penh. After a downpour, this installable web app sends you outside for a &lt;strong&gt;10-minute patrol&lt;/strong&gt; of your yard, balcony, or street. You tip, scrub, cover, or toss the small water-holding things where &lt;em&gt;Aedes&lt;/em&gt; mosquitoes breed.&lt;/p&gt;
&lt;p&gt;Snap a container → &lt;strong&gt;CLIP&lt;/strong&gt; in the browser suggests the top 3 types → you confirm and tap “water inside?” → a &lt;strong&gt;TypeScript rules table&lt;/strong&gt; prints the order (TIP / SCRUB / COVER / TOSS / CHANGE / REPORT) in English and a short Khmer line → tally. At the end, &lt;strong&gt;Gemma 4 E2B&lt;/strong&gt; (on-device, WebGPU) may write a cheeky report. If it invents a tire, we throw the text away and use a template. &lt;strong&gt;The model never decides the health action.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Built &lt;strong&gt;9–11 Oct 2026&lt;/strong&gt; for &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05" rel="nofollow"&gt;DEV Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Live demo&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://muspatrol.vercel.app" rel="nofollow noopener noreferrer"&gt;https://muspatrol.vercel.app&lt;/a&gt;&lt;/strong&gt; (static build of &lt;code&gt;main&lt;/code&gt;…&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/Chinteyley/muspatrol" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;App code is MIT. Models, fonts, weather data and photos keep their own licenses (see &lt;code&gt;NOTICE.md&lt;/code&gt;). Measured numbers live in &lt;a href="https://github.com/Chinteyley/muspatrol/blob/main/docs/numbers.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/numbers.md&lt;/code&gt;&lt;/a&gt;, and the empty field-test log lives in &lt;a href="https://github.com/Chinteyley/muspatrol/blob/main/docs/field-test-log.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/field-test-log.md&lt;/code&gt;&lt;/a&gt;. I'll fill that in after a real walk, not before.&lt;/p&gt;

&lt;p&gt;Stack: Vite + React + TypeScript, Bun, &lt;code&gt;vite-plugin-pwa&lt;/code&gt;, transformers.js, MediaPipe LLM Inference, Open-Meteo, IndexedDB. It's a static site with no backend.&lt;/p&gt;

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

&lt;p&gt;Full disclosure: I built this with a lot of help from a coding agent (the challenge allows AI use, and I'd rather say so than have you guess). The design rule I cared most about is the first one below.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The rules are code. The model is the narrator.
&lt;/h3&gt;

&lt;p&gt;Dengue advice is a health action, and I don't want a chat model improvising one. So the decision is a plain function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/rules/rules.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decideOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;labelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LabelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;water&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;OrderCard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Label plus "water inside?" goes in, and an order card comes out:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Container&lt;/th&gt;
&lt;th&gt;Order&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;plastic bucket / basin&lt;/td&gt;
&lt;td&gt;TIP + SCRUB (just SCRUB if it's dry, because eggs stick to walls)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flower-pot saucer&lt;/td&gt;
&lt;td&gt;TIP (or fill it with sand)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;old tire&lt;/td&gt;
&lt;td&gt;TOSS (recycle it, or drill drain holes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;coconut shell / cup / bottle&lt;/td&gt;
&lt;td&gt;TOSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;water jar (ពាង) / barrel&lt;/td&gt;
&lt;td&gt;COVER + SCRUB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ant-trap bowl, pet bowl&lt;/td&gt;
&lt;td&gt;CHANGE the water&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spirit-house offering water&lt;/td&gt;
&lt;td&gt;CHANGE, respectfully. Never toss the offering.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gutter / tarp puddle&lt;/td&gt;
&lt;td&gt;TIP / clear it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;roadside drain&lt;/td&gt;
&lt;td&gt;REPORT to the sangkat (low priority for &lt;em&gt;Aedes&lt;/em&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;grass / dry ground&lt;/td&gt;
&lt;td&gt;"Nice. Touch that grass."&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The spirit-house row matters to me. An AI that tells you to dump an offering is how you get uninvited from your grandma's house. The table is unit-tested, and the model never touches it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Teaching CLIP what a Cambodian yard looks like
&lt;/h3&gt;

&lt;p&gt;Vision is &lt;a href="https://huggingface.co/Xenova/clip-vit-base-patch32" rel="noopener noreferrer"&gt;&lt;code&gt;Xenova/clip-vit-base-patch32&lt;/code&gt;&lt;/a&gt; via &lt;strong&gt;transformers.js 4.3.1&lt;/strong&gt;, vision tower only, &lt;code&gt;q4f16&lt;/code&gt;: &lt;strong&gt;53.3 MB&lt;/strong&gt;. The trick that keeps it phone-sized is that the &lt;strong&gt;text side is precomputed at build time&lt;/strong&gt;. &lt;code&gt;scripts/embed-labels.ts&lt;/code&gt; embeds a few English prompts for each of 11 labels, averages them, L2-normalises the result, and writes a &lt;strong&gt;118 KiB&lt;/strong&gt; JSON file. The browser never downloads the text tower. Classifying is one image embedding and eleven cosine similarities.&lt;/p&gt;

&lt;p&gt;On the 10 Commons sample photos (Node, CPU, no GPU):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;top-1&lt;/th&gt;
&lt;th&gt;top-3&lt;/th&gt;
&lt;th&gt;mean latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10 Commons photos&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8 / 10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10 / 10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;43 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two misses are honestly kind of fair. A plastic washbasin came out as "pet bowl" first, and a dog bowl came out as "bucket" first. That's exactly why the UI never auto-picks. It always shows three chips, and you're the one standing next to the thing.&lt;/p&gt;

&lt;p&gt;In headless Chrome on the build VM, the first load (fetching the model plus WASM) took 26–28 s, and one classify after that took 777 ms. A later re-run on a different VM loaded in about 8 s, so first-load time depends a lot on network and CPU. &lt;strong&gt;I haven't measured any of this on a phone yet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ten photos is a tiny eval set and I know it. I planned to shoot my own yard photos for a proper eval. That's part of the field test that hasn't happened yet, so 8/10 on Commons is the only accuracy number I'll put my name on.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Gemma 4 E2B in a browser tab (wired up, not benchmarked)
&lt;/h3&gt;

&lt;p&gt;The narrator is &lt;strong&gt;Gemma 4 E2B&lt;/strong&gt;, Apache-2.0, using the ungated web build &lt;a href="https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm" rel="noopener noreferrer"&gt;&lt;code&gt;gemma-4-E2B-it-web.task&lt;/code&gt;&lt;/a&gt; through &lt;strong&gt;MediaPipe LLM Inference&lt;/strong&gt; (&lt;code&gt;@mediapipe/tasks-genai&lt;/code&gt; 0.10.29) on WebGPU. The file is &lt;strong&gt;2,003,697,664 bytes&lt;/strong&gt; (about 2.0 GB), downloaded only if you tap &lt;strong&gt;Load Gemma &amp;amp; rewrite&lt;/strong&gt; on the report screen.&lt;/p&gt;

&lt;p&gt;The prompt uses Gemma 4's own turn tokens, because MediaPipe won't wrap them for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`&amp;lt;|turn&amp;gt;system\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;turn|&amp;gt;\n&amp;lt;|turn&amp;gt;user\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;turn|&amp;gt;\n&amp;lt;|turn&amp;gt;model\n`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system prompt says: only mention items in the JSON log, don't change the actions, output JSON, and keep the jokes friendly (evict / daycare / landlord). I don't trust a prompt on its own, so every reply also goes through &lt;code&gt;validateReport()&lt;/code&gt;. If the model mentions a container that isn't in your log ("great job with that tire!" when you logged no tire), or a count that doesn't match, the text gets thrown away and the template report wins. The model is a narrator, not a witness.&lt;/p&gt;

&lt;p&gt;Now the honest bit: &lt;strong&gt;the build environment had no WebGPU&lt;/strong&gt; (&lt;code&gt;navigator.gpu&lt;/code&gt; doesn't exist in headless Chrome on a GPU-less VM). So I have &lt;strong&gt;not&lt;/strong&gt; run Gemma 4 here, and I have no tokens-per-second figure for a phone or a laptop. I didn't want to make one up. The code path is there, the app checks &lt;code&gt;navigator.gpu&lt;/code&gt; and device memory before offering the download, and the default report is the deterministic template. If you have a WebGPU laptop, you're the benchmark. Please tell me in the comments how it went.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Khmer, honestly
&lt;/h3&gt;

&lt;p&gt;Order cards carry short Khmer lines next to the English ones. I'm not shipping long, confident-sounding Khmer that nobody has checked, so every string is listed in &lt;a href="https://github.com/Chinteyley/muspatrol/blob/main/docs/khmer-review.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/khmer-review.md&lt;/code&gt;&lt;/a&gt; and marked &lt;em&gt;needs review&lt;/em&gt;. The neighbour notes ("Bong, your old tire is running a mosquito daycare…") are English, with romanized placeholders where the Khmer will go after a native-speaker pass.&lt;/p&gt;

&lt;p&gt;Shout-out to &lt;a href="https://huggingface.co/mengsay/Gemma-3-Tonsai-1B-v0.1" rel="noopener noreferrer"&gt;Tonsai-1B&lt;/a&gt;, a Cambodian developer's Khmer continued-pretrain of Gemma 3 1B. It isn't in this app, but it's the reason I believe "open weights + Khmer" has a future.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Offline by design
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;vite-plugin-pwa&lt;/code&gt; precaches the app shell: &lt;strong&gt;28 entries, about 1,096 KiB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The ONNX Runtime WASM is &lt;strong&gt;26,861,777 bytes&lt;/strong&gt;, which is over Workbox's 8 MB precache cap. So it isn't precached, and the browser caches it after the first classify instead.&lt;/li&gt;
&lt;li&gt;Open-Meteo responses use stale-while-revalidate and also get stored in IndexedDB, so the patrol-window card is designed to fall back to the last forecast when there's no signal.&lt;/li&gt;
&lt;li&gt;Photos stay on the device. Nothing gets uploaded. There's no server to upload to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Storms knock out 4G right when the patrol should start, so offline was the plan from day one. I haven't screenshotted an airplane-mode patrol on a real phone yet, though. That's on the field-test list.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Hosting
&lt;/h3&gt;

&lt;p&gt;The site is a static &lt;code&gt;dist/&lt;/code&gt; (28.9 MiB, mostly that WASM) served from Vercel and built from &lt;code&gt;main&lt;/code&gt;. There's no backend, no keys, and no database.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. What didn't work, and where I stopped
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CLIP's first guess isn't good enough to act on.&lt;/strong&gt; It misses top-1 on 2 of the 10 sample photos, so the app never auto-picks. The three-chip picker plus one "water inside?" tap is the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Gemma in the browser" really means "Gemma on a WebGPU browser."&lt;/strong&gt; WebGPU is the gate, so the template &lt;em&gt;is&lt;/em&gt; the product, and Gemma is the bonus.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No original yard photos yet.&lt;/strong&gt; It's Commons or nothing until I actually walk outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not built:&lt;/strong&gt; push notifications, maps, accounts, larvae detection, ElevenLabs voice lines, and a server-side "second look" model. Next time I want Gemma to &lt;em&gt;see&lt;/em&gt; the photo too, and that's probably a native build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Taking it outside
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Field test: not done yet.&lt;/strong&gt; No patrol has happened on a real street, so I have no screen-on time from the field, no tally of containers found, and no neighbour reactions. I won't invent any.&lt;/p&gt;

&lt;p&gt;What you &lt;em&gt;can&lt;/em&gt; do right now: open &lt;a href="https://muspatrol.vercel.app" rel="noopener noreferrer"&gt;https://muspatrol.vercel.app&lt;/a&gt;, tap &lt;strong&gt;Try with sample photos&lt;/strong&gt;, run the ten Commons pictures, watch CLIP fumble the washbasin, tap the right chip, get &lt;strong&gt;TIP + SCRUB&lt;/strong&gt;, finish, and read a report that only mentions what you logged.&lt;/p&gt;

&lt;p&gt;When I do the real walk after an afternoon storm, the numbers go into &lt;code&gt;docs/field-test-log.md&lt;/code&gt;. Anything committed after the deadline will be listed in the README, as the rules require.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The photos are of home.&lt;/strong&gt; My yard, my neighbour's tire, the spirit house by the gate. A closed vision API would need every one of those frames sent to someone else's server. With open weights running in the browser tab, they never leave the phone. That's privacy by architecture, not by a policy page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storms knock out the signal right when the patrol starts.&lt;/strong&gt; A cloud API is useless at exactly the moment this app matters. Open models I can cache on the device make offline the default, not a feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0 per patrol.&lt;/strong&gt; A sangkat health volunteer or a school teacher could run this for a whole street in a bad dengue year with no API key, no quota, and no bill. The only cost is one download on Wi-Fi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apache-2.0 means I can ship it and you can change it.&lt;/strong&gt; Gemma 4's license lets me wire the weights into a public PWA. CLIP plus transformers.js let me precompute the text side and ship only the vision half. People in Cambodia are already fine-tuning open models for Khmer (Tonsai on Gemma 3), and that only happens because the weights are open. I don't expect a closed vendor to put Khmer near the top of their roadmap anytime soon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Swappable parts.&lt;/strong&gt; CLIP could become SigLIP inside a single worker file. Gemma could be dropped for the template on a cheap phone, and the app still works, because the part that matters (the rules) isn't the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where closed would have won:&lt;/strong&gt; a big cloud vision model would probably recognise weird containers better than a 53 MB CLIP tower. I still chose open, because I don't want my neighbour's tire on someone else's GPU, and because a dengue tool that needs a credit card and 4G isn't one that a soi in Phnom Penh will actually use.&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 (Apache-2.0) is the on-device narrator via MediaPipe LLM Inference on WebGPU, fenced in by a validator and a deterministic fallback. Honest caveat: it's integrated but not benchmarked, because my build environment had no WebGPU.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;It's probably raining again somewhere in Phnom Penh while you read this. Somewhere a saucer is filling up. Go tip it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not medical advice. Follow the Cambodian Ministry of Health and WHO guidance on dengue.&lt;/em&gt;&lt;/p&gt;

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