JobTrack

ai-powered application tracker

JobTrack — Intelligent Job Search

A Gemini insights layer that walks six models down when the free quota runs out.

role
solo developer
status
● live demo
scale
6-model Gemini fallback
stack
React 19 · TypeScript · Firebase · Gemini
type
personal · open source

FIG.01 // JOBTRACK · MODEL FALLBACK

Six models deep, because the quota is free

The generation call walks an ordered chain and returns the first response that comes back. Any error, not only a 429, falls through to the next model; the rate-limit ledger is written, never read for routing.

Six models deep, because the quota is free01 · OPENdashboard02 · CACHE< 7 days old?per-user insights03 · PROMPTprompt builderdrop Applied · newest 50gemini-3-pro-preview01 · tried firstgemini-3-flash-preview02gemini-2.5-pro03gemini-2.5-flash04gemini-2.5-flash-lite05gemini-2.0-flash06 · last resortSIDE EFFECTai_rate_limits/{date}exhausted models04 · PARSERstrip bullets · max 4THE GUARANTEE05 · OUTPUTinsights cardfresh or cached< 7 days → cached insightsstaleany errorany errorany errorany errorany errorlogged, not routed:429 · 503 · quota errorsfirst response wins;all six fail → the lastmodel's error is shown

A job-search tracker built as a static React 19 SPA on Firebase: a four-stage drag-and-drop pipeline, conversion analytics derived in the browser, and a Gemini layer that reads your own application history back to you. There is no server of mine in the path; the browser talks to Firestore and Gemini directly, so Firestore security rules are the authorisation boundary.

01

Six models deep, because the quota is free

The insights run on Gemini's free tier, where the failure that matters is a 429 at the moment someone opens the dashboard. So the generation call is a walk down an ordered chain of six models, gemini-3-pro-preview first and gemini-2.0-flash last, returning the first response that comes back.

Any error falls through to the next model, not only a rate limit. A failure whose message carries 429, 503, 'rate limit', 'quota exceeded' or 'resource exhausted' is also appended, with arrayUnion, to an ai_rate_limits document keyed by today's date. That ledger is logged only: nothing reads it to route, so every call still starts at the top of the chain.

If all six fail, the error raised carries the last model's message rather than a generic one, so the cause is still legible after six silent retries.

6 modelsany error → next model

02

The prompt asks, the parser guarantees

The prompt receives four fields per application: company, role, stage and application date. Applications still at Applied are dropped, the rest are sorted newest-first and capped at fifty, and if nothing survives the feature says so instead of calling a model to produce filler.

The prompt asks for two to four single-sentence observations with no bullets or numbering. The parser strips any line that starts with a dash or a list number anyway, and hard-slices to four: the instruction is a request, the parser is the guarantee.

Generated insights are cached per user and reused until they are seven days old, or until the user forces a refresh, so opening the dashboard does not spend a model call.

newest 50max 4 insights7-day cache

screens