feat(meal-planner): weekly meal planning with bulk prep support #75

Open
pyr0ball wants to merge 0 commits from feature/meal-planner into main
Owner

Summary

  • Adds full weekly meal planner: create plans, assign recipes to day/meal-type slots, and review the week at a glance in a compact 7-column grid
  • Shopping list computed on-demand from assigned slots, diffed against current pantry inventory, with affiliate retailer links (Amazon, Instacart, Walmart, Target, Thrive Market, Misfits Market) for gap items
  • Bulk prep session: sequences batch cooking tasks by equipment priority (oven first), stores user edits to task durations, displays prep schedule without countdown timers (Kiwi no-panic UX policy)
  • LLM-assisted plan suggestions and cook-time estimation (BSL 1.1, Paid/BYOK) via cf-text on cloud (cf-orch, ~2GB VRAM) or LLMRouter (ollama/vllm) for local users
  • Basic meal planning moved to Free tier; config, LLM plan gen, and LLM timing estimation gated at Paid (BYOK-unlockable)

New files

  • app/db/migrations/022-025 — meal_plans, meal_plan_slots, prep_sessions, prep_tasks tables
  • app/models/schemas/meal_plan.py — Pydantic v2 request/response schemas
  • app/services/meal_plan/ — shopping_list, prep_scheduler, planner, affiliates (MIT); llm_timing, llm_planner, llm_router (BSL 1.1)
  • app/api/endpoints/meal_plans.py — 9 REST endpoints
  • frontend/src/stores/mealPlan.ts — Pinia store (immutable updates)
  • frontend/src/components/MealPlan*.vue, ShoppingListPanel.vue, PrepSessionView.vue — Vue 3 SPA

Test Plan

  • pytest tests/ -v — 101 tests, all passing
  • Create a plan for the current week (Free tier — dinner-only enforced)
  • Upgrade to Paid tier, create plan with multiple meal types
  • Assign a recipe to a slot, verify shopping list reflects pantry diff
  • Open prep session, edit a task duration, verify user_edited badge appears
  • Verify affiliate links use rel="noopener noreferrer sponsored"
  • Verify no countdown timers appear anywhere in the meal plan UI
  • Set CF_ORCH_URL and confirm LLM plan suggestions route through cf-text
  • Without CF_ORCH_URL, confirm LLM falls back to LLMRouter (local)
  • Confirm BYOK unlocks meal_plan_llm and meal_plan_llm_timing on Free tier
## Summary - Adds full weekly meal planner: create plans, assign recipes to day/meal-type slots, and review the week at a glance in a compact 7-column grid - Shopping list computed on-demand from assigned slots, diffed against current pantry inventory, with affiliate retailer links (Amazon, Instacart, Walmart, Target, Thrive Market, Misfits Market) for gap items - Bulk prep session: sequences batch cooking tasks by equipment priority (oven first), stores user edits to task durations, displays prep schedule without countdown timers (Kiwi no-panic UX policy) - LLM-assisted plan suggestions and cook-time estimation (BSL 1.1, Paid/BYOK) via `cf-text` on cloud (cf-orch, ~2GB VRAM) or LLMRouter (ollama/vllm) for local users - Basic meal planning moved to Free tier; config, LLM plan gen, and LLM timing estimation gated at Paid (BYOK-unlockable) ## New files - `app/db/migrations/022-025` — meal_plans, meal_plan_slots, prep_sessions, prep_tasks tables - `app/models/schemas/meal_plan.py` — Pydantic v2 request/response schemas - `app/services/meal_plan/` — shopping_list, prep_scheduler, planner, affiliates (MIT); llm_timing, llm_planner, llm_router (BSL 1.1) - `app/api/endpoints/meal_plans.py` — 9 REST endpoints - `frontend/src/stores/mealPlan.ts` — Pinia store (immutable updates) - `frontend/src/components/MealPlan*.vue`, `ShoppingListPanel.vue`, `PrepSessionView.vue` — Vue 3 SPA ## Test Plan - [ ] `pytest tests/ -v` — 101 tests, all passing - [ ] Create a plan for the current week (Free tier — dinner-only enforced) - [ ] Upgrade to Paid tier, create plan with multiple meal types - [ ] Assign a recipe to a slot, verify shopping list reflects pantry diff - [ ] Open prep session, edit a task duration, verify `user_edited` badge appears - [ ] Verify affiliate links use `rel="noopener noreferrer sponsored"` - [ ] Verify no countdown timers appear anywhere in the meal plan UI - [ ] Set `CF_ORCH_URL` and confirm LLM plan suggestions route through cf-text - [ ] Without `CF_ORCH_URL`, confirm LLM falls back to LLMRouter (local) - [ ] Confirm BYOK unlocks `meal_plan_llm` and `meal_plan_llm_timing` on Free tier
pyr0ball added 23 commits 2026-04-12 14:21:27 -07:00
refs kiwi#68
refs kiwi#68
refs kiwi#74
refs kiwi#68 kiwi#71
refs kiwi#68
- upsert_slot: raise 422 immediately if meal_type not in VALID_MEAL_TYPES
- update_prep_task: assert whitelist safety contract after dict comprehension
- CreatePlanRequest: week_start typed as date with must_be_monday validator; str() cast at call site
- PrepTask: frozen=True; build_prep_tasks rewired to use (priority, kwargs) tuples so frozen instances are built with correct sequence_order in one pass (no post-construction mutation)
- Move deferred import json to file-level in meal_plans.py
- Fix test dates: "2026-04-14" was a Tuesday; updated request bodies to "2026-04-13" (Monday)
closes kiwi#68, kiwi#71
- Add GET /{plan_id}/prep-session endpoint so frontend can retrieve existing sessions without creating
- Fix list_plans response_model from list[dict] to list[PlanSummary] with proper _plan_summary() mapping
- Replace assert in store.update_prep_task with ValueError (assert is stripped under python -O)
- Add day_of_week 0-6 validation to upsert_slot endpoint
- Remove MagicMock sqlite artifact files left by pytest (already in .gitignore)
refs kiwi#68
- update_prep_task: move whitelist guard above filter so invalid column
  check runs on raw kwargs (was dead code — set(filtered) - allowed is
  always empty); fixes latent SQL injection path for future callers
- main.py: move register_kiwi_programs() into lifespan context manager
  so it runs once at startup, not at module import time
- MealPlanView.vue: remove debug console.log stubs from onSlotClick and
  onAddMealType (follow-up issue handlers, not ready for production)
This pull request has changes conflicting with the target branch.
  • app/api/routes.py
  • app/db/store.py
  • app/tiers.py
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feature/meal-planner:feature/meal-planner
git checkout feature/meal-planner

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git checkout main
git merge --no-ff feature/meal-planner
git checkout feature/meal-planner
git rebase main
git checkout main
git merge --ff-only feature/meal-planner
git checkout feature/meal-planner
git rebase main
git checkout main
git merge --no-ff feature/meal-planner
git checkout main
git merge --squash feature/meal-planner
git checkout main
git merge --ff-only feature/meal-planner
git checkout main
git merge feature/meal-planner
git push origin main
Sign in to join this conversation.
No description provided.