This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

What I Built

Jejak is a field dex. You photograph something alive, an open-weight vision model identifies it against a forkable species glossary, and the result becomes a collectible card with a rarity and a live value. Cards can be shared to a feed and sold into requests on a market board.

Who would pay for it: a nature guide running birding walks.

That is a real trade here in Indonesia. A guide in Bali or Bogor takes guests out before dawn, and the guest goes home with a camera roll and a vague memory of "a blue bird". The guide's morning is worth more if the guest leaves with an artifact: this was a Jalak Bali, this was a Cekakak Sungai — here is your collection, come back for the endemics. That is differentiation a guide can charge for, on a tour that already costs real money.

So the buyer is the operator — an individual guide, or a small eco-lodge running a few — paying a small monthly fee per guide. Which only works if the marginal cost of a catch is near zero. That constraint is the entire reason the model is open-weight, and I will come back to it.

Two segments are worth naming honestly:

  • The operator pays, because a takeaway their guests actually keep is cheap differentiation for a tour they already sell.
  • The hobbyist does not pay for the identification, but plays: because scarcity is measured from the database, the first person to catch a species holds the expensive copy and the tenth person holds a cheap one.

The problem it solves: identification apps stop at the answer. You get a name, you close the app, you never open it again. There is no record, no artifact, no reason to go out again. Jejak makes the answer the beginning of the thing — the card is the record, and the collection is the reason to look up tomorrow.

Demo

  • Live: [LIVE URL]
  • Sign in with: demo / jejak-demo-2026

The demo account is pre-seeded and the market board restocks itself, so you can catch, collect and sell without a second human being online. The best first move is Catch — upload any photo of an animal and watch the whole loop run.

Code

Repo: gitlab.com/iqbal_taufiqurrochman/jejak — MIT licensed.

npm install cp .env.example .env # Neon pooled URL, session secret, OpenRouter key npm run db:push && npm run seed 

How I Built It

Stack: SvelteKit 3 (JavaScript + JSDoc), Tailwind 4, Postgres on Neon through @neondatabase/serverless, deployed on Vercel.

The open-source AI at the core: the default backend is google/gemma-3-27b-it, an open-weight Gemma vision model served through OpenRouter. One call per catch does three jobs at once — identify the animal, ground the answer against my glossary, and write a short bilingual field note — returned as JSON and parsed tolerantly. Set AI_PROVIDER=ollama and the same code runs on a laptop with gemma3:4b and no network at all.

Grounding, not vibes. The model does not free-associate a species name. data/glossary.spesies.json is a plain, forkable, CC-BY list of around 65 Indonesian species, and the model has to pick from it. If it returns a scientific name that is not in the list, the catch is rejected. Anyone who knows more about Indonesian fauna than I do can fix the list in a pull request.

The confidence gate, which is where the game lives. Below 60% confidence the model is instructed to say so, the catch fails, and the keeper walks away with nothing — or pays 50 credits to re-analyse the same photo. Every game needs a way to lose.

Value is a live number, not a label:

value = base(rarity) × (1 + 2 / (1 + keepers_who_hold_this_species)) 

Scarcity is read from the database at catch time, so catching what nobody else has is worth more, and duplicates get cheap. Other keepers post requests on the market board — "I want a Jalak Bali, I pay 520 credits" — and you sell your card into that request in one transaction. The first person who catches something ends up holding the scarce copy.

Privacy as a feature rather than a promise:

What people usually do What Jejak does
Upload the full photo Sends a 1024px copy to the model; stores only a 320px thumbnail
Strip EXIF on the server Strips it on the device, before a byte is sent
Trust the client The server refuses any image that still carries an EXIF header
Keep precise GPS Rounds to two decimals, about a kilometre
Log IP addresses Stores a truncated SHA-256 of the caller, never the address

Full-resolution photos never reach the server. The GPS read happens locally with exifr and the JPEG is re-encoded through a canvas, which destroys the whole EXIF block on the phone.

Two serverless gotchas the community wrote about, handled in code. Delehq's Why Serverless Functions Keep Exhausting Your Postgres Connections is why this uses the Neon HTTP driver and never a raw TCP pool. Omer Hochman's Your database scales to zero. Your retry loop doesn't know that. is why only the database stage is retried, with exponential backoff — the model call is not retried blindly, because re-running a vision call you have already paid for is how you double your bill.

Why Does Open Innovation Matter?

The business only works because the model is open. A closed, metered identification API turns every guest photo into a cost line. A small monthly subscription per guide would get eaten by usage that grows with the guide's success — the better a guide does, the more they pay, and the margin inverts. Open weights on a shared endpoint make the marginal catch close to free; self-hosting Gemma on a lodge's own laptop makes it literally free after hardware. That is the difference between a nice demo and something a guide in Ubud can actually afford.

Rare species should not require a third party. Indonesia has the wildlife problems you would expect from a country with desirable songbirds. The photos are of animals at known locations. A closed API means that data leaves the building by design — the request is the data. Here the default is a hosted open model, but flipping one environment variable keeps every photo on your own machine, and the server-side EXIF rejection means the app refuses to be the weak link either way.

A local list beats a global model on local names. No frontier API knows what a Cekakak Sungai is in the way a guide does. Grounding against a CC-BY glossary means fixing that is an edit to a JSON file, not a fine-tuning run — and the same file can be translated or replaced wholesale for another country's fauna.

And no lock-in as a policy. OPENROUTER_MODEL is a string. If a better open-weight vision model ships next month, that is a configuration change, not a migration.

The honest trade-off: a closed frontier model would probably identify the hard cases — small brown birds in bad dawn light — more accurately than a 27B open-weight model does. What open bought me is the cost floor that makes the side hustle arithmetic work at all, the ability to keep guest photos on hardware the operator controls, and direct ownership of the glossary and the prompt for a niche where the honest answer from a general API is too often just "bird".

My Agent Session

[AGENT SESSION EMBED — to be filled before publishing]

My First User

[FIRST USER — to be filled before publishing]

Prize Categories

Best Use of Gemma — the default vision model is google/gemma-3-27b-it, from Google's open-weight Gemma family. It is not decoration: Gemma does the identification, the grounding against the glossary, and the bilingual field note in a single call, and the whole app is built around that one call being cheap enough to give away in a demo.