<?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: Arun Yadav</title>
    <description>The latest articles on DEV Community by Arun Yadav (@itsdevcode).</description>
    <link>https://dev.to/itsdevcode</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%2F4117982%2F5574f0ee-a19b-4ce6-bd55-52c21a7097e3.png</url>
      <title>DEV Community: Arun Yadav</title>
      <link>https://dev.to/itsdevcode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/itsdevcode"/>
    <language>en</language>
    <item>
      <title>RecallLoop: I Built an AI That Turns Forgotten Bookmarks Into Lasting Knowledge</title>
      <dc:creator>Arun Yadav</dc:creator>
      <pubDate>Sun, 25 Oct 2026 10:36:50 +0000</pubDate>
      <link>https://dev.to/itsdevcode/recallloop-i-built-an-ai-that-turns-forgotten-bookmarks-into-lasting-knowledge-1eam</link>
      <guid>https://dev.to/itsdevcode/recallloop-i-built-an-ai-that-turns-forgotten-bookmarks-into-lasting-knowledge-1eam</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;p&gt;I have a habit that I suspect many developers share.&lt;/p&gt;

&lt;p&gt;I discover an interesting article about system design, Python, databases, or something completely new. I think, &lt;em&gt;"This looks useful. I'll read it properly later."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I bookmark it.&lt;/p&gt;

&lt;p&gt;Then I find another article. And another.&lt;/p&gt;

&lt;p&gt;A few weeks later, my bookmarks have become a collection of things I wanted to learn but barely remember.&lt;/p&gt;

&lt;p&gt;The problem isn't that I can't save information. &lt;strong&gt;It's that saving information doesn't mean I've learned it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this week's &lt;em&gt;Slow and Steady&lt;/em&gt; challenge, I wanted to build something that does the repetitive preparation work for me, while making it easier to build knowledge over time.&lt;/p&gt;

&lt;p&gt;That's how &lt;strong&gt;RecallLoop&lt;/strong&gt; started.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Save it. Learn it. Remember it.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;RecallLoop is an open-source, AI-powered learning companion that transforms saved articles into active-recall questions and schedules future reviews using spaced repetition.&lt;/p&gt;

&lt;p&gt;Instead of manually rereading every article or creating flashcards from scratch, users can save a URL and let RecallLoop prepare the learning material.&lt;/p&gt;

&lt;p&gt;Here's the experience:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Save an article&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Paste a public article URL into RecallLoop. The application fetches the page and extracts its readable content, removing much of the surrounding website clutter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Let AI find the important ideas&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google's open-weight &lt;strong&gt;Gemma 4&lt;/strong&gt; model analyzes the extracted text and generates up to three meaningful concepts, with explanations, questions, answers, and supporting excerpts from the original article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Practice active recall&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RecallLoop presents a question without immediately revealing the answer.&lt;/p&gt;

&lt;p&gt;Try to remember it, reveal the answer, and assess how well you recalled the concept.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Choose how well you remembered&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each review has three options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forgot:&lt;/strong&gt; Review again in approximately 10 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard:&lt;/strong&gt; Review again sooner, initially after one day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy:&lt;/strong&gt; Start with a longer interval, initially three days.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Future intervals adapt to your review history using a deterministic scheduling algorithm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Return when it's time to review&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RecallLoop tracks due cards, upcoming review dates, and review history.&lt;/p&gt;

&lt;p&gt;You don't have to repeatedly decide what to study next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who is it for?
&lt;/h3&gt;

&lt;p&gt;I built RecallLoop for developers, students, self-learners, and anyone who saves useful articles but struggles to revisit them.&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let the machine prepare the questions. Let the learner do the remembering.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction matters. RecallLoop doesn't eliminate learning effort. It eliminates some of the repetitive work surrounding learning.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live application:&lt;/strong&gt; &lt;a href="https://recallloop-web.onrender.com/" rel="noopener noreferrer"&gt;https://recallloop-web.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/itsdevcode/recallloop" rel="noopener noreferrer"&gt;https://github.com/itsdevcode/recallloop&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  RecallLoop interface
&lt;/h3&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%2Fraw.githubusercontent.com%2Fitsdevcode%2Frecallloop%2Fmain%2Fdocs%2Fscreenshots%2Flanding-desktop.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%2Fraw.githubusercontent.com%2Fitsdevcode%2Frecallloop%2Fmain%2Fdocs%2Fscreenshots%2Flanding-desktop.png" alt="RecallLoop landing page" width="800" height="2561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;RecallLoop's landing page introduces a simpler way to turn saved knowledge into something useful.&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%2Fmlyw5zxd4y69x8f6cqi0.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%2Fmlyw5zxd4y69x8f6cqi0.png" alt="RecallLoop dashboard" width="800" height="707"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The dashboard brings together saved articles, due reviews, and learning progress.&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%2Fcgkbgd9heaqoh9uk24mu.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%2Fcgkbgd9heaqoh9uk24mu.png" alt="RecallLoop active recall" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The review experience focuses on one question at a time, with the answer hidden until the learner is ready.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The demo is hosted using Render's free-tier infrastructure. Free services may sleep after inactivity, so the first request can take longer while the application wakes up.&lt;/p&gt;

&lt;p&gt;The demonstration is intended for evaluation rather than uninterrupted production usage.&lt;/p&gt;

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

&lt;p&gt;The complete project is open source:&lt;/p&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/itsdevcode" rel="noopener noreferrer"&gt;
        itsdevcode
      &lt;/a&gt; / &lt;a href="https://github.com/itsdevcode/recallloop" rel="noopener noreferrer"&gt;
        recallloop
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Turn saved articles into lasting knowledge with AI-powered recall and spaced repetition.
    &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;RecallLoop&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Save it. Learn it. Remember it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;RecallLoop is an open-source learning companion that turns saved articles into
recall cards and schedules focused reviews with a deterministic spaced-repetition policy
Bookmarks preserve a link. RecallLoop gives its ideas a reason to return
Save a public article, let hosted Gemma create source-grounded questions, and
practice one card at a time. Your recall ratings determine when each card returns.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Implemented and planned&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Implemented:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Independently runnable FastAPI API and Next.js frontend.&lt;/li&gt;
&lt;li&gt;PostgreSQL users with database-generated UUIDs, normalized unique emails
Argon2 password hashes, active status, and timezone-aware timestamps.&lt;/li&gt;
&lt;li&gt;Registration, login, current profile, and logout endpoints.&lt;/li&gt;
&lt;li&gt;Fifteen-minute HS256 JWTs transported only in an HttpOnly cookie.&lt;/li&gt;
&lt;li&gt;Same-origin API proxies, CSRF protection, registration/login pages, and a protected dashboard.&lt;/li&gt;
&lt;li&gt;Per-user article saving, paginated library, readable text details, and confirmed deletion.&lt;/li&gt;
&lt;li&gt;Bounded, SSRF-protected public HTML fetching and Trafilatura extraction in Celery.&lt;/li&gt;
&lt;li&gt;Hosted Gemma concepts and recall questions/answers with source evidence…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/itsdevcode/recallloop" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;RecallLoop uses a monorepo structure with separate frontend and backend applications.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;recallloop/
├── recallloop-api/
│   ├── app/
│   │   ├── ai/
│   │   ├── api/
│   │   ├── core/
│   │   ├── models/
│   │   ├── services/
│   │   └── workers/
│   ├── alembic/
│   └── tests/
├── recallloop-web/
│   ├── src/
│   └── tests/
├── docs/
├── render.yaml
└── render.free.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository contains the source code, database migrations, AI integration, review scheduling, deployment configurations, and tests.&lt;/p&gt;

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

&lt;p&gt;RecallLoop combines an open-weight language model, background processing, a relational database, and a focused web interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Next.js, React, TypeScript, Tailwind CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;FastAPI, Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ORM and migrations&lt;/td&gt;
&lt;td&gt;SQLAlchemy, Alembic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background processing&lt;/td&gt;
&lt;td&gt;Celery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message broker&lt;/td&gt;
&lt;td&gt;Redis-compatible Valkey&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Article extraction&lt;/td&gt;
&lt;td&gt;Trafilatura&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI model&lt;/td&gt;
&lt;td&gt;Gemma 4 (&lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI integration&lt;/td&gt;
&lt;td&gt;Google GenAI SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Render&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;pytest, Playwright&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1. Extracting useful content
&lt;/h3&gt;

&lt;p&gt;A normal article webpage contains navigation, advertisements, menus, and other elements that aren't helpful when generating learning material.&lt;/p&gt;

&lt;p&gt;RecallLoop uses Trafilatura to extract readable article text.&lt;/p&gt;

&lt;p&gt;The fetching layer includes protections such as URL validation, public-network restrictions, redirect checks, and resource limits.&lt;/p&gt;

&lt;p&gt;This was important because the application accepts URLs supplied by users.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Generating concepts with Gemma
&lt;/h3&gt;

&lt;p&gt;The core AI component uses Google's open-weight &lt;strong&gt;Gemma 4 26B&lt;/strong&gt; model through hosted inference.&lt;/p&gt;

&lt;p&gt;I chose Gemma because I wanted an open-weight model to power the most important feature of the application: turning source material into useful learning questions.&lt;/p&gt;

&lt;p&gt;The model is instructed to return structured information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"concepts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Database Indexes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"explanation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A database index helps locate records efficiently."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_excerpt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Exact supporting passage copied from the article"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Why can an index improve database query performance?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"It provides an efficient lookup path instead of requiring every row to be examined."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Illustrative structure; not a verbatim model response.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Each concept must satisfy validation rules.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Questions and answers must be meaningful.&lt;/li&gt;
&lt;li&gt;Duplicate questions and concepts are rejected.&lt;/li&gt;
&lt;li&gt;The supporting source excerpt must appear in the extracted article text.&lt;/li&gt;
&lt;li&gt;Invalid responses can trigger a bounded repair attempt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model is not allowed to invent source evidence.&lt;/p&gt;

&lt;p&gt;This makes RecallLoop more than a generic text-generation interface. The AI output has to be usable within a real learning workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The unexpected JSON problem
&lt;/h3&gt;

&lt;p&gt;One of the interesting technical challenges appeared during live Gemma testing.&lt;/p&gt;

&lt;p&gt;My first smoke test failed with a generic &lt;code&gt;AIError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After adding safer diagnostics, I discovered that the model sometimes returned valid JSON wrapped in Markdown code fences.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"concepts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original parser expected raw JSON, so it rejected that response.&lt;/p&gt;

&lt;p&gt;I added a JSON extraction step while keeping Pydantic validation and source-evidence verification.&lt;/p&gt;

&lt;p&gt;Later testing also revealed occasional schema-validation failures. A subsequent real smoke test successfully generated and validated &lt;strong&gt;three concepts in approximately nine seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That was a valuable lesson: &lt;strong&gt;a successful model response isn't automatically a valid application response.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When using AI inside software, output validation is part of the product, not an optional extra.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Background processing with Celery
&lt;/h3&gt;

&lt;p&gt;Article processing involves multiple steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Save URL
   ↓
PostgreSQL: PENDING
   ↓
Celery worker
   ↓
Fetch and extract article
   ↓
Gemma generates learning material
   ↓
Validate concepts and evidence
   ↓
PostgreSQL: READY
   ↓
Recall session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn't want users to sit on a loading screen while extraction and AI generation happened inside an HTTP request.&lt;/p&gt;

&lt;p&gt;FastAPI saves the article and queues processing. Celery handles extraction and generation in the background.&lt;/p&gt;

&lt;p&gt;The implementation includes retry limits, processing leases, and recovery logic to handle interrupted jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Spaced repetition without asking AI to schedule reviews
&lt;/h3&gt;

&lt;p&gt;I deliberately kept scheduling separate from Gemma.&lt;/p&gt;

&lt;p&gt;AI generates the concepts and questions.&lt;/p&gt;

&lt;p&gt;A deterministic Python algorithm decides the next review time.&lt;/p&gt;

&lt;p&gt;For example, a forgotten card comes back quickly, while an easily remembered card receives a longer interval.&lt;/p&gt;

&lt;p&gt;The system persists review history, timestamps, repetition counts, and scheduling state in PostgreSQL.&lt;/p&gt;

&lt;p&gt;This approach makes scheduling predictable, testable, and independent of model output.&lt;/p&gt;

&lt;p&gt;It's a simplified learning policy, not a claim to implement FSRS or the full SM-2 algorithm.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Deploying on Render's free tier
&lt;/h3&gt;

&lt;p&gt;RecallLoop has two Render configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;render.yaml&lt;/code&gt; for the original paid production architecture.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;render.free.yaml&lt;/code&gt; for a lower-cost demonstration setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The free configuration runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js frontend&lt;/li&gt;
&lt;li&gt;FastAPI backend&lt;/li&gt;
&lt;li&gt;PostgreSQL database&lt;/li&gt;
&lt;li&gt;Valkey broker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because dedicated background workers require paid compute, the free demonstration colocates FastAPI and Celery in one web service.&lt;/p&gt;

&lt;p&gt;That introduces trade-offs: both processes share limited memory, the service can sleep, and the free database has a limited lifetime.&lt;/p&gt;

&lt;p&gt;I chose to document those limitations rather than describe the demo architecture as production-ready.&lt;/p&gt;

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

&lt;p&gt;For RecallLoop, open innovation is about control over the technology that makes the product valuable.&lt;/p&gt;

&lt;p&gt;Gemma's open-weight availability means that the learning-generation component is not conceptually tied to one proprietary model endpoint.&lt;/p&gt;

&lt;p&gt;Today, my implementation uses hosted Gemma inference through Google's API. It does &lt;strong&gt;not&lt;/strong&gt; run offline.&lt;/p&gt;

&lt;p&gt;However, an open-weight model creates opportunities for future development:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model portability:&lt;/strong&gt; The inference layer can potentially be adapted to different compatible hosting environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Experimentation:&lt;/strong&gt; Developers can study how prompt design, validation rules, and model selection affect generated learning material.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customization:&lt;/strong&gt; The system could eventually support models adapted for different subjects or learning styles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transparency:&lt;/strong&gt; Developers can inspect how RecallLoop extracts articles, validates model responses, and schedules reviews.&lt;/p&gt;

&lt;p&gt;A closed API could also generate flashcards. The advantage of the open-weight approach is the flexibility to explore alternatives without making the underlying model permanently dependent on one proprietary provider.&lt;/p&gt;

&lt;p&gt;Open innovation makes experimentation and collaboration easier.&lt;/p&gt;

&lt;p&gt;And because RecallLoop itself is open source, other developers can examine its architecture, improve the scheduling algorithm, or build new learning experiences around it.&lt;/p&gt;

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

&lt;p&gt;The repetitive work I wanted to eliminate wasn't the act of studying.&lt;/p&gt;

&lt;p&gt;It was everything around studying.&lt;/p&gt;

&lt;p&gt;Before RecallLoop, turning an article into a review routine meant manually identifying concepts, writing questions, recording answers, and deciding when to revisit them.&lt;/p&gt;

&lt;p&gt;With RecallLoop, that preparation is handled by the application.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before RecallLoop&lt;/th&gt;
&lt;th&gt;With RecallLoop&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manually identify important ideas&lt;/td&gt;
&lt;td&gt;Gemma proposes source-grounded concepts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write flashcard questions&lt;/td&gt;
&lt;td&gt;Questions and answers are generated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Track review dates manually&lt;/td&gt;
&lt;td&gt;Reviews are scheduled automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search through old bookmarks&lt;/td&gt;
&lt;td&gt;Due cards appear in one place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decide what to revise next&lt;/td&gt;
&lt;td&gt;A focused review queue guides the session&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I haven't measured long-term retention improvements or time saved across real users yet.&lt;/p&gt;

&lt;p&gt;But the core workflow already demonstrates the benefit: a saved article can become structured learning material without manually creating each flashcard.&lt;/p&gt;

&lt;p&gt;The learner still has to think.&lt;/p&gt;

&lt;p&gt;The application handles much of the preparation and organization.&lt;/p&gt;

&lt;p&gt;That's the kind of laziness I wanted to reward.&lt;/p&gt;

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

&lt;p&gt;This project taught me several practical lessons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, model output needs careful validation.&lt;/strong&gt; Even when a model is available and responding, the output can violate a strict schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, background processing requires recovery planning.&lt;/strong&gt; Fetching content and generating questions aren't always reliable operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, simpler algorithms can be useful.&lt;/strong&gt; A transparent scheduling policy was easier to reason about and test than introducing unnecessary complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally, deployment constraints shape architecture.&lt;/strong&gt; Running a backend and worker on a limited free instance requires compromises that should be documented openly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;There are several directions I'd like to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More sophisticated spaced repetition, potentially using FSRS.&lt;/li&gt;
&lt;li&gt;Better article organization.&lt;/li&gt;
&lt;li&gt;Importing existing bookmarks.&lt;/li&gt;
&lt;li&gt;Improved question quality and personalization.&lt;/li&gt;
&lt;li&gt;More flexible open-weight model hosting.&lt;/li&gt;
&lt;li&gt;Better learning analytics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For now, I wanted RecallLoop to solve one focused problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Help people remember more from the things they already save.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;I'm entering:&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Gemma
&lt;/h3&gt;

&lt;p&gt;RecallLoop uses the open-weight Gemma 4 model as its central AI component. Gemma transforms extracted articles into source-grounded learning concepts, questions, and answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Render
&lt;/h3&gt;

&lt;p&gt;RecallLoop's live demonstration is deployed on Render using its web services, PostgreSQL, and Key Value infrastructure. I also built a free-tier deployment configuration that combines the API and Celery worker within a constrained demonstration environment.&lt;/p&gt;




&lt;p&gt;If you've ever saved an article and never opened it again, I'd love to hear how you deal with that problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try RecallLoop:&lt;/strong&gt; &lt;a href="https://recallloop-web.onrender.com/" rel="noopener noreferrer"&gt;https://recallloop-web.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explore the code:&lt;/strong&gt; &lt;a href="https://github.com/itsdevcode/recallloop" rel="noopener noreferrer"&gt;https://github.com/itsdevcode/recallloop&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Save it. Learn it. Remember it.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>GiftMind: Turning Memories Into Meaningful Gifts</title>
      <dc:creator>Arun Yadav</dc:creator>
      <pubDate>Sun, 18 Oct 2026 07:45:49 +0000</pubDate>
      <link>https://dev.to/itsdevcode/giftmind-turning-memories-into-meaningful-gifts-4249</link>
      <guid>https://dev.to/itsdevcode/giftmind-turning-memories-into-meaningful-gifts-4249</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;h1&gt;
  
  
  I Built GiftMind: An AI Side Hustle That Turns Memories Into Meaningful Gifts
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;What if the most meaningful gift isn't something you buy, but a memory you turn into something unforgettable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We've all struggled to find the perfect gift.&lt;/p&gt;

&lt;p&gt;You open an online store, search for "best birthday gifts," browse hundreds of products, and eventually pick something generic.&lt;/p&gt;

&lt;p&gt;The problem isn't a lack of choices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's a lack of personalization.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's why I built &lt;strong&gt;GiftMind&lt;/strong&gt; — an AI-powered gifting platform that transforms someone's interests, personality, and shared memories into personalized gift ideas and story keepsakes.&lt;/p&gt;

&lt;p&gt;Built with Google's open-weight Gemma 4 model and deployed on Render, GiftMind explores how AI could power a practical, affordable digital gifting side hustle.&lt;/p&gt;

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

&lt;p&gt;GiftMind combines two experiences: &lt;strong&gt;AI Gift Discovery&lt;/strong&gt; and &lt;strong&gt;AI Story Keepsakes&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. AI Gift Discovery
&lt;/h3&gt;

&lt;p&gt;GiftMind begins by asking about the person receiving the gift.&lt;/p&gt;

&lt;p&gt;Users create a recipient profile containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Their relationship with the recipient.&lt;/li&gt;
&lt;li&gt;Interests, hobbies, and preferences.&lt;/li&gt;
&lt;li&gt;Personal quirks and meaningful memories.&lt;/li&gt;
&lt;li&gt;The occasion and available budget.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of generating generic shopping recommendations, Gemma 4 uses this context to propose &lt;strong&gt;three personalized gift concepts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each recommendation includes a title, description, estimated budget, personalization reasoning, and a creative presentation idea.&lt;/p&gt;

&lt;p&gt;For example, imagine a friend who loves photography, coffee, vintage aesthetics, and exploring Jaipur.&lt;/p&gt;

&lt;p&gt;Rather than suggesting a generic perfume or gift card, GiftMind can propose a themed photography experience or a personalized memory collection connected to those interests.&lt;/p&gt;

&lt;p&gt;The AI is not just answering, "What should I buy?"&lt;/p&gt;

&lt;p&gt;It's answering, &lt;strong&gt;"Why would this particular person love this gift?"&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. AI Story Keepsakes
&lt;/h3&gt;

&lt;p&gt;This is my favorite part of GiftMind.&lt;/p&gt;

&lt;p&gt;Sometimes, the most valuable gift is a shared story.&lt;/p&gt;

&lt;p&gt;GiftMind includes a guided memory interview that asks users about how they met someone, their favorite moments, important milestones, and what makes that person special.&lt;/p&gt;

&lt;p&gt;Gemma 4 transforms those answers into a personalized story.&lt;/p&gt;

&lt;p&gt;The user can then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read and review the AI-generated story.&lt;/li&gt;
&lt;li&gt;Edit its title and content.&lt;/li&gt;
&lt;li&gt;Save the revised keepsake.&lt;/li&gt;
&lt;li&gt;Download a formatted PDF to share or print.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The experience follows a simple journey:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Share your memories → Generate a personal story → Edit it → Create a keepsake.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of my most important engineering decisions was to ground stories in the memories actually provided by the user.&lt;/p&gt;

&lt;p&gt;The goal is not to manufacture emotional experiences. It's to help people express real ones more beautifully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who Would Pay for GiftMind?
&lt;/h3&gt;

&lt;p&gt;The Week 2 theme is &lt;strong&gt;Side Hustle&lt;/strong&gt;, so I designed GiftMind around a business idea with a plausible paying audience.&lt;/p&gt;

&lt;p&gt;Potential customers include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Couples preparing anniversary surprises.&lt;/li&gt;
&lt;li&gt;Friends creating memorable birthday gifts.&lt;/li&gt;
&lt;li&gt;Families celebrating parents and grandparents.&lt;/li&gt;
&lt;li&gt;People creating personalized graduation or farewell keepsakes.&lt;/li&gt;
&lt;li&gt;Small gifting businesses offering customized digital products.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A potential business model could include free basic gift recommendations and paid premium keepsakes.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Proposed Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic gift discovery&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personalized digital keepsake&lt;/td&gt;
&lt;td&gt;₹99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Premium printable keepsake&lt;/td&gt;
&lt;td&gt;₹249&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customized gift bundles&lt;/td&gt;
&lt;td&gt;Future offering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are &lt;strong&gt;proposed prices, not actual sales or launched payment plans&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The current MVP focuses on validating the experience before implementing payments.&lt;/p&gt;

&lt;p&gt;For an independent developer, this type of product offers an interesting opportunity: a digital gift can be generated and delivered without holding physical inventory.&lt;/p&gt;

&lt;p&gt;Of course, real profitability would depend on inference costs, hosting expenses, conversion rates, and customer demand.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fautvzdfxqfsvia9pvr78.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fautvzdfxqfsvia9pvr78.gif" alt=" " width="760" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GiftMind is deployed on Render.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Application:&lt;/strong&gt; &lt;a href="https://giftmind-web.onrender.com" rel="noopener noreferrer"&gt;https://giftmind-web.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend API:&lt;/strong&gt; &lt;a href="https://giftmind-api.onrender.com" rel="noopener noreferrer"&gt;https://giftmind-api.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Health Endpoint:&lt;/strong&gt; &lt;a href="https://giftmind-api.onrender.com/health" rel="noopener noreferrer"&gt;https://giftmind-api.onrender.com/health&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Try the Experience
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the GiftMind application.&lt;/li&gt;
&lt;li&gt;Create an account or log in.&lt;/li&gt;
&lt;li&gt;Add a recipient and describe their interests.&lt;/li&gt;
&lt;li&gt;Create a gift project with an occasion and budget.&lt;/li&gt;
&lt;li&gt;Generate three personalized gift ideas using Gemma 4.&lt;/li&gt;
&lt;li&gt;Complete the memory interview.&lt;/li&gt;
&lt;li&gt;Generate, edit, and export a story keepsake as a PDF.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The frontend and backend services are deployed successfully. The database migrations have also run on the production database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The complete browser journey should be tested before treating every interaction above as verified in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&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%2Fqyxbc7627ndm38bji70z.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%2Fqyxbc7627ndm38bji70z.png" alt=" " width="800" height="365"&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%2F90l1nsyj2aunrtt06sfd.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%2F90l1nsyj2aunrtt06sfd.png" alt=" " width="800" height="368"&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%2Fw7j95re61wc58acrghoa.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%2Fw7j95re61wc58acrghoa.png" alt=" " width="799" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The entire GiftMind project is available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/itsdevcode/giftmind" rel="noopener noreferrer"&gt;GitHub — itsdevcode/giftmind&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The repository includes the backend, frontend, AI integration, database migrations, and automated tests.&lt;/p&gt;

&lt;p&gt;GiftMind was built as a full-stack application, not just a prompt wrapped in a simple interface.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Technology Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open-weight AI&lt;/td&gt;
&lt;td&gt;Google Gemma 4 26B A4B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI SDK&lt;/td&gt;
&lt;td&gt;Google Gen AI Python SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Python, FastAPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL 16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ORM&lt;/td&gt;
&lt;td&gt;Async SQLAlchemy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migrations&lt;/td&gt;
&lt;td&gt;Alembic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Next.js 15, React, TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF generation&lt;/td&gt;
&lt;td&gt;ReportLab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Render&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI and testing&lt;/td&gt;
&lt;td&gt;GitHub Actions, Pytest, Ruff&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Building Around Gemma 4
&lt;/h3&gt;

&lt;p&gt;GiftMind uses the instruction-tuned model:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I chose Gemma because I wanted an open-weight AI model at the heart of the product rather than relying entirely on a closed-model ecosystem.&lt;/p&gt;

&lt;p&gt;For this challenge, I used Google-hosted inference through the &lt;code&gt;google-genai&lt;/code&gt; SDK.&lt;/p&gt;

&lt;p&gt;That removed the immediate need to run GPU infrastructure, while keeping the application's AI layer designed around an open-weight model.&lt;/p&gt;

&lt;p&gt;The model powers two core workflows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gift recommendations:&lt;/strong&gt; It interprets recipient interests, relationships, occasions, and budgets to create thoughtful suggestions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory-based storytelling:&lt;/strong&gt; It transforms structured user-provided memories into a personal keepsake.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Architecture
&lt;/h3&gt;

&lt;p&gt;The backend separates the AI provider from the application's business logic.&lt;/p&gt;

&lt;p&gt;This allows different parts of the system to remain independently testable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API routes handle validation and access control.&lt;/li&gt;
&lt;li&gt;Services manage gift and story workflows.&lt;/li&gt;
&lt;li&gt;The AI provider handles Gemma inference.&lt;/li&gt;
&lt;li&gt;Structured response validation checks model output.&lt;/li&gt;
&lt;li&gt;PostgreSQL stores the resulting data.&lt;/li&gt;
&lt;li&gt;ReportLab creates the downloadable keepsake.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI responses are validated before persistence instead of assuming every generated response is correctly formatted.&lt;/p&gt;

&lt;p&gt;The application also handles provider errors and timeouts so failed generations don't have to corrupt saved project data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making AI Output More Reliable
&lt;/h3&gt;

&lt;p&gt;A personalized gifting product needs stronger guarantees than a basic chatbot demo.&lt;/p&gt;

&lt;p&gt;I implemented safeguards around:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grounding:&lt;/strong&gt; Story generation uses user-provided memories as its factual foundation and prompts discourage invented experiences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation:&lt;/strong&gt; AI-generated recommendations and stories must match expected application structures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ownership:&lt;/strong&gt; Authentication and project-level authorization restrict access to each user's data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrency:&lt;/strong&gt; PostgreSQL constraints help prevent conflicting active generation requests for the same project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Editing:&lt;/strong&gt; Users can review and modify generated keepsakes before exporting them.&lt;/p&gt;

&lt;p&gt;The user remains the final editor of the personal story.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automated Tests and Real AI Testing
&lt;/h3&gt;

&lt;p&gt;At the completion of the keepsake implementation phase, GiftMind had &lt;strong&gt;74 passing PostgreSQL-backed backend tests&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The test suite covers important application behavior, including authentication, ownership, generation workflows, persistence, and PDF export.&lt;/p&gt;

&lt;p&gt;I also tested the hosted Gemma integration directly.&lt;/p&gt;

&lt;p&gt;One real recommendation smoke test generated three validated personalized gift ideas in approximately &lt;strong&gt;30 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This was a useful lesson: mocked AI responses are excellent for automated tests, but real provider behavior still needs separate verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying GiftMind on Render
&lt;/h3&gt;

&lt;p&gt;For the deployment, I created three separate Render resources:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Next.js Web Service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hosts the public frontend and provides the user interface for gift discovery and keepsake creation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. FastAPI Web Service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Handles authentication, recipient profiles, projects, Gemma 4 integration, story generation, and PDF export.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. PostgreSQL Database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stores users, recipient profiles, gift projects, generated recommendations, memories, and saved keepsakes.&lt;/p&gt;

&lt;p&gt;The application uses secure environment variables for database access, signing secrets, and the Google API key.&lt;/p&gt;

&lt;p&gt;The frontend also uses a server-side API proxy to keep browser API calls on the same origin.&lt;/p&gt;

&lt;p&gt;During the first deployment, I encountered an interesting production issue: the backend rejected an insecure JWT signing-secret placeholder.&lt;/p&gt;

&lt;p&gt;Rather than bypassing the validation, I corrected the environment configuration with a secure random secret.&lt;/p&gt;

&lt;p&gt;After redeployment, the PostgreSQL migrations completed, FastAPI started successfully, and Render marked both web services live.&lt;/p&gt;

&lt;p&gt;It's a small detail, but one that matters: &lt;strong&gt;production safety checks should remain enabled, even during hackathons.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;For a side hustle, choosing the AI foundation is also a business decision.&lt;/p&gt;

&lt;p&gt;A promising product needs room to evolve as its audience, infrastructure costs, and requirements change.&lt;/p&gt;

&lt;p&gt;Open-weight AI offers meaningful flexibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Freedom Beyond One Hosted Endpoint
&lt;/h3&gt;

&lt;p&gt;GiftMind currently uses hosted Gemma inference.&lt;/p&gt;

&lt;p&gt;However, Gemma's available model weights create a path toward future self-hosted deployments or customized infrastructure, subject to the model's license.&lt;/p&gt;

&lt;p&gt;That matters because a product shouldn't need to be redesigned every time its inference strategy changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  More Opportunities for Specialization
&lt;/h3&gt;

&lt;p&gt;Personal gifting is a specialized use case.&lt;/p&gt;

&lt;p&gt;Future versions could experiment with evaluations and adaptation focused on culturally relevant gift ideas, grounded personal storytelling, and different gifting occasions.&lt;/p&gt;

&lt;p&gt;An open-weight foundation makes it possible to explore techniques beyond prompt engineering alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Business Flexibility
&lt;/h3&gt;

&lt;p&gt;A ₹99 digital product cannot support unlimited infrastructure costs.&lt;/p&gt;

&lt;p&gt;As demand grows, having the ability to compare hosted model inference against self-hosting becomes valuable.&lt;/p&gt;

&lt;p&gt;Open-weight models aren't free to operate: compute, storage, hosting, maintenance, and model evaluation still have costs.&lt;/p&gt;

&lt;p&gt;But additional deployment choices help small developers explore sustainable economics.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Open-Source Application Others Can Build On
&lt;/h3&gt;

&lt;p&gt;GiftMind's application code is publicly available.&lt;/p&gt;

&lt;p&gt;Developers can inspect the AI integration, contribute improvements, experiment with prompt strategies, or extend the gifting workflows.&lt;/p&gt;

&lt;p&gt;I believe open innovation matters most when it helps more people build practical products, not just research prototypes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note about privacy:&lt;/strong&gt; The current application uses Google-hosted inference, so relevant recipient information and memories are sent to that provider for processing. GiftMind is not an offline or fully local AI application.&lt;/p&gt;

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

&lt;p&gt;Building GiftMind reinforced an important distinction:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Making an AI API call is easy. Building a usable AI product is harder.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The difficult work includes deciding what the user needs to provide, validating generated output, handling failures, saving progress, protecting user data, and making sure the final result is genuinely useful.&lt;/p&gt;

&lt;p&gt;Another lesson was the importance of narrowing the scope.&lt;/p&gt;

&lt;p&gt;I could have added shopping integrations, payment gateways, image generation, and dozens of gift templates.&lt;/p&gt;

&lt;p&gt;Instead, I focused on two connected experiences: finding a thoughtful gift and turning personal memories into a keepsake.&lt;/p&gt;

&lt;p&gt;For a side hustle, I believe proving that people value the core experience is more important than shipping every feature immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Next?
&lt;/h3&gt;

&lt;p&gt;My next milestone is real customer validation.&lt;/p&gt;

&lt;p&gt;I want to test GiftMind with potential users, observe how they interact with the memory interview, learn whether they would actually gift the resulting PDF, and measure willingness to pay.&lt;/p&gt;

&lt;p&gt;If the response is promising, the next features would be premium printable templates, additional personalization options, and a payment workflow.&lt;/p&gt;

&lt;p&gt;For now, GiftMind is a deployed, open-source MVP exploring a simple idea:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technology should help us make gifts more human, not less.&lt;/strong&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Best Use of Gemma
&lt;/h3&gt;

&lt;p&gt;GiftMind uses Google's open-weight &lt;strong&gt;Gemma 4 26B A4B&lt;/strong&gt; as the core AI model for personalized gift discovery and memory-based story generation.&lt;/p&gt;

&lt;p&gt;Gemma is central to the product's value rather than an optional add-on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Render
&lt;/h3&gt;

&lt;p&gt;GiftMind runs its public Next.js frontend, FastAPI backend, and PostgreSQL database on Render.&lt;/p&gt;

&lt;p&gt;Render provides the application's deployment infrastructure, public service endpoints, environment configuration, and managed database hosting.&lt;/p&gt;

&lt;p&gt;It enabled me to take the project from a local prototype to a publicly hosted MVP without building an infrastructure platform from scratch.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;GiftMind — Turn Memories Into Meaningful Gifts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes the best gift isn't another product.&lt;/p&gt;

&lt;p&gt;It's proof that you remember what matters.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>What If Your City Was Hiding Secrets? I Built WAYBOUND to Find Them.</title>
      <dc:creator>Arun Yadav</dc:creator>
      <pubDate>Sun, 11 Oct 2026 19:42:22 +0000</pubDate>
      <link>https://dev.to/itsdevcode/waybound-turn-your-city-into-a-live-mystery-adventure-21e8</link>
      <guid>https://dev.to/itsdevcode/waybound-turn-your-city-into-a-live-mystery-adventure-21e8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;WAYBOUND&lt;/strong&gt; is an AI-powered, location-based mystery adventure platform that transforms ordinary neighborhood walks into immersive, clue-driven expeditions through real public places.&lt;/p&gt;

&lt;p&gt;Open the app. Share your location. Receive a mystery. Walk toward it.&lt;/p&gt;

&lt;p&gt;The app uses your real GPS coordinates to discover genuinely nearby public landmarks via &lt;strong&gt;Google Places API&lt;/strong&gt;, then hands that real-world data to an open-weight &lt;strong&gt;Google Gemma 2&lt;/strong&gt; AI to generate a unique narrative mission: a title, progressive riddle clues, and a hidden destination revealed only when you physically arrive.&lt;/p&gt;

&lt;p&gt;Who is it for? Anyone who's walked the same routes for years and suspects their city is more interesting than their daily commute suggests. It's built for curious pedestrians, urban explorers, families looking for structured outdoor adventures, and anyone who thinks games shouldn't require a couch.&lt;/p&gt;

&lt;p&gt;The one rule WAYBOUND never breaks: &lt;strong&gt;every place you walk to is real&lt;/strong&gt;. If the real-world discovery pipeline fails, the app says so. It never quietly substitutes a fictional location.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://waybound-frontend.onrender.com/" rel="noopener noreferrer"&gt;▶ Play WAYBOUND&lt;/a&gt;&lt;/strong&gt; · &lt;strong&gt;&lt;a href="https://github.com/itsdevcode/waybound" rel="noopener noreferrer"&gt;⭐ Star on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




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

&lt;p&gt;👉 &lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://waybound-frontend.onrender.com/" rel="noopener noreferrer"&gt;https://waybound-frontend.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full flow in five steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable location&lt;/strong&gt; — browser GPS or pick an exploration city&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose your adventure&lt;/strong&gt; — pick an archetype (&lt;em&gt;Mystery Seeker, Nature Lover, Fitness Scout…&lt;/em&gt;), duration (15 / 30 / 60 min), and difficulty&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow the map&lt;/strong&gt; — a dark-themed Google Maps view shows mystery search zones, live GPS position, and walking routes to clue waypoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solve clues&lt;/strong&gt; — tap &lt;code&gt;?&lt;/code&gt; markers to read unlocked narrative riddles; voice narration is available hands-free via ElevenLabs or browser TTS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arrive &amp;amp; claim XP&lt;/strong&gt; — server-side Haversine geofence verification confirms physical arrival; XP is awarded and persisted atomically&lt;/li&gt;
&lt;/ol&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/itsdevcode" rel="noopener noreferrer"&gt;
        itsdevcode
      &lt;/a&gt; / &lt;a href="https://github.com/itsdevcode/waybound" rel="noopener noreferrer"&gt;
        waybound
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An open-source AI adventure companion that turns nearby places into personalized real-world quests, encouraging people to explore, walk, connect, and spend more time outdoors.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/e413d254c00f7c5a58997a5430c8b1d04c6428608e9199c53aff95b182659d19/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f776179626f756e642d62616e6e65722e737667"&gt;&lt;img src="https://camo.githubusercontent.com/e413d254c00f7c5a58997a5430c8b1d04c6428608e9199c53aff95b182659d19/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f776179626f756e642d62616e6e65722e737667" alt="WAYBOUND — Turn Real Places Into Real Adventures" width="100%"&gt;&lt;/a&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;WAYBOUND&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;&lt;em&gt;Turn Real Places Into Real Adventures.&lt;/em&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;An AI-powered real-world mystery adventure platform.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;WAYBOUND transforms ordinary neighborhood walks and nearby public landmarks into immersive, game-like mystery expeditions. Powered by open-weights Gemma AI, Google Places discovery, live GPS geolocation, and walking route navigation, WAYBOUND encourages explorers to get outside, solve environmental clues, discover hidden secrets in their cities, and team up with friends.&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://waybound-frontend.onrender.com/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5e560910fdea9f30a8c75356e671d99663982792d2469a8ce7569ed1c5da595a/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f6374612d706c61792e737667" alt="Play WAYBOUND"&gt;&lt;/a&gt;
  &amp;nbsp;&amp;nbsp;
  &lt;a href="https://github.com/itsdevcode/waybound" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3973d1cc27b1bab3a259ee91a978af0c0a0768c99660ffa7cf7e815e054064ba/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f6374612d736f757263652e737667" alt="View Source"&gt;&lt;/a&gt;
  &amp;nbsp;&amp;nbsp;
  &lt;a href="https://github.com/itsdevcode/waybound/issues/new?template=bug_report.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2fd93780442f17fad4255a4b6a13d8eb14881ee7fc816993fea7aa151205ae35/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f6374612d6275672e737667" alt="Report Bug"&gt;&lt;/a&gt;
  &amp;nbsp;&amp;nbsp;
  &lt;a href="https://github.com/itsdevcode/waybound/issues/new?template=feature_request.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/168929701a5dca15563637d14ca22375f5209913aa687d40db76ad161c956ac3/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f6374612d666561747572652e737667" alt="Request Feature"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/itsdevcode/waybound/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a5f7b22acf50d2a22abb770af3edb1bb0f2c911ffd0cc3be4e101323b6996636/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f73746172732e737667" alt="GitHub Stars"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://github.com/itsdevcode/waybound/network/members" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/43018e746d0debf0357c192e0ee4ab67a7cbef147ed0aac601f2cfe66daf62eb/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f666f726b732e737667" alt="GitHub Forks"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://github.com/itsdevcode/waybound/issues" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/396c9ba623441ca7c28d09ade1428b674537ab86708c9155347456e53679cfd4/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f6973737565732e737667" alt="Open Issues"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://github.com/itsdevcode/waybound/pulls" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9c1d466cf1a82ed4efccfd74c745f5047f75edefa8d47d94064ed4cf69473b90/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f7072732e737667" alt="PRs Welcome"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://github.com/itsdevcode/waybound/./LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7bf542853c4796a86d68be88e26e1c484d61a0a46d1452e831952d9590d54ac0/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f6c6963656e73652e737667" alt="MIT License"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://www.python.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/633e99e77474df2f8c53110e70a9592caef68ad0957b4fa582d4fca349f1d1e7/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f707974686f6e2e737667" alt="Python 3.12"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://fastapi.tiangolo.com/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/74b84c49cbf147e5b846689e13402ae038632894899d435d9d212427ba2725f7/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f666173746170692e737667" alt="FastAPI"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://nextjs.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e336c4cdff746d3f0e4cb173e21e3cf4bbc5a3c57f01a449b60836a5da5dd6e6/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f6e6578746a732e737667" alt="Next.js"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://www.typescriptlang.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3b817eb96aa08fc710fcb24c6e8a483b6d5ca7eee9ae5e0557a31290df603934/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f747970657363726970742e737667" alt="TypeScript"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://www.postgresql.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5d8782061af4be85eba9a08908adfd3318096e1d191a4e59ca3fed3a077434a8/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f706f737467726573716c2e737667" alt="PostgreSQL"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://developers.google.com/maps" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2ef7c8612fe7950bad063f21cb6087a22dc1b1977382171afa4bd664b0eb2ba3/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f676f6f676c656d6170732e737667" alt="Google Maps Platform"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://ai.google.dev/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5d3f0b82de56d20dcaf5e9bf0f69a83ad894880c1819e39ceb945299b73365c9/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f67656d6d612e737667" alt="Google Gemma 2"&gt;&lt;/a&gt;
  &amp;nbsp;
  &lt;a href="https://tailwindcss.com/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/cf9f3ed6fbe1d9651d6f26cd1ccbe1e125a7da2e4ddc67483ca10dd6fb601263/68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f697473646576636f64652f776179626f756e64406d61696e2f646f63732f6173736574732f6261646765732f7461696c77696e642e737667" alt="Tailwind CSS"&gt;&lt;/a&gt;
&lt;/p&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🌟 Project Overview&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;WAYBOUND&lt;/strong&gt; bridges the gap between outdoor fitness, real-world exploration, and tabletop role-playing games.&lt;/p&gt;

&lt;p&gt;Unlike conventional navigation applications (such as Google Maps or Apple Maps) that simply give you the shortest route from Point A to Point B, &lt;strong&gt;WAYBOUND deliberately keeps your final destination a secret&lt;/strong&gt;. Instead, it generates an unfolding mystery adventure around you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔍 &lt;strong&gt;Real Locations, Never Fake Content&lt;/strong&gt;: Every quest is grounded in authentic, publicly accessible parks, historical monuments, public art installations, architectural landmarks, or viewpoints queried via Google Places API…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/itsdevcode/waybound" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The full stack:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Next.js 16 · React 19 · TypeScript · Tailwind CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Python 3.12 · FastAPI · SQLAlchemy 2.0 (async) · PostgreSQL · Alembic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Location Discovery&lt;/td&gt;
&lt;td&gt;Google Places API (New) — real public places only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maps &amp;amp; Navigation&lt;/td&gt;
&lt;td&gt;Google Maps JS SDK · Google Routes API (walking polylines + ETA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Storytelling&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Google Gemma 2&lt;/strong&gt; (open-weights, via Google AI API)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice Narration&lt;/td&gt;
&lt;td&gt;ElevenLabs TTS · Browser Web Speech API fallback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;Passwordless email OTP (HMAC-SHA256, Resend / SMTP / Gmail)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Render Blueprint (IaC, auto-migration, managed PostgreSQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;Pytest (133 backend cases) · Vitest (74 frontend cases)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Core Architecture Problem: AI Lies About Places
&lt;/h3&gt;

&lt;p&gt;AI is brilliant at generating vivid, convincing place descriptions. It will happily invent an "ancient clock tower on Merchant Street" with the confidence of a local historian — even if that tower doesn't exist.&lt;/p&gt;

&lt;p&gt;In a couch game, that's harmless. In an app that asks people to physically walk somewhere, it's a fundamental failure of trust.&lt;/p&gt;

&lt;p&gt;The entire WAYBOUND architecture is designed around one constraint:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Google Places finds the real location. AI writes the story around it. Never the other way around.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the validated pipeline every quest passes through before it reaches a player:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User GPS Coordinates
        │
        ▼
Google Places API ──► Candidate Real Places
        │
        ▼
Gemma 2 AI Layer ──► Mission Title + Clues + Proposed Waypoints
        │
        ▼
Backend Validation ──► semantically_matches_clue()? is_publicly_accessible()?
        │
        ▼
Quest Stored in PostgreSQL (only validated, real Place IDs)
        │
        ▼
Frontend: Progressive Reveal ──► Player walks, unlocks clues, arrives
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  The AI Storytelling Service
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;GemmaQuestProvider&lt;/code&gt; receives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The validated destination (a real place with a verified Google Place ID)&lt;/li&gt;
&lt;li&gt;Up to two semantically matched intermediate waypoints&lt;/li&gt;
&lt;li&gt;The explorer's approximate area and chosen archetype&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From that, Gemma generates a mission narrative, escalating riddle clues, and an atmospheric story arc. The AI &lt;strong&gt;proposes&lt;/strong&gt; waypoints by description; the backend runs two validation checks before any waypoint is ever stored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/providers/gemma.py (simplified)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_publicly_accessible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PlaceCandidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Rejects permanently/temporarily closed locations and inaccessible types.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;business_status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLOSED_PERMANENTLY&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;CLOSED_TEMPORARILY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;primary_type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;INACCESSIBLE_TYPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;semantically_matches_clue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PlaceCandidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Validates that a waypoint candidate semantically matches the clue text.
    Uses significant name tokens + a curated landmark synonym dictionary
    (e.g. &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fountain&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; → {&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;basin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;water&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cascade&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;spout&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;}).
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;clue_lower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;name_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b[A-Za-z0-9]{3,}\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;STOP_WORDS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;name_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clue_lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;syn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clue_lower&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;syn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;LANDMARK_SYNONYMS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;())):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If validation fails, the waypoint slot stays empty and the adventure continues without it — no invented fallback, ever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Reward Engineering
&lt;/h3&gt;

&lt;p&gt;Arrival verification can't just be a client-side check — GPS coordinates are trivially spoofable. WAYBOUND runs Haversine distance calculation &lt;strong&gt;server-side&lt;/strong&gt; before awarding any XP. Quest completions are stored atomically with duplicate-prevention logic, so repeated requests for the same quest yield no additional reward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cooperative Party Mode
&lt;/h3&gt;

&lt;p&gt;In two-player fellowship mode, the system distributes &lt;strong&gt;complementary clue fragments&lt;/strong&gt; between partners. One explorer sees Fragment I (the &lt;em&gt;where&lt;/em&gt;); the other sees Fragment II (the &lt;em&gt;why&lt;/em&gt;). To progress, they have to communicate — turning walking with someone into solving something together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Places Integration
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;GooglePlacesService&lt;/code&gt; queries the &lt;a href="https://developers.google.com/maps/documentation/places/web-service/op-overview" rel="noopener noreferrer"&gt;Places API (New)&lt;/a&gt; &lt;code&gt;searchNearby&lt;/code&gt; endpoint with a curated set of public exploration types (parks, museums, monuments, historical landmarks, sculptures, botanical gardens) and a strict field mask that avoids expensive billing fields. Results are cached in-memory with a 5-minute TTL to minimize API cost during repeated session loads.&lt;/p&gt;




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

&lt;p&gt;WAYBOUND is built around &lt;strong&gt;Google Gemma 2&lt;/strong&gt;, an open-weight large language model. That choice was deliberate, not incidental — and it shapes what the project can and can't do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open weights let the architecture be honest about failure modes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A closed API that hides its model weights and training data makes it extremely difficult to audit &lt;em&gt;why&lt;/em&gt; it invents a place, how to constrain it, or how the model would behave across different geographies and languages. With Gemma's open-weight nature, the entire pipeline — from prompt construction to output validation — can be inspected, forked, and improved in public.&lt;/p&gt;

&lt;p&gt;More concretely, open innovation made the following possible:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Community-auditable anti-hallucination logic.&lt;/strong&gt; The &lt;code&gt;semantically_matches_clue()&lt;/code&gt; validator and the &lt;code&gt;LANDMARK_SYNONYMS&lt;/code&gt; dictionary are open for anyone to extend. A contributor in Tokyo can add Japanese landmark synonym mappings. One in São Paulo can tune the validator for Portuguese place naming conventions. A closed model would make this sort of collaborative curation nearly impossible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deployable without vendor lock-in.&lt;/strong&gt; Because the model weights are open, a self-hosted deployment (e.g., via Ollama or a local inference server) is entirely feasible. No single API provider can unilaterally break WAYBOUND's core functionality by changing pricing or access terms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trustworthy for outdoor navigation.&lt;/strong&gt; People following WAYBOUND into unfamiliar areas need to trust the system. Open-source code means the full validation pipeline is auditable: the community can verify that no place is ever fabricated and that arrival verification happens server-side. That transparency is hard to achieve with a black-box platform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extensible by the community.&lt;/strong&gt; The provider architecture (&lt;code&gt;QuestProvider&lt;/code&gt; base class → &lt;code&gt;GemmaQuestProvider&lt;/code&gt; implementation) is designed so contributors can swap in alternative open-weight models without touching the quest generation orchestration layer. WAYBOUND can evolve alongside the open-source AI ecosystem.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Open innovation didn't just make WAYBOUND &lt;em&gt;possible&lt;/em&gt; — it made it &lt;em&gt;trustworthy&lt;/em&gt; in the specific way that a real-world application demands.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hacktoberfest Open-Source AI Challenge — Week 1: Touch Grass&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Google AI / Gemma track (open-weights AI: Google Gemma 2)&lt;/li&gt;
&lt;li&gt;Google Maps Platform track (Google Places API, Google Routes API, Google Maps JS SDK)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Your city. Your story. Your adventure.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have a city in mind? Drop it in the comments — I'd love to hear what mystery you'd want to solve there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>PlanB: An AI That Challenges Your Plan Before Reality Does</title>
      <dc:creator>Arun Yadav</dc:creator>
      <pubDate>Sun, 04 Oct 2026 17:20:52 +0000</pubDate>
      <link>https://dev.to/itsdevcode/planb-an-ai-that-challenges-your-plan-before-reality-does-4gc2</link>
      <guid>https://dev.to/itsdevcode/planb-an-ai-that-challenges-your-plan-before-reality-does-4gc2</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 have a friend who is good at making plans.&lt;/p&gt;

&lt;p&gt;But there is one problem: his plans usually depend on everything going right.&lt;/p&gt;

&lt;p&gt;The train arrives on time.&lt;br&gt;&lt;br&gt;
The internet works.&lt;br&gt;&lt;br&gt;
There is enough time.&lt;br&gt;&lt;br&gt;
Nothing unexpected happens.&lt;/p&gt;

&lt;p&gt;And that made me think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if AI didn't make Plan A for you, but instead helped you prepare for what could go wrong?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I built &lt;strong&gt;PlanB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;PlanB is an AI-powered backup plan assistant built with &lt;strong&gt;Gemma&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You give PlanB the plan you already have. Instead of replacing it, PlanB looks at what your plan depends on and finds realistic things that could go wrong.&lt;/p&gt;

&lt;p&gt;For every risk, it gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Impact&lt;/li&gt;
&lt;li&gt;Warning signs&lt;/li&gt;
&lt;li&gt;Prevention&lt;/li&gt;
&lt;li&gt;A backup action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also creates a &lt;strong&gt;Prepare Now&lt;/strong&gt; checklist, so you can take action before something goes wrong.&lt;/p&gt;

&lt;p&gt;But I wanted PlanB to handle something else too:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when the problem has already happened?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;Plan C&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;Tell PlanB what actually went wrong, and it uses your original plan, its earlier analysis, and the new situation to help you decide what to do next.&lt;/p&gt;

&lt;p&gt;The complete flow is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plan A → Find what could go wrong → Plan B → Something actually goes wrong → Plan C&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%2Fbm1qtzmxwo7rv1imyfzd.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%2Fbm1qtzmxwo7rv1imyfzd.png" alt="PlanB home screen" width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;


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

&lt;p&gt;Let's take a real example.&lt;/p&gt;
&lt;h3&gt;
  
  
  Plan A
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Tomorrow I have a job interview in Delhi at 11 AM. My train leaves at 6 AM and I expect to reach Delhi by 9 AM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PlanB first understands the goal.&lt;/p&gt;

&lt;p&gt;Then it identifies things this plan depends on, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the train arriving with enough time before the interview&lt;/li&gt;
&lt;li&gt;reaching the station on time&lt;/li&gt;
&lt;li&gt;local transport being available in Delhi&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It then generates realistic risks, warning signs, prevention steps, and backup actions.&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%2Fx8uknukx2uuqykk2k3i1.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%2Fx8uknukx2uuqykk2k3i1.png" alt="PlanB AI-generated risks and backup plans" width="800" height="637"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  But then reality changes...
&lt;/h3&gt;

&lt;p&gt;Suppose I tell PlanB:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My train is running two hours late.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PlanB doesn't start from zero.&lt;/p&gt;

&lt;p&gt;It already knows my original goal and the risks around it.&lt;/p&gt;

&lt;p&gt;It uses that context to create &lt;strong&gt;Plan C&lt;/strong&gt;, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Immediate actions&lt;/li&gt;
&lt;li&gt;A revised plan&lt;/li&gt;
&lt;li&gt;Additional risks to watch for&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%2Fov605m4kr768c0s8i4kk.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%2Fov605m4kr768c0s8i4kk.png" alt="PlanB Plan C recovery mode" width="800" height="652"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  🌐 Live Demo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://planb-five-pink.vercel.app" rel="noopener noreferrer"&gt;https://planb-five-pink.vercel.app&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  🎥 Video Demo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://youtu.be/xklFTVIHJAw" rel="noopener noreferrer"&gt;https://youtu.be/xklFTVIHJAw&lt;/a&gt;&lt;/p&gt;


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

&lt;p&gt;PlanB is completely open source.&lt;/p&gt;
&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/itsdevcode/planb" rel="noopener noreferrer"&gt;https://github.com/itsdevcode/planb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I deliberately kept the architecture small and focused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js Frontend
       ↓
FastAPI API
       ↓
PlannerService
       ↓
Gemma
       ↓
Pydantic Validation
       ↓
Structured Plan B / Plan C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no authentication, database, background queue, or unnecessary infrastructure in this MVP.&lt;/p&gt;

&lt;p&gt;For a weekend challenge, I wanted every part of the application to support the main idea.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  Gemma is the core of PlanB
&lt;/h3&gt;

&lt;p&gt;Gemma isn't an extra chatbot added to the application.&lt;/p&gt;

&lt;p&gt;It powers the main reasoning behind both &lt;strong&gt;Plan B&lt;/strong&gt; and &lt;strong&gt;Plan C&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For Plan B, the backend asks Gemma to analyze the user's existing plan and return structured information.&lt;/p&gt;

&lt;p&gt;The response looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assumptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"risks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"risk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"impact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"warning_signs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prevention"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"backup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prepare_now"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One important decision I made while designing the prompt was to focus on &lt;strong&gt;realistic problems instead of extreme ones&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, I don't want PlanB to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your train could crash.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I want it to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your train could be delayed enough that you miss the interview.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PlanB should help people prepare, not make them afraid.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building Plan C
&lt;/h3&gt;

&lt;p&gt;Plan C gives Gemma three important pieces of context:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The original Plan A&lt;/li&gt;
&lt;li&gt;The Plan B analysis&lt;/li&gt;
&lt;li&gt;What actually went wrong&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Gemma then generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Immediate actions&lt;/li&gt;
&lt;li&gt;A revised plan&lt;/li&gt;
&lt;li&gt;Additional risks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means Plan C isn't just a new unrelated AI response. It continues from the context PlanB already understands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured AI Output with Pydantic
&lt;/h3&gt;

&lt;p&gt;I didn't want the frontend to depend on unpredictable AI text.&lt;/p&gt;

&lt;p&gt;The FastAPI backend validates Gemma's output using Pydantic models.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Risk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;impact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&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;medium&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;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;warning_signs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;prevention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;backup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the model response doesn't match the expected structure, the application doesn't blindly treat it as valid data.&lt;/p&gt;

&lt;p&gt;This gives the AI layer a clear contract with the rest of the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;p&gt;The backend is built with &lt;strong&gt;FastAPI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are two main endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/v1/plans/analyze
POST /api/v1/plans/recover
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/analyze&lt;/code&gt; takes Plan A and creates the Plan B analysis.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/recover&lt;/code&gt; takes the original context plus what went wrong and creates Plan C.&lt;/p&gt;

&lt;p&gt;The backend is deployed on &lt;strong&gt;Render&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;The frontend is built with &lt;strong&gt;Next.js&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I kept the user flow intentionally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Describe Plan A
      ↓
Analyze Plan
      ↓
Understand the Goal
      ↓
Find Assumptions
      ↓
Find Possible Problems
      ↓
Prepare Now
      ↓
Something Went Wrong
      ↓
Build Plan C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend is deployed on Vercel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;I also added automated tests for the main API behavior.&lt;/p&gt;

&lt;p&gt;Gemma calls are mocked in the test suite, so tests can verify the application contract without making real AI requests.&lt;/p&gt;

&lt;p&gt;The MVP tests cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Health endpoint&lt;/li&gt;
&lt;li&gt;Successful Plan B analysis&lt;/li&gt;
&lt;li&gt;Invalid Plan A validation&lt;/li&gt;
&lt;li&gt;Successful Plan C recovery&lt;/li&gt;
&lt;li&gt;Invalid recovery input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Current result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  One Bug Taught Me an Important Lesson
&lt;/h3&gt;

&lt;p&gt;During development, Gemma sometimes returned an incomplete response.&lt;/p&gt;

&lt;p&gt;At first, it looked like the AI or JSON validation was broken.&lt;/p&gt;

&lt;p&gt;The real problem was much simpler.&lt;/p&gt;

&lt;p&gt;I had configured an output token limit that was too small for the structured response I was asking Gemma to generate.&lt;/p&gt;

&lt;p&gt;Removing that unnecessary restriction fixed the problem.&lt;/p&gt;

&lt;p&gt;It reminded me that building an AI application isn't just about writing a prompt.&lt;/p&gt;

&lt;p&gt;The model, generation settings, validation, backend, and frontend all have to work together.&lt;/p&gt;

&lt;p&gt;I also learned that AI responses can take longer than normal API requests.&lt;/p&gt;

&lt;p&gt;Instead of pretending the response is instant, PlanB shows a clear loading state while the plan is being analyzed.&lt;/p&gt;




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

&lt;p&gt;PlanB uses &lt;strong&gt;Gemma, an open-weight model&lt;/strong&gt;, as its reasoning engine.&lt;/p&gt;

&lt;p&gt;That matters to me because the intelligence behind the product doesn't have to be permanently tied to one closed model.&lt;/p&gt;

&lt;p&gt;The application has a simple boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plan
  ↓
AI Reasoning
  ↓
Structured Schema
  ↓
Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Today, I use Gemma through an API because it allowed me to build and ship the idea quickly during the weekend challenge.&lt;/p&gt;

&lt;p&gt;But because Gemma is open-weight, there is a path to running the model through different infrastructure or even locally with suitable hardware in the future.&lt;/p&gt;

&lt;p&gt;That is especially interesting for PlanB.&lt;/p&gt;

&lt;p&gt;People might use it for plans involving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Job interviews&lt;/li&gt;
&lt;li&gt;Travel&lt;/li&gt;
&lt;li&gt;Work&lt;/li&gt;
&lt;li&gt;Personal events&lt;/li&gt;
&lt;li&gt;Projects&lt;/li&gt;
&lt;li&gt;Family plans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of those plans can contain private information.&lt;/p&gt;

&lt;p&gt;Having an open-weight model creates more options for where that reasoning can happen.&lt;/p&gt;

&lt;p&gt;For me, that's one of the most exciting parts of open innovation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;It gives developers more control over how intelligence becomes part of their applications.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;h3&gt;
  
  
  Best Use of Gemma
&lt;/h3&gt;

&lt;p&gt;Gemma is the core reasoning engine behind PlanB.&lt;/p&gt;

&lt;p&gt;It analyzes Plan A, identifies what the plan depends on, finds realistic failure scenarios, creates preparation steps, and generates Plan C when something actually goes wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Render
&lt;/h3&gt;

&lt;p&gt;PlanB's FastAPI backend is deployed on Render.&lt;/p&gt;

&lt;p&gt;The Render service handles the API layer connecting the Next.js frontend with Gemma and serves both the Plan B analysis and Plan C recovery workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;I started this project with a simple observation about a friend.&lt;/p&gt;

&lt;p&gt;He didn't need another tool to make plans.&lt;/p&gt;

&lt;p&gt;He needed something that could challenge the plan he already had.&lt;/p&gt;

&lt;p&gt;That became &lt;strong&gt;PlanB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And after building it, I realized the idea applies to much more than one person.&lt;/p&gt;

&lt;p&gt;PlanB doesn't try to predict the future.&lt;/p&gt;

&lt;p&gt;It simply helps you stay ready when things don't go as planned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because when Plan A fails, you should always have a Plan B.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Mastering Routing in FastAPI: From Flat Files to Clean Architecture</title>
      <dc:creator>Arun Yadav</dc:creator>
      <pubDate>Wed, 09 Sep 2026 17:37:32 +0000</pubDate>
      <link>https://dev.to/itsdevcode/mastering-routing-in-fastapi-from-flat-files-to-clean-architecture-427g</link>
      <guid>https://dev.to/itsdevcode/mastering-routing-in-fastapi-from-flat-files-to-clean-architecture-427g</guid>
      <description>&lt;p&gt;When building an API with FastAPI, it’s deceptively easy to dump every endpoint into &lt;code&gt;main.py&lt;/code&gt;. A few &lt;code&gt;@app.get()&lt;/code&gt; decorators here, a couple of &lt;code&gt;@app.post()&lt;/code&gt; decorators there, and everything works.&lt;/p&gt;

&lt;p&gt;Then your project grows. Suddenly, &lt;code&gt;main.py&lt;/code&gt; is an 800-line monolith, team members are constantly hitting Git merge conflicts, and nobody knows where the user authentication logic ends and the billing logic begins.&lt;/p&gt;

&lt;p&gt;FastAPI solves this with &lt;code&gt;APIRouter&lt;/code&gt;. Here is how to structure your routing cleanly from day one.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Core Tool: &lt;code&gt;APIRouter&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Think of &lt;code&gt;APIRouter&lt;/code&gt; as a mini FastAPI app that doesn’t run on its own. It groups related path operations together, which you then mount onto your main application.&lt;/p&gt;

&lt;p&gt;A typical modular directory structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my_project/
├── app/
│   ├── routers/
│   │   ├── users.py
│   │   └── items.py
│   ├── dependencies.py
│   └── main.py
└── requirements.txt

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your module (&lt;code&gt;app/routers/users.py&lt;/code&gt;), define your router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_users&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&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;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/{user_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&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;alice&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;Then, register it in &lt;code&gt;app/main.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.routers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&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;Users&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;Two key parameters to notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;prefix&lt;/code&gt;&lt;/strong&gt;: Prepend &lt;code&gt;/users&lt;/code&gt; to every endpoint in the router. Notice how &lt;code&gt;users.py&lt;/code&gt; uses &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;/{user_id}&lt;/code&gt; instead of repeating &lt;code&gt;/users&lt;/code&gt; everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/strong&gt;: Groups these endpoints together in FastAPI's auto-generated Swagger UI (&lt;code&gt;/docs&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Cleaner Endpoints with Shared Dependencies
&lt;/h2&gt;

&lt;p&gt;One of the biggest advantages of &lt;code&gt;APIRouter&lt;/code&gt; is attaching security checks or common dependencies to an entire group of routes at once.&lt;/p&gt;

&lt;p&gt;Instead of writing &lt;code&gt;Depends(verify_token)&lt;/code&gt; on 15 separate endpoints, apply it to the router level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/routers/admin.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.dependencies&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;require_admin_role&lt;/span&gt;

&lt;span class="c1"&gt;# Every route attached to this router will now require admin access
&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/admin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tags&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;Admin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;require_admin_role&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@router.delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/purge-cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;purge_cache&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;cache cleared&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;h2&gt;
  
  
  3. Nesting Routers for Sub-Resources
&lt;/h2&gt;

&lt;p&gt;Real-world APIs often have hierarchical relationships—such as comments belonging to a specific post (&lt;code&gt;/posts/{post_id}/comments&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Rather than hardcoding long prefix strings, routers can be nested inside other routers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/routers/comments.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;

&lt;span class="n"&gt;comments_router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@comments_router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_comments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;comments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/routers/posts.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.routers.comments&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;comments_router&lt;/span&gt;

&lt;span class="n"&gt;posts_router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/posts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&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;Posts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Mount comments directly under a specific post
&lt;/span&gt;&lt;span class="n"&gt;posts_router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;comments_router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/{post_id}/comments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tags&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;Comments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, mounting &lt;code&gt;posts_router&lt;/code&gt; inside your &lt;code&gt;main.py&lt;/code&gt; automatically exposes both &lt;code&gt;/posts&lt;/code&gt; and &lt;code&gt;/posts/{post_id}/comments&lt;/code&gt; cleanly.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. API Versioning Without Headaches
&lt;/h2&gt;

&lt;p&gt;Versioning is simple when your routing is modular. You can bundle whole sets of routers under a version prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/main.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.routers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Create a v1 router
&lt;/span&gt;&lt;span class="n"&gt;api_v1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;api_v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&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;Users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;api_v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&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;Items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Include the full v1 bundle into the main app
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_v1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it's time for &lt;code&gt;v2&lt;/code&gt;, you can spin up an &lt;code&gt;api_v2&lt;/code&gt; router alongside it without touching older client integrations.&lt;/p&gt;




</description>
      <category>api</category>
      <category>architecture</category>
      <category>backend</category>
      <category>python</category>
    </item>
  </channel>
</rss>
