<?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: Paulo Ricardo Engcompaulo</title>
    <description>The latest articles on DEV Community by Paulo Ricardo Engcompaulo (@engcompaulo).</description>
    <link>https://dev.to/engcompaulo</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%2F360798%2F4e146e72-ecaa-49bd-98d2-ce05d047a287.jpeg</url>
      <title>DEV Community: Paulo Ricardo Engcompaulo</title>
      <link>https://dev.to/engcompaulo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/engcompaulo"/>
    <language>en</language>
    <item>
      <title>Study Trail Builder</title>
      <dc:creator>Paulo Ricardo Engcompaulo</dc:creator>
      <pubDate>Fri, 23 Oct 2026 23:03:57 +0000</pubDate>
      <link>https://dev.to/engcompaulo/study-trail-builder-3201</link>
      <guid>https://dev.to/engcompaulo/study-trail-builder-3201</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-20"&gt;Hacktoberfest Open-Source AI Challenge Week 4: 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;Study Trail Builder&lt;/strong&gt; turns any study topic into an ordered, beginner-to-advanced learning trail of videos. You type a topic — say "limits in calculus" — and you get back at least ten YouTube videos, sequenced and grouped into &lt;strong&gt;Basic&lt;/strong&gt;, &lt;strong&gt;Intermediate&lt;/strong&gt;, and &lt;strong&gt;Advanced&lt;/strong&gt;, each shown as a title next to its link.&lt;/p&gt;

&lt;p&gt;The tedious part of self-teaching from YouTube is not watching the videos — it is the curation that comes first. You search, skim a dozen results, guess which ones are beginner-friendly, figure out a sensible order, and only then start learning. Study Trail Builder does all of that boring groundwork for you: it searches, vets candidates by popularity signals, sequences them easiest-to-hardest, and splits them into three difficulty tiers. True to the &lt;strong&gt;Slow and Steady&lt;/strong&gt; spirit, it rewards laziness by taking a slow, methodical chore off your plate so you can just press play and move at your own pace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who benefits:&lt;/strong&gt; anyone picking up a new subject on their own — students, career switchers, the perpetually curious — who wants a ready-made path instead of a wall of search results.&lt;/p&gt;

&lt;p&gt;The product's defining property is that it is never allowed to fail you. Artificial intelligence does the smart part (ordering and grouping), but every AI call is wrapped in a bounded timeout with a deterministic fallback, so a result always comes back.&lt;/p&gt;

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

&lt;p&gt;Run it locally with Docker under WSL (see the ordered run steps below) and open &lt;strong&gt;&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Type a topic, submit, and the app returns a grouped trail. Links open in a new tab, so your trail stays put while you watch.&lt;/p&gt;

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

&lt;p&gt;All code lives in &lt;code&gt;desafio4/&lt;/code&gt; and is deliberately small — a Django backend, an Angular micro frontend, and a three-service Docker stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend (&lt;code&gt;backend/trail/&lt;/code&gt;)&lt;/strong&gt; — Django with plain &lt;code&gt;JsonResponse&lt;/code&gt; views (no DRF for a single endpoint). The risky logic is pure and unit-testable; the network lives at the edges.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;search.py&lt;/code&gt; — the &lt;code&gt;yt-dlp&lt;/code&gt; wrapper. Runs a keyless &lt;code&gt;ytsearchN:&lt;/code&gt; query, normalizes raw entries into &lt;code&gt;Video&lt;/code&gt; records, and drops anything missing a title, URL, or positive duration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ai.py&lt;/code&gt; — the Ollama client, prompt builder, and reply parser. Calls &lt;code&gt;POST /api/generate&lt;/code&gt; with a bounded timeout and parses the id-to-level mapping; it never raises.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;heuristic.py&lt;/code&gt; — the deterministic fallback. Orders candidates by views, then likes, then duration, with the video id as the final tie-break, and splits them into three tiers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;builder.py&lt;/code&gt; — the AI-first orchestration. Tries the AI ranking; if the result is unusable, it falls back to the heuristic. Either path produces the same shape.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;views.py&lt;/code&gt; — the single &lt;code&gt;POST /api/trail&lt;/code&gt; endpoint: validate the topic, search, build the trail, and serialize it Basic → Intermediate → Advanced.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/&lt;/code&gt; — minimal pytest + Hypothesis property tests covering heuristic determinism and AI-reply parsing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend (&lt;code&gt;frontend/src/app/&lt;/code&gt;)&lt;/strong&gt; — a minimal single-view Angular standalone app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app.ts&lt;/code&gt; — the topic-entry component: one input (1–200 chars), submit, loading state, and client-side validation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;trail-view.component.ts&lt;/code&gt; — renders the three sections top-to-bottom, each video's title and link on the same row.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;trail.service.ts&lt;/code&gt; — POSTs &lt;code&gt;/api/trail&lt;/code&gt; with a 30-second client timeout.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nginx.conf&lt;/code&gt; — serves the static Angular build and reverse-proxies &lt;code&gt;/api/*&lt;/code&gt; to the backend (same-origin, no CORS preflight).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/strong&gt; — the three services: &lt;code&gt;frontend&lt;/code&gt; (Angular + nginx), &lt;code&gt;backend&lt;/code&gt; (Django + gunicorn), and &lt;code&gt;ollama&lt;/code&gt; (the AI service), wired with &lt;code&gt;depends_on&lt;/code&gt; and healthchecks.&lt;/p&gt;

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

&lt;p&gt;The whole thing runs on an open-source stack with a locally hosted open-weight model at its core:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Django + gunicorn&lt;/strong&gt; serve a single REST-style endpoint in a lightweight container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;yt-dlp&lt;/strong&gt; searches YouTube with &lt;strong&gt;no API key&lt;/strong&gt; — a &lt;code&gt;ytsearchN:&lt;/code&gt; query with flat extraction returns titles, URLs, durations, and popularity counts without a paid developer account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; runs the open-weight &lt;strong&gt;&lt;code&gt;llama3.2:1b&lt;/code&gt;&lt;/strong&gt; model entirely on the host. It does two narrow jobs through short prompts to keep token counts low: (1) order the candidate videos basic-to-advanced, and (2) assign each one a Basic / Intermediate / Advanced level.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Angular + nginx&lt;/strong&gt; make up the micro frontend — a static build served by nginx, which also proxies API calls to the backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Graceful degradation is the headline design decision.&lt;/strong&gt; Every call into the AI layer is bounded by a &lt;strong&gt;10-second timeout&lt;/strong&gt; and guarded against connection failures, non-200 responses, and malformed output. On any failure — Ollama slow, down, or returning something unparseable — the backend silently routes to a &lt;strong&gt;deterministic heuristic&lt;/strong&gt; that orders by view count, then like count, then duration. Both paths return the identical three-section shape, so the frontend never knows or cares which one ran. The AI makes the trail smarter; it is never a single point of failure. The app does not error out just because the model had a bad moment.&lt;/p&gt;

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

&lt;p&gt;Running an &lt;strong&gt;open-weight model locally&lt;/strong&gt; instead of calling a closed, hosted API is what makes this project practical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No per-call cost.&lt;/strong&gt; Build as many trails as you like at zero marginal cost. For a free learning tool, metered API calls would be a non-starter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by default.&lt;/strong&gt; The topics you study never leave your machine. There is no third party logging what you are trying to learn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works offline.&lt;/strong&gt; The model is local, and even if it is unavailable, the heuristic fallback keeps every trail working — no dependency on a paid cloud API staying up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable model.&lt;/strong&gt; &lt;code&gt;llama3.2:1b&lt;/code&gt; is the small default, but because it is just an Ollama model behind an env var, you can point the app at a larger open-weight model without touching the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An open approach wins here precisely because the valuable part — curating a learning path — is something you want to run cheaply, privately, and repeatedly. A closed API would push the intelligence, the cost, and your search history into someone else's cloud. Open weights plus a tiny footprint keep all of that on hardware you control.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

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




&lt;h3&gt;
  
  
  Run it with Docker under WSL
&lt;/h3&gt;

&lt;p&gt;From the &lt;code&gt;desafio4/&lt;/code&gt; directory, under WSL:&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. Build and start all three services (frontend, backend, ollama)&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;


&lt;span class="o"&gt;![&lt;/span&gt; &lt;span class="o"&gt;](&lt;/span&gt;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/5r0nslnwogdvjhay8due.png&lt;span class="o"&gt;)&lt;/span&gt;



&lt;span class="c"&gt;# 2. In a second terminal, pull the model into the ollama service&lt;/span&gt;
&lt;span class="c"&gt;#    (one time only; the model is cached on the ollama_data volume afterwards)&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;ollama ollama pull llama3.2:1b

&lt;span class="c"&gt;# 3. Open the app&lt;/span&gt;
&lt;span class="c"&gt;#    http://localhost:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model download in step 2 is a &lt;strong&gt;one-time&lt;/strong&gt; operation — it is cached on the &lt;code&gt;ollama_data&lt;/code&gt; named volume and survives container restarts, so you only pull it once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;The defaults work for a standard local run; &lt;strong&gt;none of these need to change&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Default&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;OLLAMA_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://ollama:11434&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;URL of the Ollama service inside the compose network.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OLLAMA_MODEL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llama3.2:1b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The open-weight model the backend asks to order and group videos.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ports used by the stack:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;8080&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;frontend (nginx)&lt;/td&gt;
&lt;td&gt;The only port you open in a browser → &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;8000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;backend (gunicorn)&lt;/td&gt;
&lt;td&gt;Published for debugging; nginx proxies to it internally.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;11434&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ollama&lt;/td&gt;
&lt;td&gt;The AI service; the backend talks to it over the compose network.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;OLLAMA_URL&lt;/code&gt; and &lt;code&gt;OLLAMA_MODEL&lt;/code&gt; are set as environment variables on the &lt;code&gt;backend&lt;/code&gt; service in &lt;code&gt;docker-compose.yml&lt;/code&gt;; override them there if you want to point at a different Ollama host or model.&lt;/p&gt;

&lt;h1&gt;
  
  
  Example
&lt;/h1&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%2Frzzhar4wtjex16xvrbpa.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%2Frzzhar4wtjex16xvrbpa.png" alt=" " width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"I want to learn about Python"&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%2Fjwqz3zbtrkxgux8ewd7k.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%2Fjwqz3zbtrkxgux8ewd7k.png" alt=" " width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  GITHUB
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/Engcompaulo/study_trail_builder" rel="noopener noreferrer"&gt;GITHUB&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Neighbor Partner</title>
      <dc:creator>Paulo Ricardo Engcompaulo</dc:creator>
      <pubDate>Mon, 12 Oct 2026 22:26:20 +0000</pubDate>
      <link>https://dev.to/engcompaulo/neighbor-partner-4dbh</link>
      <guid>https://dev.to/engcompaulo/neighbor-partner-4dbh</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;&lt;strong&gt;Neighbor Partner&lt;/strong&gt; is a tiny web app that helps independent local shops find each other and cross-promote.&lt;/p&gt;

&lt;p&gt;Every neighborhood is full of small businesses that are not competitors but could easily send each other customers: a bakery and a bookstore, a gym and a café, a florist and a photographer. The problem is discovery. Owners are busy running their shops and rarely know who is one block over, let alone which of those neighbors complements their business instead of competing with it.&lt;/p&gt;

&lt;p&gt;Neighbor Partner solves that. A shop owner registers their shop in seconds (name, segment, target audience, neighborhood), browses the public list of registered shops, and opens any shop to see up to three AI-ranked &lt;strong&gt;non-competing&lt;/strong&gt; partner suggestions in the same neighborhood. For any pair, one click generates a short promotional blurb they can drop straight into a window sign, a flyer, or an Instagram post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who benefits:&lt;/strong&gt; local shop owners who want more foot traffic without an ad budget, and the neighborhoods that keep more of their trade local. It is the kind of lightweight tool a neighborhood association, a co-working space, or a chamber of commerce could host for its members for almost nothing.&lt;/p&gt;

&lt;p&gt;The core safety rule — never suggest a competitor — is enforced in SQL (same neighborhood, different segment) &lt;em&gt;before&lt;/em&gt; the AI ever sees the data. The model only reorders a candidate set that is already guaranteed to be safe.&lt;/p&gt;

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

&lt;p&gt;Run it locally with Docker (see the run steps below) and open &lt;strong&gt;&lt;a href="http://localhost:5000" rel="noopener noreferrer"&gt;http://localhost:5000&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On first start the app seeds a handful of example shops across two neighborhoods (Old Town and Riverside), each with multiple segments, so partner suggestions and promo blurbs work immediately without any manual setup.&lt;/p&gt;

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

&lt;p&gt;All code lives in &lt;code&gt;desafio3/&lt;/code&gt; and is deliberately small — three Python modules, three templates, one stylesheet, and a two-service Docker stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app.py&lt;/code&gt; — Flask routes and app factory; orchestrates the DB and AI layers, validates the registration form, renders pages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;db.py&lt;/code&gt; — SQLite lifecycle: schema init, first-run seeding, and queries. Owns the non-competing candidate query (&lt;code&gt;neighborhood = ? AND segment != ? AND id != ?&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ai.py&lt;/code&gt; — Ollama client. &lt;code&gt;suggest_partners()&lt;/code&gt; ranks pre-filtered candidates; &lt;code&gt;generate_promo()&lt;/code&gt; writes a short blurb. Both apply a 30s timeout and fall back to local heuristics, and neither ever raises.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;templates/&lt;/code&gt; — &lt;code&gt;base.html&lt;/code&gt;, &lt;code&gt;index.html&lt;/code&gt; (shop list + registration form), &lt;code&gt;suggestions.html&lt;/code&gt; (partner cards + promo).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;static/style.css&lt;/code&gt; — one small responsive stylesheet.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Dockerfile&lt;/code&gt; + &lt;code&gt;docker-compose.yml&lt;/code&gt; — the &lt;code&gt;web&lt;/code&gt; + &lt;code&gt;ollama&lt;/code&gt; services.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The whole thing is built on an open-source stack with a locally hosted model at its core:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flask&lt;/strong&gt; serves the pages and handles form submissions, run under &lt;strong&gt;gunicorn&lt;/strong&gt; in the container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; stores shops in a single file persisted on a Docker named volume, so data survives restarts with zero database server to manage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; runs the open-weight &lt;strong&gt;&lt;code&gt;llama3.2:1b&lt;/code&gt;&lt;/strong&gt; model entirely on the host. It is small enough to fit on a Raspberry Pi with 4 GB of memory, which is exactly the kind of cheap, always-on box a neighborhood group could afford. &lt;code&gt;ai.py&lt;/code&gt; talks to it over HTTP (&lt;code&gt;POST /api/generate&lt;/code&gt;, &lt;code&gt;stream: false&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model does two narrow jobs with short prompts to keep token counts low on modest hardware:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rank partners&lt;/strong&gt; — given a selected shop and a numbered list of &lt;em&gt;already-filtered&lt;/em&gt; candidates, return the ordered ids of the best up to three, matched on segment complementarity and related target audience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write a promo blurb&lt;/strong&gt; — given two shop names and segments, return one or two short English sentences promoting the collaboration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Graceful degradation is the headline design decision.&lt;/strong&gt; Every call into &lt;code&gt;ai.py&lt;/code&gt; is wrapped in a cascade of error handling covering timeouts, connection failures, non-200 responses, and malformed JSON. Each failure routes to a local, deterministic heuristic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If ranking fails, the pre-filtered candidates are ordered heuristically (shared/related target audience, then name) and capped at three.&lt;/li&gt;
&lt;li&gt;If blurb generation fails, a template combines the two names, segments, and neighborhood into a usable sentence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both paths return the same shape to the routes, so the UI renders identically whether the text came from the model or the fallback. The practical result: on a slow or offline Pi, the app never shows an error page — registration, browsing, suggestions, and promos all keep working. The AI makes the output smarter; it is never a single point of failure.&lt;/p&gt;

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

&lt;p&gt;Running an &lt;strong&gt;open-weight model locally&lt;/strong&gt; instead of calling a closed, hosted API changed what this project could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by default.&lt;/strong&gt; Shop details and partnership ideas never leave the owner's own hardware. There is no third party logging who is pairing with whom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It runs on a Raspberry Pi.&lt;/strong&gt; &lt;code&gt;llama3.2:1b&lt;/code&gt; fits in 4 GB of memory, so a neighborhood association can self-host the whole thing on a sub-$100 box. A closed API would push the intelligence into someone else's cloud and someone else's pricing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No per-call cost.&lt;/strong&gt; Suggestions and promo blurbs are generated as often as owners like, with zero marginal cost. For a free community tool, metered API calls would be a non-starter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works offline.&lt;/strong&gt; Flaky connectivity does not break it — the model is local, and even if the model itself is down, the heuristic fallbacks keep every feature usable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open weights plus a tiny footprint are what let a genuinely useful AI feature live on cheap, private, offline-capable hardware. That is the difference between a tool a neighborhood can actually own and one it merely rents.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

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




&lt;h3&gt;
  
  
  Run it on a Raspberry Pi (4 GB) with Docker
&lt;/h3&gt;

&lt;p&gt;From the &lt;code&gt;desafio3/&lt;/code&gt; directory:&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. Build and start the web + ollama services&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;

&lt;span class="c"&gt;# 2. In a second terminal, pull the model into the ollama service (one time; cached afterwards)&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;ollama ollama pull llama3.2:1b

&lt;span class="c"&gt;# 3. Open the app&lt;/span&gt;
&lt;span class="c"&gt;#    http://localhost:5000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ollama&lt;/code&gt; service caches the pulled model on a named volume, and the SQLite database is persisted on its own named volume, so both survive container restarts. The app runs on port &lt;strong&gt;5000&lt;/strong&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%2F6mxbq7xczonbnj98pyg7.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%2F6mxbq7xczonbnj98pyg7.png" alt=" " width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftkbbq8iugupa8o3bvlfi.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%2Ftkbbq8iugupa8o3bvlfi.png" alt=" " width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ofrs87crafbxtaab52t.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%2F1ofrs87crafbxtaab52t.png" alt=" " width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbs8prr8efbv95o5dk6np.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%2Fbs8prr8efbv95o5dk6np.png" alt=" " width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Repository
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/Engcompaulo/neighbor_partner" rel="noopener noreferrer"&gt;GITHUB&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Game Night Scoreboard</title>
      <dc:creator>Paulo Ricardo Engcompaulo</dc:creator>
      <pubDate>Sat, 10 Oct 2026 20:40:54 +0000</pubDate>
      <link>https://dev.to/engcompaulo/game-night-scoreboard-p4j</link>
      <guid>https://dev.to/engcompaulo/game-night-scoreboard-p4j</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;The &lt;strong&gt;Board Game Scoreboard&lt;/strong&gt; is an interactive electronic scoreboard for board game nights, built for Challenge 2 of Hacktoberfest 2026 with the "Touch Grass" theme (getting people off their screens and into the real world playing together). I made it for my group of friends, who get together to play Uno, dominoes, checkers, and cards, but always waste time arguing over which game to start with and jotting points on scraps of paper that get lost in the mess. The app solves that by organizing the session into simple steps and keeping the scoreboard for the whole night: the group checks off the games they own, registers the players, sets how much each game is worth, records the results as matches wrap up, and, at the end, determines the Winner of the Day complete with a certificate. The approach is deliberately minimalist about screen time: the app is just the organizer and the scoreboard, so the focus stays on the real game, at the table, among people.&lt;/p&gt;

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

&lt;p&gt;There's no public deployment, because the project is meant to run entirely on the group's own machine, without relying on the internet. To try it out, just clone the repository, go into the &lt;code&gt;touch_grass&lt;/code&gt; folder, and bring everything up with &lt;code&gt;docker compose up&lt;/code&gt;: in a few seconds the frontend is available at &lt;code&gt;http://localhost:8080&lt;/code&gt; and the local AI service responds at &lt;code&gt;http://localhost:11434&lt;/code&gt;. From there, just follow the six steps right in the interface, in English. &lt;em&gt;(Placeholder for a short video demonstrating a game night end to end.)&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;The full source code is in the &lt;code&gt;touch_grass&lt;/code&gt; directory of this repository. The frontend is plain JavaScript (ES modules), HTML, and CSS, organized into pure-logic modules (&lt;code&gt;validation&lt;/code&gt;, &lt;code&gt;catalog&lt;/code&gt;, &lt;code&gt;players&lt;/code&gt;, &lt;code&gt;scoring&lt;/code&gt;, &lt;code&gt;suggestion&lt;/code&gt;, &lt;code&gt;scoreboard&lt;/code&gt;, &lt;code&gt;results&lt;/code&gt;, &lt;code&gt;state&lt;/code&gt;) along with tests using Vitest and fast-check. The runtime infrastructure lives in the &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;docker-compose.yml&lt;/code&gt;, which define exactly two services: the frontend and the AI service.&lt;/p&gt;

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

&lt;p&gt;The core piece that makes the project work is an open-source AI: a lightweight open-weight model (for example, &lt;code&gt;llama3.2&lt;/code&gt;) served by &lt;strong&gt;Ollama&lt;/strong&gt;, running locally in a Docker container alongside the frontend. From the games the group selected and the number of people present, the app builds a prompt and asks the model for a suggested order of matches for the night. The whole project is built around that suggestion, but with &lt;strong&gt;graceful degradation&lt;/strong&gt; as a design principle: if Ollama is unavailable, slow, or returns an error, the app cancels the call after a timeout and falls back to a sequence generated locally in JavaScript that covers each selected game at least once. That way, the AI's intelligence enriches the experience when it's available, and the local fallback ensures game night never stalls because of it.&lt;/p&gt;

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

&lt;p&gt;The open-source approach works best here precisely because game night happens at someone's living room table, often without reliable internet. With an open-weight model running locally via Ollama, the app keeps working offline, without depending on any external API. The session data — player names, games, and scores — stays in the browser's &lt;code&gt;localStorage&lt;/code&gt; and in the calls to the local service, remaining on the user's machine instead of traveling to third-party servers, which preserves the group's privacy. And since there's no closed API in the way, there's no per-request cost or usage limits: anyone can spin up the project and use it freely. A closed API wouldn't make this possible without a permanent connection, without cost, and without giving up data privacy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&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%2Ff0kzn5zy5h9ge1nxtfte.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%2Ff0kzn5zy5h9ge1nxtfte.png" alt=" " width="800" height="828"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7krxkzv8c8uo9osdii9v.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%2F7krxkzv8c8uo9osdii9v.png" alt=" " width="800" height="746"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F98hmsjsekb61onec8xhu.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%2F98hmsjsekb61onec8xhu.png" alt=" " width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faydiioli1uw43lpcz4lk.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%2Faydiioli1uw43lpcz4lk.png" alt=" " width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdvtqdftdn92408xu13f2.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%2Fdvtqdftdn92408xu13f2.png" alt=" " width="800" height="655"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9g0jtzv2596vklnsdcf1.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%2F9g0jtzv2596vklnsdcf1.png" alt=" " width="800" height="735"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fly5xpywxoy9hx0j0apr6.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%2Fly5xpywxoy9hx0j0apr6.png" alt=" " width="800" height="695"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffxelwmk0kojrmyky82gz.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%2Ffxelwmk0kojrmyky82gz.png" alt=" " width="799" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F69pa3p5ciclmoylqjhvn.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%2F69pa3p5ciclmoylqjhvn.png" alt=" " width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3str8f6xg6k5a88pw4hn.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%2F3str8f6xg6k5a88pw4hn.png" alt=" " width="800" height="760"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj34borvf0gcpb0mcqzuw.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%2Fj34borvf0gcpb0mcqzuw.png" alt=" " width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqoz1akrs2svyta923wr.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%2Fkqoz1akrs2svyta923wr.png" alt=" " width="800" height="695"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr0qtr30br7hjkwjil3e5.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%2Fr0qtr30br7hjkwjil3e5.png" alt=" " width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Furyahxe5xjcjrb5xsxxk.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%2Furyahxe5xjcjrb5xsxxk.png" alt=" " width="800" height="725"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqwuol3nbrmrtzxa6iz3.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%2Fyqwuol3nbrmrtzxa6iz3.png" alt=" " width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository GITHUB
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Engcompaulo/touch_grass" rel="noopener noreferrer"&gt;GITHUB&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Trigonometry Visualizer</title>
      <dc:creator>Paulo Ricardo Engcompaulo</dc:creator>
      <pubDate>Mon, 05 Oct 2026 00:07:35 +0000</pubDate>
      <link>https://dev.to/engcompaulo/trigonometry-visualizer-26gd</link>
      <guid>https://dev.to/engcompaulo/trigonometry-visualizer-26gd</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;I built &lt;strong&gt;Trigonometry Visualizer&lt;/strong&gt;, a small Flask web app for my friend Marina, who recently went back to school and has been struggling to connect the trigonometry formulas in her textbook to what they actually look like. She kept telling me that &lt;code&gt;y = sin(x)&lt;/code&gt; was just a string of symbols until someone drew the curve for her.&lt;/p&gt;

&lt;p&gt;So the app does exactly that. The Learner picks one of five concepts — sine, cosine, tangent, right triangle, or unit circle — either from a dropdown or by typing it in, and the app renders a matplotlib figure with the mathematical formula embedded right in the picture. She can download any figure as a PNG to paste into her notes, and she can ask for a plain-English explanation of the concept generated by a local AI model. The whole thing is intentionally minimal and English-only, built to be genuinely useful for one person learning the basics rather than a sprawling math suite.&lt;/p&gt;

&lt;p&gt;The core promise is &lt;strong&gt;graceful degradation&lt;/strong&gt;: the figure, formula, and download always work even when the AI explainer is slow or offline, so a flaky model never gets in the way of studying.&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%2Ftzl74k0iy212ls9vcp5d.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%2Ftzl74k0iy212ls9vcp5d.png" alt=" " width="800" height="778"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Run it locally with Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;http://localhost:5000&lt;/code&gt;, choose a concept (try "sine" or "  Unit Circle  " — input is case- and whitespace-insensitive), view the figure and formula, click &lt;strong&gt;Download&lt;/strong&gt; to save the PNG, and click &lt;strong&gt;Explain&lt;/strong&gt; for a plain-English walkthrough.&lt;/p&gt;

&lt;p&gt;A short screen recording of the render → download → explain flow will be attached to the DEV post.&lt;/p&gt;

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

&lt;p&gt;The full source lives in the &lt;code&gt;desafio1/&lt;/code&gt; project folder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app.py&lt;/code&gt; — Flask routes: &lt;code&gt;GET /&lt;/code&gt; (page), &lt;code&gt;GET /render?concept=X[&amp;amp;download=1]&lt;/code&gt; (PNG), &lt;code&gt;GET /explain?concept=X&lt;/code&gt; (JSON explanation)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;concepts.py&lt;/code&gt; — the five-concept registry with normalization and validation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;renderer.py&lt;/code&gt; — matplotlib &lt;code&gt;Agg&lt;/code&gt; draw functions that embed each formula in the figure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;explainer.py&lt;/code&gt; — the Ollama client that degrades gracefully on any failure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs.py&lt;/code&gt; — validator for this very submission file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;templates/&lt;/code&gt;, &lt;code&gt;static/&lt;/code&gt; — the HTML/CSS/JS frontend&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;docker-compose.yml&lt;/code&gt; — the two-service (&lt;code&gt;web&lt;/code&gt; + &lt;code&gt;ollama&lt;/code&gt;) stack
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SUPPORTED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cosine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tangent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;right triangle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unit circle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GitHub repository can be embedded directly in the DEV post.&lt;/p&gt;

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

&lt;p&gt;The explanation feature is powered entirely by &lt;strong&gt;open-source AI&lt;/strong&gt;: a local &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; service running the open-weight &lt;strong&gt;llama3.2&lt;/strong&gt; model. The Flask &lt;code&gt;web&lt;/code&gt; service talks to the &lt;code&gt;ollama&lt;/code&gt; service over the docker-compose network at &lt;code&gt;http://ollama:11434&lt;/code&gt;, posting the selected concept and its formula to &lt;code&gt;/api/generate&lt;/code&gt; with &lt;code&gt;stream: false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Everything runs on your own machine — no third-party API keys, no cloud calls, no data leaving the container network. The &lt;code&gt;explainer.py&lt;/code&gt; module wraps the call with a 30-second timeout and catches every failure mode (timeout, connection error, non-200 status, malformed JSON), returning a friendly "explanation unavailable" message instead of ever crashing. That way the open model enriches the experience when it's up, and the app stays fully usable when it isn't.&lt;/p&gt;

&lt;p&gt;The rest of the stack is deliberately lean: Python 3.12, Flask for routing, matplotlib (&lt;code&gt;Agg&lt;/code&gt; backend) for headless figure rendering, and &lt;code&gt;requests&lt;/code&gt; for the Ollama client — all wired together bottom-up so each module is independently testable.&lt;/p&gt;

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

&lt;p&gt;Open innovation is what made this project possible as a weekend build for a single friend. Using an open-weight model through Ollama meant I could ship an AI tutor that costs nothing to run, needs no API key, and keeps a student's questions entirely on her own machine. A closed API would have added a billing account, rate limits, and a privacy footprint — all overkill for one person learning trig at her kitchen table.&lt;/p&gt;

&lt;p&gt;More than cost, open models let me design for &lt;strong&gt;graceful degradation on my own terms&lt;/strong&gt;: because the model runs locally and is swappable, I control the timeout, the fallback behavior, and even which model to pull. If llama3.2 is too heavy on an older laptop, swapping in a smaller open model is a one-line change. That kind of transparency and substitutability is exactly what open innovation unlocks — the app belongs to the people running it, not to a vendor's roadmap.&lt;/p&gt;

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

&lt;p&gt;I built this spec-first with Kiro: requirements → design → tasks, then implemented each module in dependency-wave order. The agent session covering the build will be linked from the DEV post.&lt;/p&gt;

&lt;p&gt;Result: &lt;a href="https://github.com/Engcompaulo/trigonometry_visualizer/tree/develop" rel="noopener noreferrer"&gt;GITHUB&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Build for a Friend&lt;/li&gt;
&lt;li&gt;Best use of a local, open-weight model (Ollama / llama3.2)&lt;/li&gt;
&lt;/ul&gt;

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