docs: Sprint 13 — F9-lite (Ollama Cloud free-text plan synthesis) across all 6 running docs
CI / backend (pytest + alembic) (push) Has been cancelled
CI / frontend (build) (push) Has been cancelled

Sprint 13 (commit bae9403) splits the Sprint 11 "Generate Meal
Plan" CTA into a 2-step modal: "Use the recipe library" (default,
Sprint 11 unchanged) or "Ask the LLM" (new). The LLM path POSTs
to /api/llm/plan; the backend calls kimi-k2.6:cloud on ollama.com,
parses the LLM’s JSON picks, creates a fresh plan, fills the
LLM’s picks, and falls through to the Sprint 6+ fillEmptySlots
pattern for the slots the LLM didn’t cover. No pre-existing WIP
files touched.

This commit updates the 6 running docs that track the sprint:

- .agent/plan.md — Sprint 13 section (S13.1-S13.4) added.
- .agent/context.md — Sprint 13 (D1-D9, Q1-Q3) added; file:line
  references; key takeaways.
- Review/sprint13-verification.md — new file: 3-step browser
  smoke + 4 API curls + a11y check + 6-risk table + future
  work section.
- Review/ui-nielsen-audit.md — Sprint 13 status block (T7.1-T7.3)
  at the top, after the Sprint 12 block.
- fix-ui-audit.md — Sprint 13 section (T7.1-T7.5) added after
  the Sprint 12 section.
- Review/handoff-ui-audit.md — Batch I added to the deploy
  instructions; Sprint 13 section added after Sprint 12; TL;DR
  table row 13 added; Last-updated footer updated.
- docs/HANDOFF.md — Sprint 13 section added after the Sprint 12
  section, with a path-forward paragraph for F9-full.

All 6 docs now reflect Sprint 13. §Future backlog remaining:
F9-full (local Ollama model pull on the host) — opt-in based on
cloud-billing feedback. _ask_llm is the single seam: F9-full only
needs to swap the URL + model name.
This commit is contained in:
2026-06-05 16:59:20 -07:00
parent bae94037f3
commit 8cb4d4198c
7 changed files with 361 additions and 6 deletions
+77
View File
@@ -424,3 +424,80 @@ User reported post-deploy: "The tour window looks great, but Clicking the X nor
- **Auto-enriching existing recipes** with macros (would require `nutrition` endpoint = 1 pt per recipe; out of free quota).
- **Modifying the pre-existing WIP** `backend/app/api/recipes.py` / `schemas/recipe.py` / `nginx/nginx.conf` — untouched.
- F8 Spoonacular + F9 Ollama + dead `Generate Meal Plan` CTA — separate.
---
## Sprint 13 — F9-lite (Ollama Cloud free-text plan synthesis) — DRAFTED, awaiting user approval
**User direction (2026-06-05):** "Proceed." F9-lite reuses the pre-existing `OLLAMA_*` config (config.py:36-38: `OLLAMA_BASE_URL=https://ollama.com/v1`, `OLLAMA_API_KEY`, `OLLAMA_MODEL=kimi-k2.6:cloud`). Avoids the local model pull (F9-full would be 4 GB on disk + a separate uvicorn process). Cloud LLM — costs apply per call (operator's existing OLLAMA billing).
**Pre-existing infrastructure to reuse (not recreate):**
- `backend/app/services/llm_matcher.py:97-144``_ask_ollama(ingredient_name, candidates)` helper. The exact call pattern Sprint 13 mirrors: `POST ${OLLAMA_BASE_URL}/chat/completions` with `Authorization: Bearer ${OLLAMA_API_KEY}`, `model: settings.OLLAMA_MODEL`, `max_tokens: 500, temperature: 0`, parse `choices[0].message.content`, strip `` blocks.
- `backend/app/services/recipe_enrichment.py` — parallel LLM helper for recipes (different prompt shape; not reused).
- `backend/app/config.py:36-38` — OLLAMA config.
**Pre-existing WIP (NOT touched):** same as Sprint 12.
### S13.1 — Backend: `POST /api/llm/plan` (public, webui-facing)
- [ ] **NEW** `backend/app/api/llm_plan.py` — single endpoint + a thin `_ask_llm(prompt)` helper. The endpoint:
1. Validates the request body (`prompt: str` 1-500 chars, `week_start: date`).
2. Reads the local recipe library (`Recipe` table, filtered by `family_profile_id`); serializes a compact list `{id, name, cuisine_tags, dietary_tags, protein_type, total_time_minutes, dietary preferences}`.
3. Builds a prompt: "You are planning a 7-day meal plan (Mon-Sun). The user wants: '<prompt>'. Pick up to 21 meals (7 breakfasts + 7 lunches + 7 dinners) from the recipe library. Return JSON: `[{"day_of_week": 1-7, "meal_type": "breakfast|lunch|dinner", "recipe_id": "<uuid>"}]`. If a slot has no good match, omit it. Use only recipe_ids from the list. Reply with JSON only — no commentary."
4. Calls `_ask_llm(prompt)` (mirrors `_ask_ollama` from `llm_matcher.py:97-144`).
5. Parses the JSON response (try `json.loads`, fall back to `re.search(r"\[.*\]", content)` to handle markdown code fences).
6. Validates each entry: `recipe_id` is a UUID, `day_of_week` in 1-7, `meal_type` in {breakfast, lunch, dinner}. Drop invalid entries.
7. Creates an empty plan via `meals.create` (Sprint 6+ endpoint), then bulk-inserts the LLM-picked items + calls `fillEmptySlots` for the slots the LLM didn't cover.
8. Returns `{plan_id, picked_count, filled_count, failed_count, reasoning: <LLM raw text if non-empty>}`.
- [ ] **MODIFIED** `backend/app/schemas/__init__.py` — add `LLMPlanRequest` + `LLMPlanResponse` Pydantic models.
- [ ] **MODIFIED** `backend/app/main.py` — register the new router at `/api/llm`.
- [ ] 503 with clear `detail: "OLLAMA_API_KEY not configured"` when env var unset.
- [ ] 422 on empty / oversized prompt.
- [ ] Cap on library size sent to the LLM: 200 recipes max (alphabetical by name). Larger libraries would exceed prompt tokens.
### S13.2 — Frontend: free-text prompt in the Sprint 11 flow
- [ ] **MODIFIED** `frontend/src/pages/Dashboard.tsx` — turn the Sprint 11 `handleGenerateFirstPlan` into a 2-step:
1. New `MealPlanPromptModal` component (inline in `Dashboard.tsx`, ~40 lines, reuse `Card`/`Button`/`Input` from `components/ui/*`): a small modal with a textarea (max 500 chars, char counter) + two radio options: "Use the recipe library" (default) and "Ask the LLM". On submit, calls one of two API methods.
2. **Library path** (default): keep Sprint 11's `meals.create` + `fillEmptySlots` exactly as-is.
3. **LLM path** (new): `mealPlannerApi.llm.plan({prompt, week_start})`. On success, invalidate `['mealPlan', weekStart]` and toast `"Planned N meals (LLM picked N, library filled the rest)"`.
- [ ] **MODIFIED** `frontend/src/api/index.ts` — add `llm.plan(data)`.
- [ ] A11y: modal has `role="dialog"`, `aria-modal="true"`, focus trapped on the textarea on open, restored to the CTA button on close. Esc dismisses. Tab cycles within the modal.
### S13.3 — Verify
- [ ] `cd frontend && npm run build` → green (tsc 0 errors, vite 0 errors).
- [ ] Backend AST clean.
- [ ] Manual API smoke:
- `curl -X POST /api/llm/plan -d '{"prompt": "easy weeknight dinners, no fish", "week_start": "2026-06-08"}'` → 200 with `{plan_id, picked_count: 7+, filled_count: 14-, ...}`. (Requires `OLLAMA_API_KEY` set on the host.)
- With `OLLAMA_API_KEY` unset → 503.
- Empty prompt → 422.
- [ ] Manual UI smoke (3 steps):
1. Land on `/` with no plan. Click "Generate Meal Plan". Modal opens with the textarea + the two radio options.
2. Type "Italian-inspired, vegetarian" + select "Ask the LLM" + click submit. Confirm: button label flips to "Asking LLM…", modal shows a small spinner, ~5-15s later the modal closes, plan grid renders, toast shows the picked/filled split.
3. Refresh the page. Confirm the plan persists.
- [ ] No regression in Sprints 1-12.
### S13.4 — Docs (all 6 running docs updated)
- [ ] `Review/ui-nielsen-audit.md` — Sprint 13 status block.
- [ ] `fix-ui-audit.md` — Sprint 13 plan section (T7.1-T7.4).
- [ ] `Review/handoff-ui-audit.md` — Batch I + Sprint 13 entry + TL;DR row 13.
- [ ] `docs/HANDOFF.md` — Sprint 13 section.
- [ ] `.agent/plan.md` — this section.
- [ ] `.agent/context.md` — Sprint 13 decisions + file:line references.
- [ ] `Review/sprint13-verification.md` — written.
### Done when (Sprint 13)
- All boxes above ticked.
- `npm run build` green.
- `Review/sprint13-verification.md` exists.
- All 6 doc files have a Sprint 13 status block.
### Out of scope (Sprint 13)
- **F9-full — local Ollama model pull on the host.** Would require pulling Mistral 7B or Llama 3 8B (~4 GB) + a separate `ollama serve` process + a different config (`OLLAMA_BASE_URL=http://localhost:11434`). F9-lite uses the cloud tier. Future sprint if the cloud costs become painful.
- **Prompt engineering / quality iteration.** The prompt is a first cut. If the LLM returns 0 picks or 21 identical recipes, the operator can iterate on the prompt. Out of scope for the initial ship.
- **Multi-week plans.** One week at a time.
- **Save the prompt as a template** for reuse. Future sprint.