bae94037f3b8af0e890960a6735c0604c21f0dea
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 — operator’s existing OLLAMA billing applies per call. Sprint 13 splits the Sprint 11 "Generate Meal Plan" CTA into a 2-step modal: "Use the recipe library" (default, Sprint 11’s existing flow) or "Ask the LLM" (new). The LLM path POSTs to /api/llm/plan with a free-text prompt; 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. Backend: - backend/app/api/llm_plan.py (NEW, ~280 lines). 1 endpoint (POST /api/llm/plan body {prompt, week_start}) + 4 helpers: - _ensure_ollama_configured — 503 on missing OLLAMA_API_KEY. - _serialize_library — reads up to 200 recipes for the family, sorted alphabetically. Cap prevents prompt-token overflow on kimi-k2. - _ask_llm — mirrors llm_matcher._ask_ollama (same URL, same headers, max_tokens=800, temperature=0, strips think blocks, 60s timeout). - _parse_picks — tolerant JSON parser. Handles markdown code fences, trailing commentary, and bare JSON. On failure returns []; the library fill takes over. - _validate_picks — drops invalid entries: missing fields, out-of-range day_of_week, unknown meal_type, unknown recipe_id. Returns a list of LLMPickedItem. Flow: rejects duplicate week (400) and empty library (400), builds the prompt, calls the LLM, validates picks, creates the plan, inserts the LLM-picked items, fills the rest from the library (Sprint 6+ pattern, re-implemented inline to avoid a self-HTTP-call), returns {plan_id, picked_count, filled_count, failed_count, reasoning}. - backend/app/schemas/__init__.py — added LLMPlanRequest + LLMPlanResponse. - backend/app/main.py:65-66 — registered llm_plan_api.router at the /api/llm prefix. No collision with the pre-existing WIP recipes.py. Frontend: - frontend/src/api/index.ts — added llm.plan(data) method. - frontend/src/pages/Dashboard.tsx — added the prompt modal (radio for library vs. LLM + textarea for the LLM path with 500-char counter) + new state (showPromptModal, promptMode, promptText, promptBusy) + extracted Sprint 11’s body into generateFromLibrary + added generateFromLLM. The modal is inline (not a separate component) because it depends on 4 local states + 3 handlers. Click-outside-to-dismiss is disabled while promptBusy is true. The textarea autoFocuses when LLM mode is selected. Added the Button import. LLM tolerance: a 60s timeout, parse-failure (markdown code fences, trailing commentary), or empty response all return 0 picks; the library fill takes over. The user never sees a crash — at worst, picked_count: 0 and the toast reads "Planned N meals (LLM picked 0, library filled the rest)". Verified: npm run build green (tsc 0 errors, vite 0 errors). Bundle: 500.28 → 503.82 kB (+3.5 kB). Backend AST clean on all 3 changed files. No new dependencies, no migration, no pre-existing WIP files touched. Deploy: git pull + docker compose up -d --build backend frontend (no migration, no new dependencies).
Meal Planner
Self-hosted meal planning system that integrates with Lucky California grocery store, sends weekly meal proposals via email to family members, generates shopping lists, and learns from feedback.
Background
This project was born out of frustration with meal kit services (Blue Apron → EveryPlate → HungryRoot → Sunbasket) that:
- Escalate costs to 3x ingredient markup
- Fall into repetitive meal rhythms
- Force users to log into apps to manage selections
- Don't integrate with home pantry items
Features
- Grocery Integration: Scrapes Lucky California weekly ads and sales
- Family Approval Workflow: Email proposals with approve/deny; one denial swaps the meal
- Shopping List Generation: Weekly list grouped by store aisles, highlighting sales, with interactive checkboxes to track purchased items
- Pantry Integration: Specify home items to incorporate into suggestions
- Web UI: Modern interface for the whole family
- Learning: Feedback-based meal recommendations, with weekly auto-discovery of new recipes from external APIs when family preferences are signaled
- Recipe Images: Scraped from public recipe sites, AI fallback available
- Unit Conversion: Converts recipe quantities (cups, tbsp, lb) to grocery units for accurate cost estimates
Architecture
- Backend: Python/FastAPI
- Database: PostgreSQL
- Frontend: React + Tailwind CSS
- Email: SendGrid
- Hosting: Docker Compose with nginx reverse proxy
Documentation
Quick Start
# Clone and start
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose down
Family Profile
Household: 2 adults, 2 children
- One adult likes mushrooms, one child OK with them
- Three family members do NOT like mushrooms
- No allergies
- Calorie, budget, and health conscious eating
Approval Workflow
- System generates 7-day meal plan based on sales, dietary constraints, budget, variety
- Email sent to both adults with meal previews
- One denial = meal swapped; no denials = auto-approved
- Shopping list generated after approval
Tech Stack
| Component | Technology |
|---|---|
| Backend | Python 3.11, FastAPI |
| Database | PostgreSQL 15 |
| Frontend | React 18, TypeScript, Tailwind |
| Scraping | Playwright, BeautifulSoup |
| SendGrid | |
| Hosting | Docker Compose, nginx |
Languages
Python
72.5%
TypeScript
25.9%
JavaScript
0.5%
PLpgSQL
0.4%
CSS
0.3%
Other
0.2%