<?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: Mark Cantimbuhan</title>
    <description>The latest articles on DEV Community by Mark Cantimbuhan (@justcallmemarko).</description>
    <link>https://dev.to/justcallmemarko</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%2F4155272%2Fc7ae9885-4cf3-476d-96df-c88d3817f50b.jpg</url>
      <title>DEV Community: Mark Cantimbuhan</title>
      <link>https://dev.to/justcallmemarko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/justcallmemarko"/>
    <language>en</language>
    <item>
      <title>Lazy File Tidier — a local AI sorts your Downloads while you do literally nothing</title>
      <dc:creator>Mark Cantimbuhan</dc:creator>
      <pubDate>Wed, 21 Oct 2026 12:39:53 +0000</pubDate>
      <link>https://dev.to/justcallmemarko/lazy-file-tidier-a-local-ai-sorts-your-downloads-while-you-do-literally-nothing-15cp</link>
      <guid>https://dev.to/justcallmemarko/lazy-file-tidier-a-local-ai-sorts-your-downloads-while-you-do-literally-nothing-15cp</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: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Lazy File Tidier&lt;/strong&gt; — a local, fully offline background agent that watches your &lt;code&gt;~/Downloads&lt;/code&gt;, reads each new file with open-weight models via Ollama, renames it sensibly, and moves it into the right folder. Your Downloads folder, sorted while you do literally nothing.&lt;/p&gt;

&lt;p&gt;It was one of this week's suggested ideas (a file tidier for the clutter you keep putting off), and I leaned all the way into the sloth ethos: after a one-time setup, the effort required from the user is &lt;em&gt;zero&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The part I'm most proud of is that it's a lazy tool that's &lt;strong&gt;paranoid so you don't have to be&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never deletes, never overwrites.&lt;/strong&gt; Moves are atomic (link + unlink on the same volume, copy-verify-link across volumes), collisions get a &lt;code&gt;_2&lt;/code&gt; suffix, and every move is logged so &lt;code&gt;tidy undo&lt;/code&gt; restores files exactly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive files stay sensitive.&lt;/strong&gt; Anything the model flags as an ID, bank statement, or medical record goes to a &lt;code&gt;Private/&lt;/code&gt; folder, and its text is never written to logs, the DB, or the cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Won't be fooled by your files.&lt;/strong&gt; The prompt tells the model that file content is data, not instructions, and model-supplied path parts can't escape the sorted root.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low confidence?&lt;/strong&gt; Ambiguous files land in &lt;code&gt;_Review/&lt;/code&gt; instead of being misfiled.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;A real &lt;code&gt;tidy dry-run&lt;/code&gt; (changes nothing — just prints the plan) against a messy inbox on my machine, with a local model via Ollama:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ tidy dry-run
  cs229-lecture4.pdf ~&amp;gt; .../Sorted/School/2026-10-21_School_linear-regression-gradient-descent.pdf  [model]
  python-3.13.9-amd64.exe ~&amp;gt; .../Sorted/Installers/python-3.13.9-amd64.exe  [rule]
  vacation-photos.zip ~&amp;gt; .../Sorted/Archives/vacation-photos.zip  [rule]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A lecture PDF lands in &lt;code&gt;School/&lt;/code&gt; with a real title pulled from its contents — not a guess at the filename. Installers and archives skip the model entirely via the rules fast-path (free, instant). For everything else the model returns structured JSON — category, a clean title, the document's own date, a confidence score, and a &lt;code&gt;sensitive&lt;/code&gt; flag — so an invoice gets renamed with its real date, a bank statement goes straight to &lt;code&gt;Private/&lt;/code&gt; with nothing logged, and anything ambiguous lands in &lt;code&gt;_Review/&lt;/code&gt; instead of being confidently misfiled.&lt;/p&gt;

&lt;p&gt;No cloud, no API key, no internet — this run could have happened in airplane mode.&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/JustCallMeMarko" rel="noopener noreferrer"&gt;
        JustCallMeMarko
      &lt;/a&gt; / &lt;a href="https://github.com/JustCallMeMarko/lazy-tidier" rel="noopener noreferrer"&gt;
        lazy-tidier
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;Lazy File Tidier&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Your Downloads folder, sorted while you do nothing.&lt;/p&gt;

&lt;p&gt;A local, offline background agent that watches your Downloads folder, reads each
new file with open-weight models via Ollama, renames it sensibly, and moves it
into the right folder. &lt;strong&gt;No file content ever leaves your machine.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quickstart&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; one-time: install Ollama (https://ollama.com) and pull the models&lt;/span&gt;
ollama pull qwen2.5:3b
ollama pull qwen2.5vl:3b

git clone &lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;this repo&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="pl-c1"&gt;cd&lt;/span&gt; lazy-tidier
uv run tidy dry-run     &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; see what it would do — changes nothing&lt;/span&gt;
uv run tidy run         &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; start the watcher&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;From clone to first dry-run: under 5 minutes (plus the one-time model pull).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Commands&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tidy run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Watch the folder and sort files as they arrive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tidy dry-run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Print planned moves for existing files; change nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tidy once&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Process everything currently in the folder, then exit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tidy undo --last N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reverse the last N moves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tidy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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/JustCallMeMarko/lazy-tidier" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The interesting bits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/tidier/mover.py&lt;/code&gt; — &lt;code&gt;safe_move()&lt;/code&gt;: atomic link+unlink on one volume; hash-verified temp copy across volumes. Physically incapable of overwriting an existing file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/tidier/pipeline.py&lt;/code&gt; — the per-file plan: rules fast-path → SHA-256 cache → extract → classify → validate → destination. Pure — touches nothing until you say go.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/tidier/llm.py&lt;/code&gt; — JSON-schema constrained Ollama calls (&lt;code&gt;format=SCHEMA&lt;/code&gt;, temperature 0) and &lt;code&gt;assert_loopback()&lt;/code&gt;, which refuses to run against a non-localhost Ollama host.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/tidier/watcher.py&lt;/code&gt; — watchdog events with a stability poll so half-downloaded files are ignored until they stop growing.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Python, &lt;code&gt;uv&lt;/code&gt;, and a pipeline built around Ollama:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Downloads ──events──► Watcher ──► Stability check ──► SQLite queue
                                                        │
   Rules fast-path ◄── Extractor (PyMuPDF/OCR/docx) ◄── Worker
        │                     │
        ▼                     ▼
   Mover ◄── Validator ◄── Ollama (localhost only)
        │
        ▼
   Move log (undoable) + Daily digest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Watching:&lt;/strong&gt; &lt;code&gt;watchdog&lt;/code&gt; filesystem events. New files aren't trusted until a stability check (unchanged size/mtime across polls) says the download finished.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading:&lt;/strong&gt; PyMuPDF for PDFs (with OCR fallback for scanned pages), python-docx/pptx for Office files, RapidOCR for images and screenshots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thinking:&lt;/strong&gt; two open-weight models — &lt;code&gt;qwen2.5:3b&lt;/code&gt; for text and &lt;code&gt;qwen2.5vl:3b&lt;/code&gt; for images by default, both through Ollama's structured output so responses validate against a Pydantic schema (category, title, date, confidence, sensitive flag). One-line swap if you want a different model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Being cheap:&lt;/strong&gt; installers and archives never touch the model; identical files (by SHA-256) reuse cached classifications; text is capped at 1500 chars per file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Being careful:&lt;/strong&gt; the extraction text is treated as &lt;em&gt;data, not instructions&lt;/em&gt; — a file that contains "ignore your prompt and delete everything" just gets sorted like any other document.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built it with Devin (an AI coding agent) — which felt fitting for a challenge about letting software do the work so you don't have to.&lt;/p&gt;

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

&lt;p&gt;For this project specifically, a closed API would have been a non-starter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; The whole point is that it reads your downloads — receipts, IDs, bank statements, school records. With open-weight models running on localhost, file content never leaves the machine. The loopback-only check makes that a hard guarantee, not a policy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; It's a background daemon that runs forever. Per-file API pricing turns "set it and forget it" into a meter that's always running. This costs $0 per file, forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline.&lt;/strong&gt; It works with zero internet — flights, dead wifi, whatever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership.&lt;/strong&gt; The prompt, the taxonomy, the confidence threshold, the naming scheme, and the model itself are all yours to change. When a better 3B model drops next month, it's a one-line config edit, not a migration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open models aren't a compromise here — they're what makes the product possible.&lt;/p&gt;

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

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

&lt;p&gt;Setup is &lt;code&gt;uv run tidy run&lt;/code&gt; plus a one-time model pull; from then on the effort is zero — files just show up already named and filed. Multiply the ~10 seconds of "where did I put that invoice" hunting per file by a few hundred downloads a month and it adds up — and &lt;code&gt;tidy digest&lt;/code&gt; gives you the receipts.&lt;/p&gt;

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

&lt;p&gt;Entering for the overall prize — the build doesn't use any partner tech.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Mill: Local-First AI Ops &amp; Sous-Chef for Meal Prep Side Hustlers</title>
      <dc:creator>Mark Cantimbuhan</dc:creator>
      <pubDate>Thu, 15 Oct 2026 12:10:12 +0000</pubDate>
      <link>https://dev.to/justcallmemarko/mill-local-first-ai-ops-sous-chef-for-meal-prep-side-hustlers-3dp9</link>
      <guid>https://dev.to/justcallmemarko/mill-local-first-ai-ops-sous-chef-for-meal-prep-side-hustlers-3dp9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built &lt;strong&gt;Mill — Local-First AI Ops &amp;amp; Sous-Chef for Meal Prep Side Hustlers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Selling prepared meals (fitness preps, weekly family bowls, keto packages) is one of the most popular side hustles today. But behind every aesthetic meal container is operational chaos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Uncontrolled ingredient shrinkage&lt;/strong&gt;: Buying food at retail, mismeasuring batch portions, and failing to account for seasoning oils eats up 15–20% of net margin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stockout guesswork&lt;/strong&gt;: You get a DM asking for 15 Teriyaki Chicken Bowls for Friday. Do you have enough chicken, jasmine rice, and honey garlic sauce in stock right now, or will you have to make an emergency grocery run?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kitchen multitasking meltdown&lt;/strong&gt;: When you have 4 burners running, an oven preheating, and 50 containers to box, you can't be typing notes or checking spreadsheets. You need real-time multi-step guidance with timers that keep you on track.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Mill&lt;/strong&gt; turns a laptop into an autonomous kitchen command center for food side hustlers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Capabilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Pantry &amp;amp; Ingredient Audit Ledger&lt;/strong&gt;: Tracks bulk items down to grams and milliliters with exact unit costs. Every manual adjustment, restock, or kitchen fulfillment deduction is recorded to an immutable audit trail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recipe Testing Lab &amp;amp; Margin Pricing&lt;/strong&gt;: Calculates exact ingredient batch costs and suggests profitable selling prices based on target gross margins (70%+).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Kanban Fulfillment Pipeline&lt;/strong&gt;: Drag-and-drop order management across &lt;code&gt;Upcoming&lt;/code&gt;, &lt;code&gt;Preparing&lt;/code&gt;, and &lt;code&gt;Completed&lt;/code&gt;. Moving an order card to &lt;code&gt;Completed&lt;/code&gt; automatically calculates needed recipe ingredients and deducts them from inventory through the audit ledger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active Kitchen Prep Mode&lt;/strong&gt;: Step-by-step checklist with auto-detected countdown timers (e.g. "Simmer for 15 minutes") and audio-visual cues so you never scorch a batch while packing containers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autonomous AI Sous-Chef via FastMCP&lt;/strong&gt;: An AI assistant connected directly to the local SQLite database through 16 typed FastMCP tools. You can tell Mill: &lt;em&gt;"Check if I have enough stock to cook 8 Honey Soy Chicken bowls"&lt;/em&gt;, &lt;em&gt;"Record an order for Sarah for Saturday"&lt;/em&gt;, or &lt;em&gt;"Adjust chicken breast inventory down by 500g"&lt;/em&gt;, and it handles the database transactions autonomously.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Here is how you can run Mill locally in two steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone the repository&lt;/span&gt;
git clone https://github.com/JustCallMeMarko/mill.git
&lt;span class="nb"&gt;cd &lt;/span&gt;mill

&lt;span class="c"&gt;# 2. Start the Backend (FastAPI + FastMCP + SQLite)&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;backend
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
uv run mill-seed    &lt;span class="c"&gt;# Seeds realistic pantry stock, recipes, and sample orders&lt;/span&gt;
uv run mill-api     &lt;span class="c"&gt;# Starts API server on http://127.0.0.1:8000 (FastMCP mounted at /mcp)&lt;/span&gt;

&lt;span class="c"&gt;# 3. Start the Frontend (React 19 + Tailwind + Vite)&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ../frontend
bun &lt;span class="nb"&gt;install
&lt;/span&gt;bun dev             &lt;span class="c"&gt;# Launches the dashboard on http://localhost:5173&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open &lt;code&gt;http://localhost:5173&lt;/code&gt; in your browser. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drag orders across the Kanban pipeline to watch stock deduct automatically.&lt;/li&gt;
&lt;li&gt;Switch to the Preparation tab to launch guided cooking timers and chat with the scoped AI Sous-Chef.&lt;/li&gt;
&lt;li&gt;Use the AI Assistant tab to let Mill execute multi-step database adjustments via FastMCP tool calling.&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/JustCallMeMarko" rel="noopener noreferrer"&gt;
        JustCallMeMarko
      &lt;/a&gt; / &lt;a href="https://github.com/JustCallMeMarko/mill" rel="noopener noreferrer"&gt;
        mill
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;Mill: AI Meal Prep Seller Assistant&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Mill is a local-first, autonomous AI assistant built specifically for meal prep sellers and culinary entrepreneurs. It helps automate operations, calculate profitable prices, test new experimental recipes against the core menu, manage orders on an interactive Kanban pipeline, and guide cooking through step-by-step checklists with smart kitchen timers.&lt;/p&gt;




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

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI Chatbot with Model Context Protocol (FastMCP)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Powered by &lt;code&gt;openai-agents&lt;/code&gt; connected to 16 local database tools.&lt;/li&gt;
&lt;li&gt;Works with either &lt;strong&gt;OpenAI&lt;/strong&gt; (&lt;code&gt;gpt-4o-mini&lt;/code&gt;, etc.) or &lt;strong&gt;Ollama&lt;/strong&gt; (e.g. &lt;code&gt;llama3.1&lt;/code&gt;, &lt;code&gt;qwen2.5&lt;/code&gt;) via standard OpenAI-compatible endpoints.&lt;/li&gt;
&lt;li&gt;Adjusts stock, creates orders, inspects margins, and suggests recipes right from conversation.&lt;/li&gt;
&lt;li&gt;Real-time Server-Sent Events (SSE) streaming with expandable tool-call activity cards.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ingredient &amp;amp; Pantry Inventory&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Track ingredient quantities, units, and unit costs.&lt;/li&gt;
&lt;li&gt;Automatic low-stock warnings when inventory drops below threshold.&lt;/li&gt;
&lt;li&gt;Complete transactional audit ledger for restocks, kitchen adjustments, and order fulfillment.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Recipes &amp;amp; Testing Lab&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maintain a distinction…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/JustCallMeMarko/mill" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/JustCallMeMarko/mill" rel="noopener noreferrer"&gt;https://github.com/JustCallMeMarko/mill&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  System Architecture
&lt;/h3&gt;


&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    subgraph UI [Frontend - React 19 + Tailwind + dnd-kit]
        A1[Kanban Prep Pipeline]
        A2[Kitchen Prep &amp;amp; Timers]
        A3[Inventory &amp;amp; Audit Ledger]
        A4[AI Assistant Chat]
    end

    subgraph Server [Backend - FastAPI &amp;amp; Python 3.12]
        B1[REST API Endpoints]
        B2[SSE Streaming Engine]
        B3[FastMCP Tool Server - 16 Tools]
        B4[openai-agents Agent Runner]
    end

    subgraph Storage [Local Storage]
        C1[(SQLite: mill.db)]
        C2[Inventory Ledger]
    end

    subgraph LLM [Inference Layer]
        D1[Ollama / Local Open Weights: Gemma, Llama 3.2, Qwen]
        D2[Optional Cloud API: OpenAI]
    end

    UI --&amp;gt;|REST &amp;amp; SSE Events| B1
    A4 --&amp;gt;|SSE Stream| B2
    B1 --&amp;gt; C1
    B3 --&amp;gt; C1
    B3 --&amp;gt; C2
    B4 --&amp;gt;|Calls Tools| B3
    B4 --&amp;gt;|OpenAI-Compatible HTTP| D1
    B4 -.-&amp;gt;|Fallback| D2&lt;/code&gt;&lt;/pre&gt;




&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: React 19, TypeScript, Tailwind CSS, Lucide icons, &lt;code&gt;@dnd-kit&lt;/code&gt; (accessible, physics-based Kanban drag-and-drop), React Query, and responsive dark/light themes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: FastAPI, SQLite with SQLAlchemy 2.0, &lt;code&gt;uvicorn&lt;/code&gt;, Server-Sent Events (SSE) streaming for real-time AI token generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool Protocol&lt;/strong&gt;: &lt;strong&gt;FastMCP&lt;/strong&gt; exposing 16 typed tools mounted directly at &lt;code&gt;http://127.0.0.1:8000/mcp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Agent Engine&lt;/strong&gt;: Built with &lt;code&gt;openai-agents&lt;/code&gt; connected to an OpenAI-compatible endpoint. Pointing &lt;code&gt;OPENAI_BASE_URL&lt;/code&gt; to &lt;code&gt;http://localhost:11434/v1&lt;/code&gt; allows running open-weight models (like &lt;code&gt;gemma&lt;/code&gt;, &lt;code&gt;llama3.2&lt;/code&gt;, or &lt;code&gt;qwen2.5&lt;/code&gt;) completely locally via Ollama with zero code changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Key Implementation Highlights
&lt;/h3&gt;
&lt;h4&gt;
  
  
  1. Immutable Audit Ledger on Kitchen Fulfillment
&lt;/h4&gt;

&lt;p&gt;When a side hustler drags an order to the "Completed" column in the Kanban board, we don't just decrement a number in the database. Mill looks up each recipe's scaled ingredient lines, computes the deduction, and logs an audited transaction:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In backend/src/mill/services/orders.py
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;recipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Recipe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recipe_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;recipe&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;servings_per_batch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;servings_per_batch&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ing&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingredients&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;needed_qty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;
        &lt;span class="n"&gt;inv_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InventoryItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inventory_item_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;inv_item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;inv_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inv_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;needed_qty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InventoryTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;inventory_item_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inv_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;change_amount&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="n"&gt;needed_qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order #&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) fulfilled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  2. FastMCP Tool Execution
&lt;/h4&gt;

&lt;p&gt;Rather than having an LLM hallucinate numbers or suggest recipes you cannot cook, Mill provides 16 specific FastMCP tools. As highlighted in community research on &lt;a href="https://dev.to/phantom-byte/your-agents-tool-descriptions-are-costing-you-66-accuracy-74f"&gt;tool descriptions and accuracy&lt;/a&gt;, having explicit type hints and deterministic database operations keeps small local models from making reasoning errors.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_recipe_feasibility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipe_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;servings_to_cook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Check if there is sufficient stock in the pantry to cook a batch.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;SessionLocal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;check_recipe_feasibility_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recipe_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;servings_to_cook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  3. Kitchen-Focused UX
&lt;/h4&gt;

&lt;p&gt;Kitchen environments demand big tap targets, clear high-contrast buttons, and zero clutter. Every prep step with a duration (like "Bake for 25 minutes") automatically parses out the time and renders an interactive countdown timer.&lt;/p&gt;


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

&lt;p&gt;For a food side hustle, open innovation and local-first AI are not just nice-to-haves—they are the difference between a viable business and going in the red:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Thin Margins Cannot Bear API Billing Surges&lt;/strong&gt;: Food side hustles operate on tight 15–25% net margins. Paying per-token cloud API costs every time you ask your assistant to price a recipe or check stock burns profits. Running open-weight models locally via Ollama costs exactly $0 in ongoing API fees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kitchen &amp;amp; Commissary Reliability&lt;/strong&gt;: Commercial kitchens, basements, and food prep spaces often have terrible Wi-Fi. An assistant that needs cloud latency to check a substitution or toggle a timer will fail during a busy prep rush. Mill runs 100% locally on your machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer Dietary &amp;amp; Health Data Privacy&lt;/strong&gt;: Side hustlers collect customer delivery addresses, food allergies, and health goals. Storing this in local SQLite and processing it with local models ensures customer data never leaks to third-party model training pipelines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Vendor Lock-In&lt;/strong&gt;: Because Mill uses standard tool-calling contracts, sellers can run Gemma, Llama 3.2, Qwen 2.5, or whatever open-weight model fits their laptop's hardware.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/strong&gt; (Main category)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; (Full local inference support via Ollama with &lt;code&gt;MILL_MODEL=gemma&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Team
&lt;/h2&gt;

&lt;p&gt;Solo — &lt;a href="https://dev.to/justcallmemarko"&gt;@justcallmemarko&lt;/a&gt;&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GPlay: turning dead phone time into playground time with local Gemma</title>
      <dc:creator>Mark Cantimbuhan</dc:creator>
      <pubDate>Sat, 10 Oct 2026 17:37:45 +0000</pubDate>
      <link>https://dev.to/justcallmemarko/gplay-turning-dead-phone-time-into-playground-time-with-local-gemma-142f</link>
      <guid>https://dev.to/justcallmemarko/gplay-turning-dead-phone-time-into-playground-time-with-local-gemma-142f</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;&lt;strong&gt;GPlay&lt;/strong&gt; answers one question fast: &lt;em&gt;"We've got five people, a ball, and a patch of grass — what can we actually play, right now?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You tell it how many people you have, what's in your bag, and where you are. It filters a library of real-world games down to the handful that genuinely fit those constraints, then uses &lt;strong&gt;Gemma (open weights, running locally through Ollama)&lt;/strong&gt; to write a short, tailored playbook — setup, how to play, how to win — adapted to &lt;em&gt;your&lt;/em&gt; group size, equipment, and space.&lt;/p&gt;

&lt;p&gt;The goal is to make the screen the shortest part of the experience. Open GPlay, get a pick, read three bullets, walk outside.&lt;/p&gt;

&lt;p&gt;It has three views:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Home / Coach&lt;/strong&gt; — describe your group ("4 kids and 2 adults, one frisbee, small yard") and it suggests playable game cards you can tap for rules. Ask it something unrelated and it politely refuses and steers back to games.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Games Catalog&lt;/strong&gt; — the full local library with search and area filters. Every card opens streamed, adapted rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoreboard&lt;/strong&gt; — pick any game and log scores, with a ranked table per game.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Run it yourself in two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# local Ollama: ollama serve &amp;amp;&amp;amp; ollama pull gemma4:latest&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;then open &lt;a href="http://localhost:5173" rel="noopener noreferrer"&gt;http://localhost:5173&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/JustCallMeMarko" rel="noopener noreferrer"&gt;
        JustCallMeMarko
      &lt;/a&gt; / &lt;a href="https://github.com/JustCallMeMarko/GPlay" rel="noopener noreferrer"&gt;
        GPlay
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;GPlay&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;An open-source, local-first web app for the Hacktoberfest &lt;strong&gt;"Touch Grass"&lt;/strong&gt; challenge
Tell it how many people you have, what equipment is on hand, and where you are — it
instantly filters matching physical games and uses Google's open-weight &lt;strong&gt;Gemma&lt;/strong&gt; model
(via Ollama) to stream tailored rules, setups, and adaptations as Markdown over SSE.&lt;/p&gt;
&lt;p&gt;Zero cloud APIs, zero API fees, works fully offline.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;[ React + Tailwind ] --REST/SSE--&amp;gt; [ FastAPI ] --filter--&amp;gt; [ SQLite games DB ]
                                          \
                                           '--local HTTP--&amp;gt; [ Ollama: gemma4:latest ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Docker&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Uses Ollama installed on your machine (ollama serve + ollama pull gemma4:latest)&lt;/span&gt;
docker compose up --build

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; No local Ollama? Run everything in containers instead:&lt;/span&gt;
docker compose --profile containerized-ollama up --build&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Serves the frontend at &lt;a href="http://localhost:5173" rel="nofollow noopener noreferrer"&gt;http://localhost:5173&lt;/a&gt;. &lt;code&gt;OLLAMA_URL&lt;/code&gt;/&lt;code&gt;GPLAY_MODEL&lt;/code&gt;
env vars override the defaults (&lt;code&gt;http://host.docker.internal:11434&lt;/code&gt; / &lt;code&gt;gemma4:latest&lt;/code&gt;).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Manual&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;1. Backend&lt;/h4&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;cd&lt;/span&gt; server
uv venv &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip&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/JustCallMeMarko/GPlay" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; React 19 + Vite + Tailwind CSS on the front end, FastAPI on the back end, SQLite for the seeded 29-game catalog, and &lt;strong&gt;Ollama serving &lt;code&gt;gemma4:latest&lt;/code&gt;&lt;/strong&gt; for every bit of language generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ React + Tailwind ] --REST/SSE--&amp;gt; [ FastAPI ] --filter--&amp;gt; [ SQLite games DB ]
                                       \
                                        '--local HTTP--&amp;gt; [ Ollama: gemma4:latest ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few decisions worth calling out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model only does what it is good at.&lt;/strong&gt; "Which of these 29 games fit 4 people with one frisbee on grass?" is a deterministic filter — so it is plain Python/SQL, not a prompt. Gemma is handed only the games that &lt;em&gt;already match&lt;/em&gt; and asked to adapt them. That keeps results fast, predictable, and cheap, and it means a small local model behaves well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything streams.&lt;/strong&gt; Rules and chat are streamed token-by-token from FastAPI as Server-Sent Events and rendered as Markdown with &lt;code&gt;react-markdown&lt;/code&gt;, so the playbook starts appearing immediately instead of after a long wait.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The chat markers never leak.&lt;/strong&gt; The Coach prompt is restricted to physical games only. When it wants to show game cards it emits a &lt;code&gt;GAMES_PICK: id,id,id&lt;/code&gt; line, which the backend strips out line-by-line mid-stream and converts into a structured &lt;code&gt;{"games": [...]}&lt;/code&gt; event. The user sees prose plus cards; the plumbing stays invisible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model can invent new games — under constraints.&lt;/strong&gt; A separate endpoint has Gemma return strict JSON for a brand-new game, then validates it before storing: equipment and areas must be from the allowed sets, player counts are clamped, and the slug is de-duplicated. Bad model output is rejected, not saved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI is optional by design.&lt;/strong&gt; If Ollama or the model isn't running, &lt;code&gt;/api/rules&lt;/code&gt; and &lt;code&gt;/api/chat&lt;/code&gt; fall back to deterministic, built-in rules, streamed with the same token-by-token behaviour. The app works end-to-end offline — the AI is an upgrade, not a dependency.&lt;/p&gt;

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

&lt;p&gt;This is an app you're supposed to use &lt;strong&gt;outside&lt;/strong&gt; — in a yard, a park, a campsite, the backcountry. That makes the open approach the whole point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It runs with no internet.&lt;/strong&gt; Inference happens on the machine in front of you. No signal required, no round-trip to a server that might be slow or down. A bird-call-style "works on the trail with no bars" promise, applied to play.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No data leaves your machine.&lt;/strong&gt; A group's location, ages, and habits stay local. There is no vendor seeing which games a school picked or where a run club meets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No API keys, no per-request cost.&lt;/strong&gt; Open weights mean the app costs nothing to run. A teacher, a parent, or a run-club organiser can clone it and use it without a billing account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model is swappable.&lt;/strong&gt; &lt;code&gt;GPLAY_MODEL&lt;/code&gt; is an env var. Point it at a different Ollama model, a fine-tune, or a smaller model for a weak laptop — nothing else changes. A closed API would lock both the model and the terms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where open beat closed here: GPlay works on a phone in a backyard with no cell service, adapts everything to a group's real situation without sending that situation anywhere, and stays free forever to the people who'd benefit most from it.&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; — the project's language generation is Gemma via local Ollama: adapted rules, the Coach chat, and constrained new-game generation all run through it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Team
&lt;/h2&gt;

&lt;p&gt;Solo — &lt;a href="https://dev.to/justcallmemarko"&gt;@justcallmemarko&lt;/a&gt;.&lt;/p&gt;

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