<?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: Ayushya Ranjan</title>
    <description>The latest articles on DEV Community by Ayushya Ranjan (@dagger7164).</description>
    <link>https://dev.to/dagger7164</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%2F4156357%2Fa0ca8ffb-27db-4cb5-8139-6b42db4b7649.jpg</url>
      <title>DEV Community: Ayushya Ranjan</title>
      <link>https://dev.to/dagger7164</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dagger7164"/>
    <language>en</language>
    <item>
      <title>Stop opening the whole PDF: local summaries for reading debt</title>
      <dc:creator>Ayushya Ranjan</dc:creator>
      <pubDate>Sun, 25 Oct 2026 11:56:25 +0000</pubDate>
      <link>https://dev.to/dagger7164/stop-opening-the-whole-pdf-local-summaries-for-reading-debt-4iki</link>
      <guid>https://dev.to/dagger7164/stop-opening-the-whole-pdf-local-summaries-for-reading-debt-4iki</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;tldr-everything is a local reading-debt killer. You hand it a PDF, a Markdown file, or a paste. It gives back three layers, each from its own model call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a 30-second summary, about 60–90 words, the claim and the outcome&lt;/li&gt;
&lt;li&gt;a 3-minute explanation, about 350–500 words, the why, the how, and the jargon&lt;/li&gt;
&lt;li&gt;detailed notes grouped by page, with citations like &lt;code&gt;(p. 4)&lt;/code&gt;, plus the top takeaways and the questions the document never answers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a second mode, "tell me only what I need." You type a goal, for example "I want to deploy this with Docker on a VPS." Each chunk gets a relevance score from 0 to 5. Chunks under the cutoff are dropped. From the rest you get prerequisites, practical steps, the pages that matter, what you can skip, and the gotchas. If nothing scores high enough, it says the document does not cover the goal and shows the closest sections. It does not invent a procedure.&lt;/p&gt;

&lt;p&gt;The person who gets to be lazier is anyone with a folder of tutorials, docs, and papers they saved "for later." They can spend a minute on the summary, or ask one question, and only open the original when a page citation is worth it.&lt;/p&gt;

&lt;p&gt;The default model is Qwen2.5-3B-Instruct through Ollama. It fits in about 8 GB of RAM, including CPU-only. A 3B model will wander if you ask it for five sections at once, so the pipeline never does that. One job per call. Short prompts. Chunks of about 1,500 tokens. If the chunk summaries still do not fit, they are reduced in rounds until they do. Python then checks the work: word counts, commands that must actually appear in the source, and numbers the document never stated. A fenced command that is not in the PDF is dropped. An inline command that is not in the PDF is marked &lt;code&gt;[unverified]&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Local only. Nothing is hosted, and the document stays on the machine unless you point the base URL at a remote server.&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%2Fafrvzzlehwltpqzllvhm.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%2Fafrvzzlehwltpqzllvhm.png" alt=" " width="800" height="880"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull qwen2.5:3b
ollama create tldr-qwen &lt;span class="nt"&gt;-f&lt;/span&gt; ollama/Modelfile
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tldr-qwen
tldr summarize examples/docker-vps.md &lt;span class="nt"&gt;--layer&lt;/span&gt; 30s
tldr goal examples/docker-vps.md &lt;span class="nt"&gt;--goal&lt;/span&gt; &lt;span class="s2"&gt;"I want to deploy this with Docker on a VPS"&lt;/span&gt;
tldr ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;tldr-qwen&lt;/code&gt; is a one-line Modelfile on top of &lt;code&gt;qwen2.5:3b&lt;/code&gt; that sets &lt;code&gt;num_ctx 8192&lt;/code&gt;. Ollama's OpenAI-compatible endpoint often ignores a per-request context size and silently cuts the prompt. The derived model is how a small local model gets the whole chunk.&lt;/p&gt;

&lt;p&gt;A hand-written sample of the shape of the output, taken from the Docker-on-a-VPS note in the repo, is in &lt;code&gt;examples/sample-output.md&lt;/code&gt;. The practical steps from that note are the commands that are actually in the file:&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="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker.io
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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/AyushyaRanjan" rel="noopener noreferrer"&gt;
        AyushyaRanjan
      &lt;/a&gt; / &lt;a href="https://github.com/AyushyaRanjan/TLDR" rel="noopener noreferrer"&gt;
        TLDR
      &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;tldr-everything&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;The Reading Debt Killer. Turn a PDF or a long paste into a 30-second summary, a 3-minute explanation, and page-cited notes, or ask for only the parts that match a goal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/your-org/tldr-everything/actions" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/your-org/tldr-everything/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://github.com/AyushyaRanjan/TLDR/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://github.com/AyushyaRanjan/TLDR/pyproject.toml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/13466c63b4f964f1bc47e46eabb6ea9ebff2cb7777cba82af790a55719bbbfda/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31302532422d626c75652e737667" alt="Python 3.10+"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The code in this repository is MIT. The default model weights are not. See &lt;a href="https://github.com/AyushyaRanjan/TLDR#model-licensing" rel="noopener noreferrer"&gt;Model licensing&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;A GIF belongs in &lt;a href="https://github.com/AyushyaRanjan/TLDR/docs/screenshots/README.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/screenshots/&lt;/code&gt;&lt;/a&gt;. Record the Streamlit app on &lt;code&gt;examples/docker-vps.md&lt;/code&gt;, save it as &lt;code&gt;docs/screenshots/demo.gif&lt;/code&gt;, and drop this line in once the file exists:&lt;/p&gt;
&lt;div class="highlight highlight-text-md notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-s"&gt;![&lt;/span&gt;tldr-everything summarizing a short Docker guide&lt;span class="pl-s"&gt;]&lt;/span&gt;&lt;span class="pl-s"&gt;(&lt;/span&gt;&lt;span class="pl-corl"&gt;docs/screenshots/demo.gif&lt;/span&gt;&lt;span class="pl-s"&gt;)&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Until then, the &lt;a href="https://github.com/AyushyaRanjan/TLDR/examples/sample-output.md" rel="noopener noreferrer"&gt;sample output&lt;/a&gt; below is the shape of a result.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The problem&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Tutorials, docs, papers, and long articles pile up unread. Opening one means paying the whole reading cost before you know if it answers the question you saved it for.&lt;/p&gt;
&lt;p&gt;tldr-everything cuts that cost into layers. Each layer is its own short model call, because the default model is…&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/AyushyaRanjan/TLDR" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT license. Python 3.10+. PyMuPDF for the PDF, a small Typer CLI (&lt;code&gt;tldr&lt;/code&gt;), and a Streamlit UI. The model client is the OpenAI SDK pointed at any compatible endpoint, default &lt;code&gt;http://localhost:11434/v1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Tests never call the network. A fake client covers cleanup, chunk overlap, the injection guard, length retries, recursive reduce, scanned PDFs, and the "this document does not cover your goal" path.&lt;/p&gt;

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

&lt;p&gt;The open-weight model is Qwen2.5-3B-Instruct, run locally with Ollama. The app talks to it through Ollama's OpenAI-compatible API, so LM Studio, vLLM, or a larger Qwen is a config change. &lt;code&gt;llama3.2:3b&lt;/code&gt; is the other small option documented in the README.&lt;/p&gt;

&lt;p&gt;The build is arranged around the fact that 3B is small:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each prompt in &lt;code&gt;prompts.py&lt;/code&gt; has one job, three to six rules, one tiny example, and a plain-text format. Relevance comes back as a single line, &lt;code&gt;SCORE: 0-5&lt;/code&gt;, parsed with a regex. Garbage input scores 0, so the chunk is dropped.&lt;/li&gt;
&lt;li&gt;Document text is wrapped in &lt;code&gt;&amp;lt;document&amp;gt;&lt;/code&gt; tags and labeled untrusted. Literal tags inside the file are neutralized first, so a PDF cannot close the delimiter early.&lt;/li&gt;
&lt;li&gt;Map calls run a few at a time (&lt;code&gt;LLM_CONCURRENCY&lt;/code&gt; defaults to 2). The CLI and the UI share a progress callback and an ETA from the latency actually observed.&lt;/li&gt;
&lt;li&gt;After generation, a post-processor strips "Sure! Here is..." and checks length. Outside a tolerant band, it retries once, then keeps the text and raises a warning badge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;PyMuPDF extracts text page by page. Scanned PDFs, which come back nearly empty, get a clear "OCR is not supported" message. Encrypted PDFs are refused. Cleanup de-hyphenates prose and leaves fenced and indented code alone.&lt;/p&gt;

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

&lt;p&gt;The whole point of the tool is unread private reading: papers, internal docs, a tutorial you would rather not upload. A closed API would mean sending that pile to someone else's server, and paying for every chunk. Here the default path is localhost. Logs record call type and timing, not the document. If the base URL is not local, the CLI and the UI say so before the run.&lt;/p&gt;

&lt;p&gt;A small open-weight model also forced the design. I could not hide a sloppy prompt behind a huge context window. The Modelfile, the chunk size, the recursive reduce, and the grounding checks exist because Qwen2.5-3B will invent a plausible &lt;code&gt;docker&lt;/code&gt; command if you let it. Those checks are ordinary Python. They keep working if you swap the model later.&lt;/p&gt;

&lt;p&gt;It also costs nothing to run after the weights are on disk. The same pipeline that summarizes one saved article can sit on a laptop with no key and no meter.&lt;/p&gt;

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

&lt;p&gt;The app reports both clocks on every run: minutes to read the source at 220 words per minute, and minutes to read the summary. A 4,000-word article is roughly 18 minutes. The 30-second layer is about 80 words, well under a minute, and it cites the page if you want to check a claim.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>A preflight log check a drone hobbyist can run on their own laptop</title>
      <dc:creator>Ayushya Ranjan</dc:creator>
      <pubDate>Sun, 18 Oct 2026 12:53:19 +0000</pubDate>
      <link>https://dev.to/dagger7164/a-preflight-log-check-a-drone-hobbyist-can-run-on-their-own-laptop-23gf</link>
      <guid>https://dev.to/dagger7164/a-preflight-log-check-a-drone-hobbyist-can-run-on-their-own-laptop-23gf</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;Drone Flight-Log Explainer reads an ArduCopter / Pixhawk &lt;code&gt;.BIN&lt;/code&gt; flight log and turns it into a plain-English preflight report.&lt;/p&gt;

&lt;p&gt;The person who would pay is a hobbyist who just had a rough flight and does not want to learn a log browser before they fly again. They want a yes-or-no on four things: did the battery sag, did the airframe vibrate hard enough to clip the sensors, did GPS lose a 3D fix, and did the autopilot throw a failsafe. A small repair bench or a club instructor would pay for the same check, so a pilot shows up with a written list instead of a raw file.&lt;/p&gt;

&lt;p&gt;The report covers battery voltage, current, and drain; vibration and accelerometer clipping; satellite count and HDOP; autopilot messages, error codes, and mode changes; then a short "inspect before the next flight" list. Every number comes from the log. The wording is not allowed to invent a voltage, a time, or a cause.&lt;/p&gt;

&lt;p&gt;A club could charge a few dollars per log, or a shop could include the report with a pack test. The check itself runs locally, so the cost of one report is not an API bill.&lt;/p&gt;

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

&lt;p&gt;Run it from the project folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
uvicorn app.main:app &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt; and click &lt;strong&gt;Try demo flight&lt;/strong&gt;. That flight is synthetic and already contains a battery sag, a vibration spike, a GPS fix loss, and a battery failsafe that ends in RTL. Uploading a real &lt;code&gt;.BIN&lt;/code&gt; uses the same pipeline. No account and no API key are required.&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/AyushyaRanjan" rel="noopener noreferrer"&gt;
        AyushyaRanjan
      &lt;/a&gt; / &lt;a href="https://github.com/AyushyaRanjan/drone-log-explainer" rel="noopener noreferrer"&gt;
        drone-log-explainer
      &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;Drone Flight-Log Explainer&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A small web app for hobbyist ArduCopter / Pixhawk pilots. Upload a dataflash &lt;code&gt;.BIN&lt;/code&gt; log, or run a built-in demo flight, and get a plain-English diagnostic report covering battery, vibration, GPS, and autopilot warnings.&lt;/p&gt;

&lt;p&gt;The analyzer produces every number in the report. The language model only explains those findings. If &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; is missing or the call fails, a template writes the same sections so the app still works 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;browser (index.html + Chart.js)
    |  POST /api/analyze  or  POST /api/demo
    v
FastAPI (app/main.py)
    |-- parser.py      pymavlink DF reader -&amp;gt; pandas DataFrame per message
    |-- analyzers/     battery, vibration, GPS, warnings, summary
    |-- report.py      verdict, checklist, downsampled timeseries
    |-- explainer.py   Claude, or the template fallback
    v
in-memory report store, plus JSON under data/reports/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;No database. Reports are kept in process memory and written as JSON when the disk is writable.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run it&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;pip install -r requirements.txt
uvicorn app.main:app --reload&lt;/pre&gt;

&lt;/div&gt;
&lt;p&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/AyushyaRanjan/drone-log-explainer" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;The app is a small Python service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app/parser.py&lt;/code&gt; reads the dataflash log with &lt;code&gt;pymavlink&lt;/code&gt; and builds one pandas table per message type.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/analyzers/&lt;/code&gt; applies fixed thresholds for battery, vibration, GPS, and warnings.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/explainer.py&lt;/code&gt; writes the English report from those findings. If &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; is unset, a template does it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/static/index.html&lt;/code&gt; draws the charts with Chart.js.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;pytest -q&lt;/code&gt; covers the demo anomalies, a clean flight, a missing message type, a hand-built &lt;code&gt;.BIN&lt;/code&gt;, and the HTTP routes.&lt;/p&gt;

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

&lt;p&gt;The part that makes the product work is open and local. &lt;code&gt;pymavlink&lt;/code&gt; decodes the &lt;code&gt;.BIN&lt;/code&gt;. pandas and numpy compute the sag fit, the vibration runs, and the GPS fractions. FastAPI serves one static page. Those libraries are what turn a flight log into findings. A closed chat API cannot read a dataflash file, and it should not be the thing deciding that a cell hit 3.28 V.&lt;/p&gt;

&lt;p&gt;The English layer is a template filled from the analyzer JSON, so the app works with no model and no key. An optional Claude call can rephrase that same JSON. It receives the summary, findings, and checklist, never the raw log, and the prompt forbids numbers that are not in the data. If the call fails, the template is what the pilot sees.&lt;/p&gt;

&lt;p&gt;I used a coding agent in Cursor to build the pipeline. The agent is not in the product. The product a pilot runs is the parser, the rules, and the template.&lt;/p&gt;

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

&lt;p&gt;A flight log is a map of where someone flew, how long they were up, and how the aircraft failed. Uploading that file to a hosted model is a bad default for a hobby tool. Running the parser and the rules on the laptop means the log never leaves the machine, and a useful report does not depend on someone else's per-token price.&lt;/p&gt;

&lt;p&gt;The thresholds are in the repo. A pilot can see that "critical" means a cell under 3.3 V, vibration above 60 m/s² for more than two seconds, or a failsafe subsystem. A closed model that summarizes a log in one paragraph can sound confident and still invent a cause. Here the open code produces the findings, and the words are only allowed to explain them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pymavlink&lt;/code&gt; is also the reason this can exist as a side tool at all. ArduPilot's log format is documented and readable because the autopilot stack is open. A closed vendor log would have made the same preflight check a paid black box.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>I Made a Game That Only Counts If You Leave the House</title>
      <dc:creator>Ayushya Ranjan</dc:creator>
      <pubDate>Sat, 10 Oct 2026 22:42:39 +0000</pubDate>
      <link>https://dev.to/dagger7164/i-made-a-game-that-only-counts-if-you-leave-the-house-6b0</link>
      <guid>https://dev.to/dagger7164/i-made-a-game-that-only-counts-if-you-leave-the-house-6b0</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;I spend most of my day on a screen. So I built &lt;strong&gt;Touch Grass&lt;/strong&gt;, an app that gives me a reason to leave it.&lt;/p&gt;

&lt;p&gt;Every morning, you get one outdoor task. "Find moving water." "Say hello to three people you pass." "Pick up ten pieces of litter." You can do it any time that day. Then you take a live photo as proof. A small AI on my laptop looks at the photo, and plain code decides if you passed.&lt;/p&gt;

&lt;p&gt;The tasks are not only about nature. Some are about people: ask a shop owner how long they've been open, or ask someone for their favorite place to eat. The goal is to go out, look around, and talk to someone, not just count steps.&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%2Fb1qtns7imeqxyg03lt00.jpeg" 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%2Fb1qtns7imeqxyg03lt00.jpeg" alt=" " width="720" height="1600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Today's task. Anytime today. No timer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;40 hand-written tasks in six kinds: nature, movement, exploration, social, creative and community&lt;/li&gt;
&lt;li&gt;Live camera only. No gallery, so the proof comes from really being there&lt;/li&gt;
&lt;li&gt;If the AI gets it wrong, tap "Mark done anyway" for half points. The app never blocks your streak&lt;/li&gt;
&lt;li&gt;Works with no signal. The photo waits on your phone and goes through when you're back online&lt;/li&gt;
&lt;li&gt;XP, levels, a daily streak and a monthly count&lt;/li&gt;
&lt;li&gt;You choose which kinds of tasks you want more or less of, and how bold the social ones are&lt;/li&gt;
&lt;li&gt;Weather-aware tasks, a streak freeze, a daily reminder, a bad-day mode and a monthly recap&lt;/li&gt;
&lt;li&gt;It counts its own screen time, because the screen should be the shortest part of the day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's for people like me who want a small push to go outside, and a little game to keep it going.&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%2F64uqt88uecplq18ei12f.jpeg" 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%2F64uqt88uecplq18ei12f.jpeg" alt=" " width="720" height="1600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;No signal? The photo is saved on the phone and waits.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffsdxe3cqfkljrvkgl3ce.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%2Ffsdxe3cqfkljrvkgl3ce.png" alt=" " width="800" height="1779"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Back online. The laptop checked the photo and the task is done.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/V403W-E0q_o" width="710" height="399"&gt;
  &lt;/iframe&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/AyushyaRanjan" rel="noopener noreferrer"&gt;
        AyushyaRanjan
      &lt;/a&gt; / &lt;a href="https://github.com/AyushyaRanjan/TouchGrass" rel="noopener noreferrer"&gt;
        TouchGrass
      &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;Touch Grass&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/AyushyaRanjan/TouchGrass/docs/logo.jpg"&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%2FAyushyaRanjan%2FTouchGrass%2FHEAD%2Fdocs%2Flogo.jpg" alt="Touch Grass logo" width="128"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Single-user app that gives you one daily outdoor task (nature, movement, exploration, social, creative, community). You submit a live photo as proof from a mobile app; a laptop server verifies it with a local VLM served by Ollama.&lt;/p&gt;

&lt;p&gt;See &lt;code&gt;AGENTS.md&lt;/code&gt; for the full spec, hard rules and game/verdict rules.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.astral.sh/uv/" rel="nofollow noopener noreferrer"&gt;uv&lt;/a&gt; (Python package manager)&lt;/li&gt;
&lt;li&gt;Python 3.11+&lt;/li&gt;
&lt;li&gt;Node.js 20+ (for the Expo app in &lt;code&gt;app/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt; running locally with a vision model
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;ollama pull qwen2.5vl:3b&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Optional: &lt;a href="https://tailscale.com" rel="nofollow noopener noreferrer"&gt;Tailscale&lt;/a&gt; so the phone can reach the laptop over HTTPS-grade WireGuard while the app still uses HTTP&lt;/li&gt;
&lt;li&gt;Optional: an &lt;a href="https://expo.dev" rel="nofollow noopener noreferrer"&gt;Expo&lt;/a&gt; account if you want a standalone APK (&lt;code&gt;eas build&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&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; 1. Install dependencies (creates .venv and uv.lock)&lt;/span&gt;
uv sync
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 2. Configure environment&lt;/span&gt;
copy .env.example .env   &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Windows; use `cp .env.example .env` on macOS/Linux&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Edit .env and set API_TOKEN to a long random string. Never commit .env.&lt;/span&gt;&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/AyushyaRanjan/TouchGrass" 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;The AI in the app is open and runs on my own laptop. It's an everyday machine: an RTX 3050 with 4 GB of video memory and 16 GB of RAM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI.&lt;/strong&gt; I used &lt;strong&gt;Qwen2.5-VL (3B)&lt;/strong&gt; through &lt;strong&gt;Ollama&lt;/strong&gt;. It looks at the photo and answers small yes/no questions, like "Is a leaf visible?" It also writes a short note about what it sees, and that note becomes the hint you read in the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule that made it work: the AI never decides pass or fail.&lt;/strong&gt; Small AI models like to say yes. So plain code makes the call. You pass only if every required question gets a "yes". That also means nobody can sweet-talk the AI into a pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choosing the model.&lt;/strong&gt; I ran a quick test on 7 photos:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Right answers&lt;/th&gt;
&lt;th&gt;"Not sure" answers&lt;/th&gt;
&lt;th&gt;Speed per photo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen2.5-VL 3B&lt;/td&gt;
&lt;td&gt;12 of 12&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3 to 6 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 3 4B&lt;/td&gt;
&lt;td&gt;8 of 10&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4.5 to 6.5 seconds, but 71 seconds on the first photo, and it crashed once&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Gemma also gave "no" answers while its own note said the thing was visible. That's a tiny test, not a benchmark, but it was enough to pick Qwen. Swapping the model took one line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rest of the build:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Photo checks come first.&lt;/strong&gt; Blurry, dark, tiny or repeated photos are stopped before the AI sees them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server:&lt;/strong&gt; Python (FastAPI) and SQLite, running on my laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App:&lt;/strong&gt; Expo (React Native), built as an Android app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The link between phone and laptop:&lt;/strong&gt; Tailscale, a private network between my own devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline:&lt;/strong&gt; the phone keeps a queue. It sends photos when it can reach the laptop, and it never sends the same photo twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tasks are written by hand,&lt;/strong&gt; because a small AI writes vague tasks. The AI only rewrites the wording a little.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaks and XP are worked out from my history&lt;/strong&gt; each time, so they can't get out of sync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The demo voice-over&lt;/strong&gt; was made with ElevenLabs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Being honest about how I built it:&lt;/strong&gt; I used coding agents (OpenCode and Cursor) to write most of the code, from detailed prompts and a rules file. Those run on hosted models. The AI that runs inside the app is the local one. I built the Android file with Expo's cloud build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What doesn't work well yet:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Social tasks are checked by the place only.&lt;/strong&gt; The AI can see the cafe, but not the chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The AI is small,&lt;/strong&gt; so it's sometimes unsure or wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My laptop has to be on&lt;/strong&gt; for photos to get checked.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Android only.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faq3ltzdqdhbwby68cedc.jpeg" 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%2Faq3ltzdqdhbwby68cedc.jpeg" alt=" " width="720" height="1600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The app counts its own screen time. That 7 minutes was a testing day.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F97meqw93u68mv3gcg1fy.jpeg" 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%2F97meqw93u68mv3gcg1fy.jpeg" alt=" " width="720" height="1600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;You decide which kinds of tasks you want more or less of.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bugs I hit along the way
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A tiny photo was stopped in 0.6 seconds.&lt;/strong&gt; My test photo was only 30 KB. The photo check caught it before the AI even looked, and that's the point of checking first. The message said "move a little closer", which was wrong, because the problem was resolution, not distance. I changed it to say the resolution is too low.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gemma disagreed with itself.&lt;/strong&gt; One of the two models I tried said "no" to "is this outdoors?" while its own note said "sky is visible". It also took 71 seconds on the first photo and crashed once. Qwen got every question it answered right and was much faster. That's why I picked Qwen. It was a small test, but the difference was clear.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I typed the model name wrong, twice.&lt;/strong&gt; My first try at a third model failed with a "not found" error. The tag had a typo. I fixed it and moved on, because Qwen was already doing the job.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A photo of a screen passed the "outdoors" question.&lt;/strong&gt; I photographed a monitor showing a tree, and the AI said "outdoors". When I asked directly "is this a screen?", it got it right. So the model can tell, I just wasn't asking. For now, spotting screens is only logged. It's a clear next step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The same note appeared for every question.&lt;/strong&gt; The small model describes the photo once and pastes that note under each answer. So some hints are less useful than I'd like. A fix would be to ask one question at a time, which costs a few more seconds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One extra retry made a check take 13 seconds instead of 5.&lt;/strong&gt; My rule that every note must be at least four words sometimes made the model try again. It's a fair trade for better hints, but I watch it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Uploads failed with "Unsupported FormDataPart".&lt;/strong&gt; Expo's network code didn't accept the usual way of sending a photo. The server never saw a request. The app also said "couldn't reach your server" for every kind of error, which was misleading. I switched the upload to a different method and made the error messages say what really went wrong.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;My history thumbnails were blank.&lt;/strong&gt; The image requests didn't carry my token, so my own server said "401 unauthorized" to my own app. I now download each thumbnail with the token and keep a copy on the phone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The app showed "done" when the server said it wasn't.&lt;/strong&gt; When I deleted a test photo on the laptop, the phone still remembered it. Today's task showed as finished with no way to upload. I made the phone check with the server and drop items the server doesn't know about, and I added a button to clear the local cache.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Close button did nothing after an "unsure" result.&lt;/strong&gt; I tapped it three times. It only went away when I switched tabs. The cause was the screen being redrawn from fresh data, which brought the card back. Closing now takes effect immediately and stays closed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expo Go can't open the app in airplane mode.&lt;/strong&gt; It loads the app from my laptop, so with no connection it can't start. The offline queue works once the app is open. For real use I needed a standalone app that doesn't depend on my laptop.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;My photos stay mine.&lt;/strong&gt; They go from my phone to my laptop and nowhere else. No cloud service ever sees them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing per photo.&lt;/strong&gt; I can check as many photos as I want, and nobody sends me a bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could swap models in one line.&lt;/strong&gt; I tried two, looked at the results, and picked the better one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I set the rules myself:&lt;/strong&gt; the questions, the exact format the model must answer in, and the code that decides. That's why the AI can't just agree with everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The small model made the design better.&lt;/strong&gt; A bigger closed model might see more detail. But because mine is small, I had to break the job into simple yes/no questions and let code decide. That made the app fairer and easier to trust.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An app that tells you to go outside shouldn't send your photos to a company on the way. This one doesn't.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>I Built a Private "Open When" App for a Friend Who Feels Low at Night</title>
      <dc:creator>Ayushya Ranjan</dc:creator>
      <pubDate>Sat, 03 Oct 2026 16:18:01 +0000</pubDate>
      <link>https://dev.to/dagger7164/i-built-a-private-open-when-app-for-a-friend-who-feels-low-at-night-21n8</link>
      <guid>https://dev.to/dagger7164/i-built-a-private-open-when-app-for-a-friend-who-feels-low-at-night-21n8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;OpenWhen&lt;/strong&gt; is a private comfort app I built for "a close friend", who "often feels low and alone, especially at night".&lt;/p&gt;

&lt;p&gt;When you feel low, generic motivational quotes don't help. What helps is hearing from people who actually know you. So the app works like digital "open when..." letters: people who care about my friend (including me) recorded voice notes and wrote messages, each tagged with a feeling. My friend taps how they feel, optionally types a few words, and the app brings back the right message from a real person. A small local AI model writes a one-line, gentle introduction, but it never tries to be the comforter. The comfort comes from people.&lt;/p&gt;

&lt;p&gt;It also has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Log a win:&lt;/strong&gt; a place for my friend to save things they got through, which the app surfaces on harder days ("Remember this?").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I have a challenge:&lt;/strong&gt; the AI breaks a problem into one tiny five-minute step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saved for later:&lt;/strong&gt; favorite messages that stay available even when the server is offline. &lt;em&gt;(remove if not built)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A safety layer:&lt;/strong&gt; if my friend writes something that suggests self-harm, the AI is bypassed completely and the app shows a fixed message with one-tap buttons for Tele-MANAS (14416), my phone number, and 112.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a comfort tool, not therapy, and the app says so.&lt;/p&gt;

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


&lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/0O-eB5yYAJA" width="315" height="560"&gt;
    &lt;/iframe&gt;
  &lt;/div&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/AyushyaRanjan" rel="noopener noreferrer"&gt;
        AyushyaRanjan
      &lt;/a&gt; / &lt;a href="https://github.com/AyushyaRanjan/OpenWhen" rel="noopener noreferrer"&gt;
        OpenWhen
      &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;Open When...&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Open When... is a small, private web app that keeps a few kind words close by
for someone who is having a hard day. When they say how they feel, it shows them
a message or voice note from someone who loves them, or one of their own logged
wins. It runs entirely on one laptop and talks to no company's servers.&lt;/p&gt;
&lt;p&gt;It is built to be read and changed by one person, not scaled. If you are looking
for a product, this is not it. If you are looking for something warm, small and
yours, it might be.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it is, and who it is for&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The person who uses it is &lt;strong&gt;one specific friend&lt;/strong&gt; who often feels low or alone
They open it on their phone or laptop, tap how they feel, and get back something
human that already existed — a note from a parent,…&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/AyushyaRanjan/OpenWhen" 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;Everything that touches my friend's feelings runs on my own laptop (i5-12450HX, 16 GB RAM, RTX 3050 with 4 GB VRAM).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model runtime:&lt;/strong&gt; &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; for local inference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat model:&lt;/strong&gt; &lt;code&gt;[MODEL YOU CHOSE, e.g. qwen2.5:3b]&lt;/code&gt;, a small open-weight model. I compared [LIST THE MODELS YOU TESTED] on sample inputs and picked the warmest one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings:&lt;/strong&gt; &lt;code&gt;[EMBEDDING MODEL, e.g. nomic-embed-text]&lt;/code&gt; for matching a feeling to the right message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; FastAPI and SQLite, with plain NumPy cosine similarity for retrieval. No heavy vector database was needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; a plain HTML/CSS/JS installable PWA with no frameworks, no CDNs and no external fonts, so it works with zero internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phone access:&lt;/strong&gt; [HOW YOU DID IT: e.g. Tailscale / Cloudflare Tunnel], with a passcode login &lt;em&gt;(remove if not added)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built with an open-source agent:&lt;/strong&gt; I wrote the build as a sequence of prompts and used &lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; as the coding agent. [MENTION WHICH MODEL POWERED OPENCODE, and note that it only wrote code and never saw my friend's data.]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Design decisions I'm proud of&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval first, generation second.&lt;/strong&gt; Because the comfort comes from real messages, a small model is enough, and it can't invent a memory it was never given.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety runs before the model.&lt;/strong&gt; Crisis checks use keywords plus an optional model check, and a crisis match never reaches the model. The response is fixed and human-written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contributors are never blocked.&lt;/strong&gt; I first screened loving notes from friends with the same filter, which falsely flagged them. I fixed it so the filter protects what my friend types, and only flags contributor notes for my review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Honest limitations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A 3B-parameter model is less fluent than the biggest closed models, especially in [Hinglish/Hindi, if relevant].&lt;/li&gt;
&lt;li&gt;A keyword safety filter is a minimum safeguard, not a guarantee.&lt;/li&gt;
&lt;li&gt;It's only as good as the messages people record, and it can't replace real support.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I couldn't have built this the same way with a closed API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy:&lt;/strong&gt; my friend's most vulnerable moments are about as personal as data gets. With an open-weight model on my own laptop, none of it is sent to a third-party server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; it costs nothing per message, so there's no usage bill and no subscription that could disappear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control:&lt;/strong&gt; I picked the model by testing several, wrote the system prompts myself, and wrote the safety rules myself instead of inheriting a vendor's policy. When the safety filter wrongly blocked a loving note, I could open the code, find out why and fix it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It keeps working:&lt;/strong&gt; it runs offline, and the messages and voice notes stay in a plain SQLite file and a folder I can back up. If any company changes its terms tomorrow, my friend's letters are still there.&lt;/li&gt;
&lt;/ul&gt;

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