DEV Community

Cover image for ClaimTrail: a 15-minute walk has to include coming back
Adarsh Singh
Adarsh Singh

Posted on

ClaimTrail: a 15-minute walk has to include coming back

Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass Submission 🌿

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass.

What I Built

Would anyone open an application just to go outside?

That is the question behind ClaimTrail. I wanted to explore whether AI could give a short walk a purpose without making the walk another screen-based activity. Finding a park is already easy. Having one small thing to notice there is a different starting point.

ClaimTrail turns an available place description or review excerpt into an observation question. You enter your actual starting landmark or address, choose parks, gardens or public squares, and select a complete-trip budget. After choosing a feasible candidate, you read the source and a question, put the phone away, and return to record what you noticed.

ClaimTrail search form showing the starting landmark and 15-minute trip budget

MOCKED/SYNTHETIC UI DEMONSTRATION: current application interface; invented starting landmark and mocked service status. No location request was sent.

The intended user is someone with a little spare time: a student between tasks, or anyone curious about familiar surroundings. There is no account, route tracking, photo requirement or leaderboard. The three outcomes are “Saw it,” “Didn't see it” and “Couldn't tell.” A personal observation stays separate from what an online source says.

I have not established that this makes people go outside more often. It is a product hypothesis, not a measured result. The experiment is to make the screen useful briefly, then get it out of the way.

Demo

Watch the captioned synthetic software demonstration on YouTube

Evidence status: the captioned software video and interface captures use mocked provider and model responses. They demonstrate current UI behavior, not a successful current SerpApi-to-Gemma run. An earlier live demonstration exists locally, but it predates the round-trip changes. A fresh real-provider rehearsal remains unverified. No physical outdoor visit is claimed.

Synthetic park recommendation with estimated round-trip walking time

MOCKED/SYNTHETIC UI DEMONSTRATION: the returned candidate is fictional. The recommendation shows travel and buffer; the mission view below also exposes the five-minute activity allocation.

ClaimTrail displaying a sourced excerpt and observation question

MOCKED/SYNTHETIC UI DEMONSTRATION: neither the excerpt nor the selected question came from a live provider/model call during this capture. The question uses the application's actual approved wording.

Synthetic reflection receipt saved in browser storage

MOCKED/SYNTHETIC UI DEMONSTRATION: the reflection is simulated, not field evidence. Saving, refreshing and deleting use the real frontend's local-storage behavior.

A video is preferable to exposing the backend publicly. This version is a local, loopback-only application, not a hardened internet-facing API service.

Code

ClaimTrail source and setup instructions on GitHub

ClaimTrail

Less scrolling. More discovering.

A real listing detail becomes a small outdoor observation question, followed by a private reflection. The world is the experience; the app is preparation and reflection.

New project: October 11, 2026. Hacktoberfest Week 1 deadline: October 12, 12:29 PM IST.

Implemented

Real SerpApi Maps search/detail and walking-directions filtering, bounded review fallback, local Gemma structured output, deterministic grounding/card validation React search-to-reflection flow, browser-local persistence, copy and deletion. A candidate must fit an estimated round-trip budget from the entered starting landmark or address: outbound walk + on-site task + return walk + safety buffer. The provider's resolved origin is not independently verified; do not enter only a city. Walking pace and conditions can differ. Reminders run while the app is active, not as background alarms. See BUILD-LOG for actual verification. No outdoor visit, public deployment or submission is claimed.

Run locally — Windows PowerShell

Prerequisites: Python…

The application code is MIT licensed. Gemma and upstream place data have their own terms; the repository's license does not relicense either. The README explains the local services, credential setup and limitations.

How I Built It

Keep the model's job small

React and TypeScript provide the field-journal interface. FastAPI handles search, evidence retrieval and mission preparation. SerpApi supplies Google Maps place results, walking estimates and available source text. Ollama runs gemma3:1b locally.

ClaimTrail architecture connecting React, FastAPI, SerpApi and local Gemma

Implementation diagram, not a deployment or live-test result. Location queries leave the machine through SerpApi; reflection notes do not enter the backend pipeline.

The backend filters place categories and travel feasibility before offering candidates. When a user selects one, it checks the selection's origin and budget, retrieves matching place details, and looks for an eligible description. If necessary, one reviews request examines a small bounded set of excerpts. Missing usable evidence means no mission, not an invented fallback.

Gemma selects a supported feature and source quote, then returns structured JSON with an approved observation card. The current choices are deliberately narrow: paths, seating and signs. This is constrained selection, not an open-ended adventure generator.

JSON matching a schema is not enough. The backend also checks provenance and meaning. This excerpt from backend/app/mission.py, lines 93–104, runs after schema parsing:

    source = next((e for e in evidence if e.source_id == proposal.source_id), None)
    normalize = lambda text: ' '.join(text.split())
    quote = normalize(proposal.exact_quote)
    if source is None or len(quote) < 8 or quote not in normalize(source.excerpt):
        raise MissionError('The model quote could not be traced to this place. No mission was created.')
    if proposal.feature not in eligible_features(source.excerpt) or proposal.feature not in eligible_features(quote):
        raise MissionError('The source does not support an allowed observation. Try another place.')
    if proposal.estimated_on_site_minutes != minutes:
        raise MissionError('The model changed the requested activity time. No mission was created.')
    for field, value in CARDS[proposal.feature].items():
        if getattr(proposal, field) != value:
            raise MissionError('The model mixed unsupported observation details. No mission was created.')
Enter fullscreen mode Exit fullscreen mode

The quote must occur in the selected evidence after whitespace normalization. The feature must be supported, the activity time must match, and the card fields must agree. These checks establish a traceable question, not proof that a listing is true today.

Earlier build logs record both an abstention and a mixed-card response being rejected before an accepted local-model result. Those are historical development results, not proof that the revised routing flow has passed a fresh live run.

A fifteen-minute mission has to include coming back

The most important correction was not in the prompt. A destination ten minutes away is not suitable for a fifteen-minute break if the user must also explore and return.

The initial experience treated activity time separately from travel. The current implementation requests outbound and return walking directions independently. It does not simply double the outward estimate or turn straight-line distance into a confident walking time.

It rounds the two estimates upward, reserves activity time, and adds a buffer of at least three minutes. For a fifteen-minute budget, the activity allowance is five minutes. These lines from backend/app/places.py, 97–107, make feasibility a deterministic decision:

    activity_minutes = ACTIVITY_MINUTES[total_minutes]
    buffer_minutes = max(SAFETY_BUFFER_MINUTES, ceil(max(outbound_minutes, return_minutes) * 0.25))
    estimated_total = outbound_minutes + activity_minutes + return_minutes + buffer_minutes
    return {
        'budget_minutes': total_minutes,
        'outbound_minutes': outbound_minutes,
        'return_minutes': return_minutes,
        'activity_minutes': activity_minutes,
        'buffer_minutes': buffer_minutes,
        'estimated_total_minutes': estimated_total,
        'fits_budget': estimated_total <= total_minutes,
Enter fullscreen mode Exit fullscreen mode

Excerpt ends within the returned dictionary; additional metadata and the closing brace are omitted.

Time-budget comparison showing accepted and rejected round-trip missions

Illustrative synthetic calculations, not measured routes: 3 + 5 + 3 + 3 = 14 fits; 10 + 5 + 10 + 3 = 28 does not.

An empty result is better than quietly spending time the user does not have. Even an accepted estimate depends on the entered origin, provider routing, walking pace and conditions. The app cannot certify entrances, access or safety.

Tests needed to follow the browser

The release audit found an embarrassing boundary mismatch: normal searches returned HTTP 422 while 50 backend tests passed. The browser sent total_minutes=15 as query text, but the endpoint used numeric literals. Tests calling functions or omitting the parameter missed that path.

The correction accepts the four textual query values and converts them after validation. HTTP-level regressions now exercise the actual values the browser sends.

Desktop controls also overlapped. Changing an origin or budget could leave old recommendations visible, and late responses could restore stale state. Separate grid rows and revision-bound requests corrected those cases. The return reminder now reserves the return walk plus its buffer; it updates while the app is active, not as a background alarm.

The latest release record reports 72 passing backend tests, 14 passing mocked Chromium tests and a passing production build. Browser coverage includes three widths, stale responses, reminders and reflection persistence/deletion. These are useful checks, not live-provider or outdoor validation.

Why Does Open Innovation Matter?

Local open-weight inference makes the model boundary inspectable. I can change the schema, examine rejected outputs and experiment with the configured model without depending on a proprietary hosted language-model endpoint. Alternative models would still need their own evaluation; changing a name is not evidence of compatibility.

The model is central to mission preparation: an unavailable or rejected response does not silently become a template-only success. But with only three approved cards, a simpler non-AI selector is a fair baseline to compare later. I have not measured whether Gemma improves on it.

Local inference does not make the whole application offline, completely private or free. Starting-point queries reach SerpApi. Discovery needs internet and provider credits; inference needs a running local model. Personal reflections remain in browser storage and can be deleted, but anyone using that browser profile may read them.

There are further limits: provider latency, sparse place coverage, repetitive questions and no verified user adoption. Even request cost needs care: five displayed results can require examining more than five candidates.

My main lesson is that the model should not own promises the application can check. Let it select a question. Make ordinary code enforce the source, time and state boundaries. Whether that question gives someone a worthwhile reason to step outside is the next thing to test.

This is a solo project. AI coding assistance contributed to implementation and tests, and an AI assistant drafted this article. The technical account is based on source inspection and recorded verification; it does not represent independent manual authorship of every line.

Prize Categories

  • Best Use of Gemma: local gemma3:1b selects the source-linked observation card; deterministic backend validation controls acceptance. Historical inference evidence exists, with the current complete live flow still pending verification.
  • Best Use of SerpApi: place discovery, independently requested walking legs and retrieved evidence determine which destinations and questions are possible. It is a functional data dependency, not a decorative search call.

Thanks to the maintainers of Gemma, Ollama, SerpApi, React and FastAPI. The distinction I want ClaimTrail to preserve is simple: a source claim, an AI-selected question and a person's observation are three different things.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.