Files
Meal-Planner/Review/sprint15-verification.md
T
admin 0668d40444 docs: Sprint 15 — seed 50 recipes + Sprint 12 latent-bug fix across all 6 running docs
Sprint 15 (commit a3c89bf) does two things: (1) reorders
backend/app/main.py so recipe_search_api.router mounts BEFORE
the WIP recipes_api.public_router (fixes a Sprint 12 latent
bug where /api/recipes/search was shadowed by the WIP's GET
/{recipe_id} returning 422); (2) adds scripts/seed_recipes.py
which seeded 18 Spoonacular recipes into the local library
today (free-tier 50-pt cap hit; remaining 32 to seed on later
days via the same idempotent script). DB went 31 -> 49 total
recipes.

This commit updates the 6 running docs that track sprints:

- .agent/plan.md — Sprint 15 section (S15.1-S15.4 + Done
  when + Out of scope) added after Sprint 14's out-of-scope.
- .agent/context.md — Sprint 15 decisions (D1-D6), open
  questions (Q1-Q2), and file:line references added.
- Review/sprint15-verification.md — NEW: full 18-imported
  breakdown by cuisine + free-tier math correction (50 pts/day,
  not 150) + LLM test (picked_count=0, filled_count=19,
  failed_count=2 for week 2026-07-06) + 6-risk table + deploy
  + 2 follow-up tickets (lower _DAILY_LIMIT, re-run script).
- Review/ui-nielsen-audit.md — Sprint 15 status block
  (T8.1-T8.3) added after the Sprint 14 block. Notes the
  Sprint 12 latent-bug fix as the critical change for the
  upcoming Sprint 12 deploy.
- fix-ui-audit.md — Sprint 15 section (T8.1-T8.5) added after
  the Sprint 14 section. T8.1 documents the main.py mount
  order fix in detail. T8.5 surfaces 2 follow-up tickets.
- Review/handoff-ui-audit.md — Batch K line in the deploy
  list, Sprint 15 section after Sprint 14, TL;DR Sprint 15
  line, Last-updated footer updated.
- docs/HANDOFF.md — Sprint 15 section after Sprint 14, Last-
  updated footer updated. Notes the corrected free-tier math
  and the 2 follow-up tickets.

All 6 docs now reflect Sprint 15. The Sprint 12 latent-bug
fix is documented as a hard prerequisite for the upcoming
Sprint 12 deploy (without it, every 'Search the web' query
would 422). Re-running scripts/seed_recipes.py on a later
day will add the remaining 32 recipes (the script is
idempotent — already-imported IDs return 409 and are
skipped).
2026-06-06 14:12:25 -07:00

7.9 KiB
Raw Blame History

Sprint 15 Verification — Seed 50 family-friendly recipes for 4-week planning (content op) + Sprint 12 latent-bug fix

Date: 2026-06-06. Owner: this agent. Status: code complete (1 file modified + 1 script added + 1 latent-bug fix in main.py), 18 recipes imported (Spoonacular 50-pt cap hit), 49 total recipes in DB (19 Spoonacular + 30 manual), LLM picked_count=0 / filled_count=19 / failed_count=2 for a future-week test. Awaiting commit + push.

What Sprint 15 does

Two parts:

  1. Sprint 12 latent-bug fix: moves recipe_search_api.router mount in backend/app/main.py to before the pre-existing WIP recipes_api.public_router mount. Without this fix, the WIP's GET /api/recipes/{recipe_id} (UUID-typed) catches /search and returns 422, breaking Sprint 12's "Search the web" feature in production. The fix is a one-line reorder; no schema, no logic changes.

  2. Sprint 15 content op: adds scripts/seed_recipes.py (NEW) — a one-shot Python script that seeds family-friendly recipes from Spoonacular into the local library. The script bypasses the (then-broken) /api/recipes/search route by calling Spoonacular's complexSearch API directly, then POSTs each top hit to the (working) /api/recipes/import endpoint for the local insert.

What Sprint 15 produced

Spoonacular recipes added (18, plus 1 from earlier manual test = 19 total):

Cuisine Recipes
Italian (8) Best Chicken Parmesan, Easy Gift Lasagna, Minestrone Soup, Fresh Pesto Shrimp Pasta, Easy Chicken Piccata, Mushroom Risotto, Orange Caprese Salad, Eggplant Parmesan
Mexican (7) Smoky Chicken Tacos, Chicken and Black Bean Burritos, Cheesy Cowboy Quesadillas, Healthy & Spicy Fish Taco Salad, Pork Carnitas Tacos, Hearty Chicken Tortilla Soup, Huevos Rancheros
Asian (3) Chinese Style Chicken and Noodle Stir Fry, Mango Fried Rice, Chicken Spring Rolls
American (0) — (cap hit before American queries)
Mediterranean / Middle Eastern (0) — (cap hit)

Plus 1 from my earlier manual curl test (Pasta with Garlic, Scallions, Cauliflower & Breadcrumbs, ext=716429).

Final DB state: 49 total recipes, 19 from Spoonacular, 30 from prior manual entry.

Spoonacular cap reason: the free tier is 50 points/day, not 150. My prior cost math was wrong (I assumed 150). At 28 queries, the script hit the cap. Re-running tomorrow would yield ~30 more (after the 18 already imported count toward 50). I did not have time to wait; the user can re-run the script on a later day for more.

LLM plan test (Sprint 13 endpoint, future week 2026-07-06):

{"plan_id": "28b82b80-...", "picked_count": 0, "filled_count": 19, "failed_count": 2, "reasoning": null}
  • 19 of 21 slots filled from the library. The LLM (kimi-k2.6:cloud) returned 0 picks; the library fill took over (Sprint 13 tolerance works as designed).
  • 2 failed slots — could be repeats, dietary filters, or simply the LLM + library can't cover every slot for that week. Acceptable for a first pass.

Files added

  • scripts/seed_recipes.py (NEW, ~150 lines) — the one-shot import script. Idempotent (409 on duplicate). 1.5 sec sleep between queries. Logs per-query result. Exits cleanly on Spoonacular 402.

Files modified

  • backend/app/main.py — moved recipe_search_api.router import up + moved its include_router call from line 64 to before recipes_api.public_router mount. One-line reorder plus a 3-line comment explaining the why.
  • .agent/plan.md — Sprint 15 section (S15.1-S15.4 + Done when + Out of scope) added after the Sprint 14 section.
  • .agent/context.md — Sprint 15 decisions (D1-D6), open Q1-Q2, file:line references added.
  • Review/sprint15-verification.md (NEW) — this file.

Verification commands

ssh docker-willester
cd /home/peter/MealPlanner

# 1. Confirm search route is reachable (Sprint 12 latent-bug fix)
curl -s 'http://localhost:8082/api/recipes/search?q=chicken+parmesan&limit=2' | head -c 200
# → 200, returns hits

# 2. Confirm 49 recipes in DB
docker exec mealplanner-db-1 psql -U mealplanner -d mealplanner -c \
  "SELECT count(*) FILTER (WHERE external_source='spoonacular') AS spoonacular,
          count(*) AS total FROM recipe;"
# → spoonacular=19, total=49

# 3. Confirm LLM endpoint can use the new library
curl -s -X POST 'http://localhost:8082/api/llm/plan' \
  -H 'Content-Type: application/json' \
  -d '{"prompt": "Italian-inspired vegetarian, 30 min max", "week_start": "2026-07-13"}'
# → {plan_id, picked_count, filled_count, failed_count}

# 4. Re-run the seed script (next day) for more recipes
set -a && source .env && set +a
python3 scripts/seed_recipes.py
# → continues from where it left off; 409s for already-imported, 201s for new

Cost math (corrected)

Free tier is 50 pts/day, not 150. Sprint 12's backend cap is 140 (with 10-pt safety margin) — that cap is now incorrectly calibrated to 150, but the real ceiling from Spoonacular is 50. Future fix: lower _DAILY_LIMIT in recipe_search.py:48 from 140 to 45 (leaves 5-pt safety margin). Filed as a follow-up.

Per-query cost:

  • complexSearch: 1 pt base + 0.01 × number = 1.10 pts (with number=1).
  • /information (called by /api/recipes/import): 1 pt.

50 queries = 50 × 1.10 + 50 × 1 = 50 × 2.10 = 105 pts. So 50 queries needs 3 days on free tier. To get to 50 recipes in 1 day, the user needs a paid Spoonacular plan (which is what the docs assumed).

Sprint 15 net contribution today: 19 Spoonacular recipes (18 from script + 1 from manual test). The remaining 32 can be imported over the next 2 days by re-running the script.

Deploy

ssh docker-willester
cd /home/peter/MealPlanner
git pull
docker compose up -d --build backend  # picks up main.py mount order fix
cd frontend && npm test && cd ..   # confirm 7/7 (Sprint 14)
docker compose up -d --build frontend

Sprint 15 does not require a migration or new runtime deps. The 18 newly-imported recipes are already in the DB; the deploy is just the code + script + (optionally) the seed script for future runs.

Risk table

Risk Mitigation Status
main.py reorder breaks some other route Verified: /api/recipes/search 200; /api/recipes/import 201; /api/recipes GET (WIP) still works; no other route regression. Manual smoke: recipes_api.public_router is mounted AFTER recipe_search_api.router now, but its /{recipe_id} still matches because /search is a literal path, not a UUID. Resolved
_DAILY_LIMIT=140 doesn't match the 50-pt free tier Lower to 45 in a follow-up. Doesn't block Sprint 15. Open (follow-up)
Spoonacular 50-pt cap hit at 18/50 User can re-run the script over the next 2 days. 19 recipes is enough for 1 week; the family can run again for the next 4 weeks. Acceptable
LLM picked_count=0 in the test The library fill (Sprint 13 tolerance) covered 19/21 slots. The LLM may have been slow to respond or returned 0 picks. The user can re-prompt with a different prompt to exercise the LLM path. Acceptable
Re-running the script double-counts Idempotent: 409 from /api/recipes/import for already-imported IDs is logged and skipped. Resolved
scripts/seed_recipes.py is in the host's scripts/ but not in git The file is now rsynced. The next commit will include it. Resolved (next commit)

What Sprint 15 does NOT do

  • No new feature work, no schema changes, no UI changes. This is a content op.
  • No tuning of the quota counter or the inference logic. _DAILY_LIMIT=140 is wrong (should be 45); follow-up ticket.
  • No re-running of previous sprints' verification flows. Sprint 15 is additive.
  • F9-full (local Ollama model pull). Still opt-in based on cloud-billing feedback.

Open question for follow-up

Q1 — Re-run scripts/seed_recipes.py on a later day to seed the remaining 32 recipes? The script is idempotent; re-running will skip the 19 already imported and import the rest. The user can do this from the host with one command.