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

What I Built

Every October I ask the same question: are the maples in High Park at their best yet, or am I a week early? I'm not the only one. Someone in Kyoto asks it about momiji (紅葉), someone in Seoul about danpung (단풍), and in a few months someone in Sydney or Taipei will ask it about cherry blossoms. The answer usually exists. It's just scattered across yesterday's photos on the internet.

Peak Season answers it for wherever you are. Open the page, and it shows:

  • the plant of the moment for your area: autumn leaves now, cherry blossoms in spring. It's picked from where recent reports are coming in, so it works in both hemispheres without a calendar;
  • a short list of places near you, Peak first, then by distance. Each place has a stage badge (Green / Turning / Peak / Past peak), how many recent reports back it up and how fresh they are, and one photo with its photographer's credit and licence;
  • Directions buttons that open Google Maps, Apple Maps or OpenStreetMap on your phone.

There's no feed, account or infinite scroll. I wanted the screen to be the shortest part of the outing: you decide, tap Directions, and go outside.

It's for anyone who loves an autumn walk but doesn't want to drive an hour to find bare branches. And it's built to keep running, for free, long after this challenge.

Demo

Live: https://peak-season-dvl.pages.dev/

It asks for your location. If you'd rather not share it, pick a city. To look at other places, add coordinates to the URL:

The data refreshes every 12 hours, and "Last updated" at the bottom shows how fresh it is. It's early October, so most of the world is still green or just starting to turn, and that's what the site shows. Where there isn't enough evidence, it says "Not enough reports".

Code

GitHub logo klee1611 / peak-season

Where nature is at its peak near you, right now. Gemma on a free GitHub Actions runner judges autumn leaves and cherry blossoms from iNaturalist photos. $0/month.

Peak Season

Where nature is at its peak near you, right now. Autumn leaves, cherry blossoms, and more anywhere in the world.

Peak Season collects recent public nature photos (iNaturalist) and web reports (SerpApi, for famous spots). An open-weight model, Gemma, runs on a free GitHub Actions runner and judges how far along each seasonal moment is in each photo. The site then tells you, for wherever you are, which places nearby are at their best, how fresh the evidence is, and gives you one-tap directions in your own map app. Decide, tap Directions, go outside.

Built for the DEV Hacktoberfest Open-Source AI Challenge, Week 1: "Touch Grass".

How it works

 every 12 h, GitHub Actions (CPU only, free for public repos) iNaturalist (global) ─┐ SerpApi
…

MIT licensed. The pipeline is Python 3.12 and the site is vanilla JavaScript, with no framework, map library or third-party scripts. There are 110 tests.

How I Built It

The open-source AI at the core is Gemma (gemma4:e2b-it-qat, a small multimodal Gemma), served by Ollama on a free GitHub Actions runner. It runs on the runner's CPU and costs nothing.

GitHub Actions, every 12 hours (free CPU runner) iNaturalist photos SerpApi web snippets (worldwide) (famous spots only) │ │ └───────────────┬────────────────┘ ▼ Gemma via Ollama judges the stage (every answer cached in SQLite) │ ▼ Group into ~5 km cells, weighted average │ ▼ Small JSON files, one per region: git commit │ ▼ Cloudflare Pages serves the static site; your browser loads only your region 

1. Collect. The workflow pulls recent iNaturalist observations of each plant worldwide. It keeps only photos whose Creative Commons licence allows showing them. One run finds about 5,600 maple observations in 3,100 places.

2. Gemma looks at the photos. For each photo, Gemma answers in strict JSON: is this really a maple, and is it green, turning, at peak or past peak? The answer is checked against the plant's list of stages. Invalid output gets one retry, then it's marked unclassified. Photos of just a trunk or a single fallen leaf are rejected, because they say nothing about the tree.

3. Gemma reads the web, in the local language. iNaturalist is used mostly by naturalists in North America and Europe. Millions of people visit Arashiyama or Seoraksan in autumn, but those places barely appear there. So for world-famous spots that are in season nearby but have few reports, the pipeline searches Google through SerpApi, in the spot's own language, for the past week only:

"嵐山" 紅葉 見頃 Arashiyama, autumn leaves, best viewing hl=ja gl=jp "parc de la Gatineau" couleurs automne hl=fr gl=ca "fall colours" "High Park" hl=en gl=ca 

Gemma then reads the result snippets and extracts a stage. It understands phrases like 見頃 (at its best), 色づき始め (starting to turn) or 단풍 절정 (peak). The site links to the sources and doesn't download their images.

4. Combine. Reports are grouped into cells about 5 km across and averaged. Fresh, confident reports count more (confidence × weight × e^(−age / 3 days)), and a web snippet counts 0.6 of a photo. A place with fewer than two reports shows "Not enough reports" instead of a stage.

5. Publish static files. The workflow commits small JSON files, one per region of about 1,250 km (about 240 KB for the whole world), and Cloudflare Pages redeploys. Your browser downloads only your region and its neighbours, and works out distances itself. Visitors never trigger an API or model call, so traffic costs nothing, however much there is.

A free CPU is slow, so the pipeline has a budget

On the runner's CPU, Gemma takes about 44 seconds per photo (17 s on my laptop's GPU). That's about 110 photos per run, against thousands of new ones. So each run spends its time carefully: at most 3 photos per place, famous spots first, then the busiest places, newest photos first. It stops at a fixed time so it always finishes publishing. Every answer is cached, so the next run picks up where the last one stopped.

SerpApi has its own guardrails. Before searching, the pipeline asks SerpApi's account endpoint how many searches are left (that check is free). It keeps a reserve, caps searches per run, and never repeats a search within 48 hours. If it can't check the account, it makes zero searches.

The first live SerpApi run found a bug: photos ran first and used up the time, so Gemma read only 13 of the 32 snippets I'd paid searches for. Now, when SerpApi is on, photo work stops 20 minutes early so every paid search gets read.

Don't trust the model: test it first

A plant only appears on the site after its prompt agrees with iNaturalist's community annotations (people tagging a photo "Green Leaves", "Colored Leaves", "Flowers"...) on at least 20 photos, at 70% or better.

My first maple prompt failed at 62%. Gemma called green leaves "turning" whenever sunlight hit them, when the canopy was lit from behind, or when leaves had cream-coloured edges. I also found a bug in my own test: one observation can be tagged both "Green Leaves" and "Colored Leaves" (a half-turned tree), and I was counting a correct answer as wrong. After fixing both, maple passed at 81%, and again at 81% on 26 older photos the prompt had never seen. Cherry blossoms passed at 92%, with one caveat I'd rather say out loud: none of the sample photos showed buds only, so "buds vs. bloom" hasn't really been tested yet.

Two more rules. iNaturalist's "Colored Leaves" tag marks a place as in season but never sets a stage, because it covers everything from the first red leaf to bare branches. And nothing in the code knows what a maple is. Each plant is a YAML file and a prompt, so cherry blossoms went live in the southern hemisphere with no code changes.

Why Does Open Innovation Matter?

Without an open model, this project couldn't run for free.

Peak Season looks at hundreds of photos a day. With a closed vision API, that would be a monthly bill that grows with the world. With Gemma on a free runner it's $0 a month, however many people visit, so it can keep running after the hackathon instead of shutting down when the credits run out.

The same open model reads a photo from Toronto and a Japanese news snippet about Arashiyama. I didn't have to pay extra to make it work in more than one language.

People's photos only go to a model running on the project's own runner. They're never sent to a third-party AI service, and never stored or rehosted.

Nothing is locked in. The model is called through the OpenAI-compatible protocol, so moving to a bigger Gemma, or to a hosted Gemma when the CPU isn't enough, takes two repository secrets and no code change. The site is plain files that could move to any static host tomorrow.

Anyone can check the work. The prompts, the validation script, its results and the data are all public. If you think Gemma is wrong about your local park, you can see why, and fix it.

And a new plant is a pull request. Plum blossoms, tulips, lavender or ginkgo each need one YAML file and one prompt, which would make a nice first Hacktoberfest contribution.

The same pipeline tells someone in Toronto when to see maples and someone in Sydney when to see cherry blossoms, and I'd like it to stay open for anyone to use or extend.

Prize Categories

  • Best Use of Gemma: Gemma runs self-hosted on a free CPU runner, judges photos, and reads web snippets in Japanese, Korean, Chinese, French and English, with a public, repeatable test against iNaturalist annotations.
  • Best Use of SerpApi: budgeted searches in the local language for famous spots that iNaturalist misses, with the budget read live from SerpApi's account endpoint and every response cached.

Thanks for reading. Now close the tab and go outside. 🍁