Public Access
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).
This commit is contained in:
@@ -501,3 +501,34 @@ The Sprint 11 "Generate Meal Plan" CTA was library-only. Sprint 13 splits it int
|
||||
- `frontend/src/components/OnboardingTour.test.tsx` (NEW) — 6 cases (S14.3)
|
||||
- `frontend/package.json` (MODIFIED) — devDeps + scripts
|
||||
|
||||
---
|
||||
|
||||
## Sprint 15 — Seed 50 family-friendly recipes for 4-week planning (content op)
|
||||
|
||||
### Decisions
|
||||
|
||||
- **D1 — 50 recipes, not 100.** 4 weeks × 21 meals = 84 picks needed minimum. 50 unique recipes with 1.7× rotation is enough variety and fits in a single Spoonacular day (105 pts under 140 cap).
|
||||
- **D2 — Distribution: 5 cuisines × 10 each.** Italian + Mexican + Asian + American + Mediterranean/Middle Eastern. This gives 5/7 days of cuisine rotation per week, the family's stated preference.
|
||||
- **D3 — Use Sprint 12's `POST /api/recipes/import` endpoint directly.** No new code path, no schema change, no UI change. The endpoint already does the 1-pt `/information` call + ingredient upsert + Recipe insert.
|
||||
- **D4 — Pick the top hit per query, not curated.** I trust Spoonacular's ranking; if the top hit is a bad fit, the user can delete it via the existing UI. The 50-query list is curated; the per-query hit is Spoonacular's pick.
|
||||
- **D5 — 1-2 sec sleep between imports.** The 1-pt rate is fine on the free tier, but throttling keeps me well under the per-second rate limit and avoids a burst that could trigger Spoonacular's abuse detector.
|
||||
- **D6 — Run on the host, not locally.** The host has the live backend (`mealplanner-backend-1` up, healthy). Local backend would need its own DB connection + env. The script lives at `scripts/seed_recipes.py` and runs in the host's shell.
|
||||
|
||||
### Open questions
|
||||
|
||||
- **Q1 — Curate the queries or use my list as-is?** Default: use my list as-is. The user can re-run or pick more queries if the result set is biased.
|
||||
- **Q2 — After seeding, the 4-week plan is generated client-side or server-side?** Default: client-side via the existing Dashboard's "Generate Meal Plan" CTA. Server-side plan synthesis is a future sprint.
|
||||
|
||||
### Sprint 15 file:line references
|
||||
|
||||
- `backend/app/api/recipe_search.py:48-50` — `_DAILY_LIMIT: float = 140.0`, `_SEARCH_URL`, `_INFO_URL`. The quota gate.
|
||||
- `backend/app/api/recipe_search.py:55` — `_points_available()` (140 - `_points_used`).
|
||||
- `backend/app/api/recipe_search.py:100-148` — `search_recipes` endpoint (`GET /api/recipes/search?q=...&limit=10`).
|
||||
- `backend/app/api/recipe_search.py:194-308` — `import_recipe` endpoint (`POST /api/recipes/import`).
|
||||
- `backend/app/schemas/__init__.py:398-411` — `RecipeSearchHit` Pydantic model.
|
||||
- `backend/app/schemas/__init__.py:413-415` — `RecipeImportRequest` Pydantic model (`{external_id, external_source}`).
|
||||
- `backend/app/security.py:54-78` — `require_session` (the SESSION_PASSWORD auth that gates both endpoints).
|
||||
- `backend/app/models/__init__.py:162-197` — `Recipe` model (the destination table for the imports).
|
||||
- `scripts/seed_recipes.py` (NEW) — the 50-query one-shot Python script.
|
||||
- `Review/sprint15-verification.md` (NEW) — the deploy + curl flow.
|
||||
|
||||
|
||||
@@ -553,3 +553,57 @@ User reported post-deploy: "The tour window looks great, but Clicking the X nor
|
||||
- **Tests for `recipes` API client or `useOnboarding` callers.** Not the S9 bug class. Future sprint.
|
||||
- **Tests for the backend.** The venv on `docker-willester` is broken; running pytest locally requires Nix fixes. Out of scope.
|
||||
- **F9-full (local Ollama model pull).** Opt-in based on cloud-billing feedback only. `_ask_llm` is the single seam — F9-full only needs to swap the URL + model name.
|
||||
|
||||
---
|
||||
|
||||
## Active sprint: Sprint 15 — Seed 50 family-friendly recipes for 4-week planning (content op)
|
||||
|
||||
### S15.1 — Recipe target list (50 queries, family default)
|
||||
|
||||
Distribution: **5 cuisines × 10 recipes each** = 50. Each cuisine gets a mix of cooking methods (sheet-pan, skillet, slow-cooker, one-pot, 30-min) so the LLM has variety. Each query is a free-text Spoonacular `complexSearch` query.
|
||||
|
||||
- **Italian (10):** "chicken parmesan", "spaghetti carbonara", "lasagna", "minestrone soup", "pesto pasta", "chicken piccata", "mushroom risotto", "caprese salad", "italian wedding soup", "eggplant parmesan"
|
||||
- **Mexican (10):** "chicken tacos", "beef enchiladas", "black bean burritos", "shrimp fajitas", "chicken quesadilla", "taco salad", "sopa de tortilla", "carnitas", "chicken tortilla soup", "huevos rancheros"
|
||||
- **Asian (10):** "chicken stir fry", "beef and broccoli", "pad thai", "fried rice", "teriyaki salmon", "tofu curry", "chow mein", "spring rolls", "pho", "kung pao chicken"
|
||||
- **American (10):** "chili", "meatloaf", "mac and cheese", "BBQ chicken", "pot roast", "shepherd's pie", "chicken pot pie", "beef stew", "burgers", "pulled pork"
|
||||
- **Mediterranean/Middle Eastern (10):** "chicken shawarma", "falafel", "hummus bowl", "greek salad", "lamb kebabs", "tabbouleh", "roasted vegetable wrap", "couscous", "stuffed peppers", "baked falafel"
|
||||
|
||||
**Dietary tags inferred from title+ingredients:** `_infer_protein_simple` in `recipe_search.py:151-170` (Sprint 12). Vegetarian entries: caprese, minestrone, pesto pasta, mushroom risotto, black bean burritos, taco salad, sopa de tortilla, huevos rancheros, fried rice, tofu curry, spring rolls, mac and cheese, falafel, hummus bowl, greek salad, tabbouleh, roasted vegetable wrap, couscous, stuffed peppers, baked falafel. That's ~20/50 = 40% vegetarian, which is the "vegetarian-heavy" target.
|
||||
|
||||
### S15.2 — Import script (one-shot Python)
|
||||
|
||||
- [ ] `scripts/seed_recipes.py` (NEW) — reads the 50-query list, calls `GET /api/recipes/search?q=...&limit=5`, picks the top hit per query, calls `POST /api/recipes/import` with `{external_id, external_source: "spoonacular"}`. Idempotent: 409 on duplicate → skip.
|
||||
- [ ] Uses the existing `SESSION_PASSWORD=test-family-password` for `require_session` auth. POST body is JSON. Runs in a single Python process.
|
||||
- [ ] 1-2 sec sleep between queries to stay well below the 1-pt + 0.01 × 5 hits = 1.05 pts/search rate. **No race with the frontend's quota counter** — process-local, single-threaded.
|
||||
- [ ] Logs per-query result: `external_id`, `name`, `points_used_so_far`, `quota_status`.
|
||||
|
||||
### S15.3 — Verify
|
||||
|
||||
- [ ] `SELECT count(*) FROM recipe WHERE external_source='spoonacular';` returns ~50 (give or take the 5-10 that fail to return hits).
|
||||
- [ ] Spot-check 5 random recipes in the UI: `/recipes` page shows them with the right image, ingredients, prep time.
|
||||
- [ ] `mealPlannerApi.llm.plan({prompt: 'Italian, vegetarian', week_start})` returns `picked_count > 0` and uses the new recipes.
|
||||
- [ ] 4-week plan generation: `meals.fillEmptySlots` should pull from a richer library, fewer "no recipe available" failures.
|
||||
|
||||
### S15.4 — Docs (all 6 running docs updated)
|
||||
|
||||
- [ ] `Review/sprint15-verification.md` (NEW) — the 50-query list + the script + the curl flow + the expected counts.
|
||||
- [ ] `.agent/plan.md` — Sprint 15 section (S15.1-S15.4 + Done when + Out of scope).
|
||||
- [ ] `.agent/context.md` — Sprint 15 decisions (D1-D6), open Q1-Q2, file:line references.
|
||||
- [ ] `Review/ui-nielsen-audit.md` — Sprint 15 status block.
|
||||
- [ ] `fix-ui-audit.md` — Sprint 15 section.
|
||||
- [ ] `Review/handoff-ui-audit.md` — Batch K, TL;DR, last-updated.
|
||||
- [ ] `docs/HANDOFF.md` — Sprint 15 section + last-updated footer.
|
||||
|
||||
### Done when (Sprint 15)
|
||||
|
||||
- [ ] All boxes above ticked.
|
||||
- [ ] 50 (or close to 50) Spoonacular recipes in the local `recipe` table.
|
||||
- [ ] `_points_used` counter ends at ~105-130 pts (under the 140 cap).
|
||||
- [ ] All 6 doc files have a Sprint 15 status block.
|
||||
|
||||
### Out of scope (Sprint 15)
|
||||
|
||||
- **No new feature work, no schema changes, no UI changes.** This is a content op. Any change to the import endpoint, the search endpoint, or the recipe model is out of scope.
|
||||
- **No tuning of the quota counter or the inference logic.** Sprint 12's `_infer_protein_simple` is good enough.
|
||||
- **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.
|
||||
|
||||
Reference in New Issue
Block a user