This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Stela is a phone-friendly web app that tells you what the ground under your feet was, long ago. Open it, tap Start, then look up from the screen and look around. A calm "stone scribe" talks you through the place: the rock and how old it is, what the spot probably was (sea floor, swamp, river plain), which fossils have been found nearby, which rare animals have been recorded around you, and the early history of the area. Tap Listen and the story is read aloud, so you spend your time looking at the ground, the cliffs and the streets around you instead of reading.: the rock and how old it is, what the spot probably was (sea floor, swamp, river plain), which fossils have been found nearby, which rare animals have been recorded around you, and the early history of the area. It can read the story aloud, so the screen is the shortest part of the experience.
It's for walkers, hikers, families and school groups: anyone standing on a cliff, a road cutting or a city street who wonders what was here before them.
The one rule behind the whole project: code collects the facts from open datasets, and the open-weight model may only narrate those facts. The model never looks anything up.
Demo
Live app: https://stela-0oz5.onrender.com/ (it runs on Render's free tier, so if nobody has used it for a while, the first load can take up to a minute to wake up.)
You don't need to stand on a fossil coast to try it. The start screen has a Try these places row (Dover, the Jurassic Coast, Cincinnati, Hell Creek, La Brea, New Orleans) that opens the map at each place and runs the normal lookup.
Code
RedwanNiloy
/
Stela
Every place is a stone with a story carved in it.
Stela
Every place is a stone with a story carved in it.
Open Stela on your phone, tap Start (or drop a pin on the map), and put the phone away. A calm "stone scribe" tells you about the ground under your feet: the rock and its age, what the place probably was (sea, land, swamp), the fossils found nearby, rare animal records nearby, and human history close by. It can read the story aloud.
Built for the Hacktoberfest Open-Source AI Challenge, Week 1 "Touch Grass".
The one rule
Code collects facts from open datasets. The open-weight LLM may only narrate those facts. The model never looks anything up. If a source is empty, the story says "nothing recorded nearby". If a source is down, the story says it could not be checked. If the model itself is down or too slow, you get the same facts as a plain…
Built between October 8 and October 11, 2026 for this challenge.
What It Does
- Ground: the rock unit, its age range, what the place probably was (with a small original scene drawing), and a "dig down through time" timeline from today, through human history, to when the rock formed.
- Fossils: the groups of fossils recorded nearby, with credited illustrations of each animal group.
- Rare animal records: endangered and vulnerable species recorded nearby, each with a credited species photo and a local name when a source provides one.
- People and past civilizations: short, attributed Wikipedia excerpts about the early history of your neighbourhood, city, district, region and country, with folklore kept separate and labelled "tradition, not verified history."
- Passport and Walk mode: a passport that lives only in your browser, and an optional Walk mode that asks for a new story only after you have moved about 300 m.
- Optional sounds, off by default, synthesized in the browser.
Collecting Eras: Badges for Standing on Old Ground
Every lookup can earn an era badge based on how old the rock under you is, and the number of different eras you collect sets your rank, from Noob Pre-Historic Traveler up to Deep Time Master. The badge comes straight from the rock's mapped age range, with no AI involved, and the boundaries come from the International Commission on Stratigraphy's time scale.
| Badge | The rock's oldest age is... |
|---|---|
| Fresh Ground Walker | up to about 12 thousand years |
| Ice Age Wanderer | up to 2.58 million years |
| Mammal Age Explorer | up to 66 million years |
| Dino Era Traveler | up to about 252 million years |
| Ancient Seas Voyager | up to about 539 million years |
| Deep Time Pioneer | over about 539 million years |
For example, the chalk at Dover (about 100 million years old) would give Dino Era Traveler, Cincinnati's rock (about 450 million years) would give Ancient Seas Voyager, and New Orleans, built on very young river deposits, would give Fresh Ground Walker.
You can't earn them from the couch. Only your device's location counts (Start, "Use my location", or Walk mode). A pin dropped on the map, or an example place, earns only a separate "Armchair Explorer" badge that never counts toward your rank. So the game rewards going somewhere older. Badges are stored only in your browser and are never sent to a server.
How I Built It
-
Open-weight model: Google's Gemma 4 26B A4B (
google/gemma-4-26b-a4b-it:free), served through OpenRouter's OpenAI-compatible API. The base URL, key and model name are three environment variables, so swapping the model is a config change, not a code change. On a laptop, the code can use a local model through Ollama instead. - Open data, fetched in parallel: Macrostrat for the rock unit and age, the Paleobiology Database for fossil finds, GBIF with IUCN categories for rare animal records, Wikipedia for history, and OpenStreetMap Nominatim for the place hierarchy. Pictures come from Wikimedia Commons and PhyloPic, shown only when the author and licence are known, with credit.
- Backend: FastAPI, with in-memory caching and a per-IP rate limit. Logs never contain coordinates or IP addresses. Frontend: one mobile-first page of vanilla JS with Leaflet and no build step.
- Scope ladders: every layer says how far from you its facts really come from (exact area, wider area, nearest city, region, or nothing), and the narrator must repeat that. If a source is empty, the story says "nothing recorded nearby." If the model fails or is slow, the same facts appear as a plain summary instead of an error.
- Guardrails around the model: the prompt forbids inventing species, dates, peoples or rulers, and code checks discard any story that names something not in the supplied facts, calls the animals alive or living here, or uses "here" for a wider place, showing the plain facts instead.
- Deployed on Render (free tier) from GitHub, with the key kept in Render's environment settings.
- I built it with Claude Code, working in small steps and testing against saved real API responses.
Why Does Open Innovation Matter?
Every fact can be checked. Because the facts come from open datasets with open licenses, each card shows its source and credit, and anyone can verify a claim or reuse the data. With a closed model answering from its own memory, you couldn't tell a real fossil record from a confident guess.
I could swap and constrain the model. Small models slip. In testing, a 4B model sometimes turned scientific names into common names and once said rare animals "wander" the area, and a model once blended a Wisconsin fact and a United States fact into one sentence. Because the model and its prompt are mine to configure, I could tighten the rules and add code checks, and I'd switch models if one behaved worse.
It costs nothing to run. The narrator is an open-weight Gemma model on a free endpoint, the data comes from free public datasets, and Stela runs on Render's free tier. The tradeoff is that free endpoints have rate limits and can be slow or unavailable, so Stela falls back to a plain facts summary instead of an error.
Privacy is built in. Stela doesn't store or log your location. The passport and badges live only in your browser. In online mode, your coordinates are sent to the data services and the facts to the model provider, and the README says so plainly.
Where open goes next: offline hiking. Today, Stela needs an internet connection, because every story comes from live public data services, and the narrator is a hosted model. But the openness is what makes an offline version possible. On a laptop, the code can already use a local open-weight model through Ollama instead of the hosted one. The missing piece is the data: the plan is to download and cache the rock, fossil and history data for a region I'm about to hike, so the whole thing can run with no signal and nothing leaving the device. I haven't built that cache yet, so for now the local model only replaces the model, not the data lookups. A closed API couldn't offer this path at all.
The honest tradeoffs today: the hosted model needs a server and an API key, and Stela depends on live public data services, so it needs an internet connection.
The Hardest Bug: One Shared Clock, Many Visitors
The hardest problem in Stela had nothing to do with AI. It was a rate-limited external API shared by many visitors at once.
OpenStreetMap Nominatim, the free service that turns coordinates into place names, allows about one request per second [CONFIRM against the usage policy wording]. Several parts of Stela need it: the place lookup, the fossil fallback, and the people and history card. And many visitors can hit the server at the same time.
The first design. One shared asyncio.Lock and a "last request" timestamp in app/geo.py keep the whole app under the limit. Every caller waits its turn, about 1.1 seconds apart, like a queue. With one user, it worked.
The failure mode I found. The queue and the request timeout (25 s [CONFIRM]) interact. By my arithmetic, once more than about 22 lookups are waiting, the ones at the back time out and their cards say "could not be looked up." A retry joins the back of the queue again, and the follow-up story call could repeat the whole lookup, which makes it worse. None of this showed up in single-user testing. I found it by firing 16 concurrent requests for 16 different spots at the server. [CONFIRM what happened, for example how many failed.]
The fix has three parts:
-
Single-flight on the server. Requests for the same spot share one in-flight task (
asyncio.ensure_futureplusshield), so ten people asking about the same place cost one Nominatim call, and one caller giving up doesn't cancel it for the others. - A short 60-second cache lets the story call reuse a partial result instead of repeating the lookup. It expires quickly on purpose, because caching an incomplete result means caching a failure.
- Quiet retries in the page [CONFIRM: two retries and the delay], so a failed card recovers without the user pressing anything.
What this does not prove
- It is not a like-for-like speedup. The "before" run used 16 different spots, and the "after" run used a mix of repeated and different spots. The after run shows single-flight working for repeated spots. It doesn't show the queue got faster.
- The queue itself is unchanged. Different spots still wait about 1.1 seconds each, so the slowest request was still around 20 seconds for 10 distinct spots.
- My "success" count is soft. Eight of the 15 successful responses in the "before" run returned zero history excerpts. Those were probably ocean or empty places, but my test didn't tell a real empty result from a failed one beyond checking for a failed flag.
- I have no data from the live server. Whether the failures I saw on Render were this queue or Nominatim or Wikipedia rate-limiting the server's IP is still unconfirmed.
What I took from it
- A shared resource under concurrency fails in ways single-user testing can't show. Firing concurrent requests at it exposed the problem.
- Retries can multiply load unless they are coordinated.
- Caching an incomplete result is risky, so partial results expire fast.
- Free open services come with usage policies, and respecting them shapes the architecture.
What I Learned Outside (and What Broke)
I tested Stela on foot in Dhaka, walking from Mirpur 14 to Gulshan [ON 11 Oct / 10:00 am], using the live link on my phone with Walk mode on.
What worked
- Walk mode did its job. It stayed quiet while I walked and asked for a new story only after I had moved a set distance (about 300 m), instead of polling on a timer.
- The history changed with the neighbourhood. The "People and past civilizations" card gave different excerpts in Mirpur 14 and in Gulshan, because Stela looks up the place hierarchy (neighbourhood, city, district) and finds the Wikipedia history that matches each level.
- Results were fast. In Dhaka, where the open rock map has no data, the whole result took about 4 seconds. At Dover, the example place with the most data, the full page took about 6 seconds. I measured both [on mobile data / on Wi-Fi] with the server already awake.
- The badge unlock animation was smooth on my phone.
What the walk showed me about the limits
- Dhaka has no rock or fossil data. The open geological map returns nothing for Dhaka, so the rock and fossil cards were empty, and the app said so instead of guessing. The geology half of the idea works best where a geological map exists, so I tested the rock and fossil features on the example places rather than at my own doorstep.
- The rare animal records did not change when I walked. They were the same in Mirpur 14 and in Gulshan, because the search covers a much wider area than my walk. That's an honest limit of a radius lookup in a dense city.
- Walk mode only works while the screen is on and the page is visible. When I locked the phone or switched apps, it stopped updating. That's a browser limit: browsers pause GPS for hidden pages, so a web app can't track you in the background. A native app could, but I chose a web app so anyone can open it from a link.
- Battery drain was normal for an app that keeps GPS on.
- It needs internet. Every story comes from several live data calls, so on the move it depends on mobile data. I'm not claiming it works with no signal.
Some failures came from the data, not the code:
- Hammerhead sharks in Dhaka. My first test in Dhaka listed sharks and rays under "rare animals alive today." The record I opened was a photo of a dead hammerhead on the ground, probably from a market. GBIF records say where an animal was seen, not where it lives. So I renamed the card "Rare animal records" and the story now says "recorded nearby," never "alive." Photos are of the species in general, never of the sighting.
- No rock map for Bangladesh. The rock layer returned nothing for Dhaka or ten other Bangladeshi towns I tried, so I replaced Dhaka in the examples with New Orleans, which gives a real, very young rock. That gap is an honest finding about open geological data coverage.
- No Bengali animal names. None of Dhaka's eight rare species had a Bengali, Hindi or Urdu name in GBIF, so Dhaka shows English names or only scientific names, and the card says local names may be incomplete.
- Fossil records follow research. Dense records often mean people have worked there, so a quiet place may be understudied, not empty. The app says so.
- Wikipedia rate limits. Loading one place triggers several requests, and anonymous clients can hit HTTP 429. Stela spaces its requests, shows what it has with a note when some fail, and falls back to placeholders.
What's Next
- Richer people and history. Right now the history card shows short, attributed Wikipedia excerpts. I'd like to tell the story of who lived in an area and how it changed over time in more depth, with a source for each claim. The rule would stay the same: the model may only retell what the sources say.
- District and division fallbacks for a "places to visit" card (museums and historic sites), which I scoped out this week.
- Offline use by caching data for a region before a hike.
- More languages, since local animal names and history are thin outside English sources.
Prize Categories
- Best Use of Render: the app is deployed on Render's free tier from GitHub, with secrets in the environment settings and a health check.
- Best Use of Gemma: the stone scribe is Gemma 4 26B A4B, served through OpenRouter. Gemma only narrates facts that my code collected from open datasets. It never looks anything up, and code checks discard any story that names a species, person or date that isn't in those facts.






Top comments (1)
Official Platform Update
Security protocols have been updated for all developer accounts.