Transforms Jeopardy questions into multiple-choice format using Gemini 2.5 Flash API with parallel processing.
-
Install dependencies:
bun install
-
Set your Gemini API key(s):
Option A: Using .keys file (recommended for multiple keys):
Create a
.keysfile in the project root with one API key per line:AIzaSyAYO2P5AI1jiSYSxW9rq0V4OoBIOz_dOzs AIzaSyBXYZ123456789abcdefghijklmnopqr AIzaSyC987654321zyxwvutsrqponmlkjihgLines starting with
#are treated as comments and ignored. The.keysfile is already in.gitignorefor security.Option B: Environment variable (single key):
export GEMINI_API_KEY='your-api-key'
Option C: Environment variable (multiple keys, comma-separated):
export GEMINI_API_KEY='key1,key2,key3'
Note: If
.keysfile exists, it will be used instead of the environment variable. -
Make sure
jeopardy.dbis in the project root
Default (5 parallel workers):
bun startTest with 2 workers and 5 batches:
bun start --workers 2 --limit 5High throughput (requires paid API tier):
bun start --workers 10 --delay 6000All options:
bun start \
--workers 5 \ # Parallel workers (default: 5)
--batch-size 100 \ # Questions per batch (default: 100)
--delay 3000 \ # Milliseconds between batches (default: 3000)
--limit 10 \ # Max batches to process (optional)
--db ./jeopardy.db # Database path (default: ./jeopardy.db)Run all tests:
bun testprocessing.log- All processing activity with timestampsfailed-batches.log- Details of failed batches for manual review
- Migration: Adds
processing_statuscolumn on first run to track batch states - Crash Recovery: Resets any stuck
processingbatches tounprocessedon startup - Parallel Workers: Launches N async workers (default: 5) that run concurrently
- Atomic Claiming: Each worker atomically claims batches via database transactions
- Processing: Sends batches to Gemini 2.5 Flash API for question generation
- Validation: Checks uniqueness, non-empty options, and sanitizes invalid JSON metadata
- Database Update: Marks questions as
completedwith generated options - Graceful Shutdown: Ctrl+C waits for workers to finish current batches
Automatic recovery with key rotation:
- 429 Quota Exceeded: Marks current key as exhausted (permanently removed from rotation), automatically switches to next available key with 5-second delay. Continues until all keys are exhausted.
Automatic retry with backoff:
- 503 Service Unavailable: Pauses for 30 seconds and retries up to 10 times before stopping
Fatal errors (stops all workers):
- All API keys exhausted (all keys hit quota limits)
- 10 consecutive 503 errors
- Other rate limits (429)
- Server errors (5xx)
- Network errors (ECONNREFUSED, ENOTFOUND)
Non-fatal errors (logs and continues):
- Invalid JSON responses
- Validation failures
- Partial batch responses
Failed batches are marked as unprocessed and will be retried on next run.
Sequential (1 worker):
- ~5,300 batches for full dataset (529,939 questions)
- ~5 seconds per batch (API call + processing)
- Total time: ~7.5 hours
Parallel (5 workers, default):
- 5 workers × 3s delay = ~10 requests/minute
- Stays safely under 15 RPM free tier limit
- Total time: ~53 minutes (3.3x speedup)
Parallel (10 workers, paid tier):
- 10 workers × 6s delay = ~10 requests/minute (safe pacing)
- Total time: ~26 minutes (17x speedup vs sequential)
Rate limit formula: requests_per_minute = (workers × 60) / delay_ms