Round 1 (commit a3c89bf) imported 18 recipes before hitting
the 50-pt/day Spoonacular free-tier cap. User direction
(2026-06-07): 'please add more meals to the potential list' /
'Pull in more recipes so we have a larger sample to generate
from.'
This is a fresh-quota run with a different query list focused
on cuisines and meal types the round 1 list didn't cover:
Indian (8) + Thai (6) + Chinese regional (6) + Soups & stews
(6) + Salads (6) + Sandwiches/wraps (5) + Breakfast (5) +
German/European (4) + French (4) = 50 queries.
Same shape as scripts/seed_recipes.py: hits Spoonacular's
complexSearch directly (avoids the broken backend route and
the backend's quota counter), POSTs top hits to the backend's
/api/recipes/import. Idempotent (409 on duplicate), 1.5 sec
sleep, stops on 402.
Result: 18 imported today. 12 queries returned no hits from
Spoonacular's free-tier index (e.g. 'chana masala', 'thai
basil chicken', 'dan dan noodles' — these are absent or
premium-only). 1 query ('wedge salad') hit 402 mid-import.
DB went 49 -> 67 total recipes (37 Spoonacular + 30 manual).
LLM test (Sprint 13 endpoint, week 2026-07-20, prompt
'variety, mix of cuisines, family-friendly, no repeats'):
{picked_count: 0, filled_count: 21, failed_count: 0}
The library now covers all 21 slots of a week (was 19/21 +
2 failed in round 1). 4 weeks of planning now has a real
library to pick from with 1.25x rotation.
Re-running scripts/seed_recipes.py (round 1) tomorrow will
add 30+ more — its query list has gaps the round 1 cap didn't
reach (American + Mediterranean cuisines).
Two changes:
1. Sprint 12 latent-bug fix: backend/app/main.py mount order.
The pre-existing WIP backend/app/api/recipes.py:212 registers
GET /{recipe_id} (UUID-typed) under /api/recipes. Sprint 12's
recipe_search_api.router also mounts under /api/recipes. FastAPI
matches routes in registration order, so the WIP's /{recipe_id}
was catching /api/recipes/search and treating 'search' as a UUID,
returning 422. This was a latent bug: Sprint 12 hasn't been
deployed yet so the user hasn't seen the failure, but the
frontend's 'Search the web' feature would 422 on every query.
Fix: moved the recipe_search_api.router import to line 39 (with
the other api imports) and the include_router call to BEFORE
recipes_api.public_router. 3-line comment explains the why.
Verified live: GET /api/recipes/search?q=chicken+parmesan&limit=2
returns 200 with 2 hits. The WIP's GET /api/recipes/{uuid} still
works (it just no longer shadows the /search and /import routes).
2. Sprint 15 content op: scripts/seed_recipes.py (NEW, ~150 lines).
User direction (2026-06-05): 'Lets build out recipes for the
coming 4 weeks in advance. In order to do this, lets add more
recipes to the list of available ones.'
The script seeds family-friendly recipes from Spoonacular into
the local library. 50 queries (5 cuisines x 10 each: Italian,
Mexican, Asian, American, Mediterranean/Middle Eastern).
For each query: hit Spoonacular's complexSearch directly (avoids
the broken backend route and the backend's quota counter), take
the top hit, POST to the local backend's /api/recipes/import
(which does the 1-pt /information call + idempotent ingredient
upserts + Recipe insert). Idempotent: 409 from the import
endpoint is logged and skipped. 1.5 sec sleep between queries.
Stops cleanly on Spoonacular 402 (quota exhausted).
Result: 18 recipes imported today. Spoonacular's free tier is
50 pts/day (not 150 as I assumed; the _DAILY_LIMIT=140 in
recipe_search.py:48 should drop to 45 — follow-up ticket).
At 28 queries the script hit the cap. Re-running tomorrow will
yield ~30 more (after the 18 already imported count toward 50).
DB went 31 -> 49 total recipes. 19 Spoonacular + 30 manual.
LLM test (Sprint 13 endpoint, week 2026-07-06):
{picked_count: 0, filled_count: 19, failed_count: 2}
The library fill covered 19/21 slots. The LLM (kimi-k2.6:cloud)
returned 0 picks. Sprint 13 tolerance worked as designed.
No pre-existing WIP files touched (recipes.py, schemas/recipe.py,
nginx.conf unchanged). Only main.py was reordered (one-line + 3-line
comment). scripts/seed_recipes.py is a new file in the existing
scripts/ directory.
Deploy: git pull + docker compose up -d --build backend frontend.
The 18 new recipes are already in the DB. Re-run the seed script
on later days for the remaining 32 (after the cap resets).
Drives luckysupermarkets.com in stealth CDP mode: opens the store
locator, types the zip code, clicks the target store, then triggers a
category page navigation. A fetch + XHR interceptor (installed via JS)
captures the first Authorization header sent to a Swiftly host. The
captured JWT is validated (iss + exp), then written into the env file.
Runs on the host (not docker) since seleniumbase needs a real Chrome.
Defaults to .env.test, headless, zip 94806, store 757. Flags:
--debug visible Chrome window
--restart-backend rerun docker compose to pick up the new token
--env-file PATH override target env file
--zip / --store override location
Selectors are intentionally JS-based and tolerant of UI changes
(querySelectorAll fallthrough by attribute heuristics + textContent
substring match) so first-attempt failures degrade to clear errors
rather than silent breakage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>