admin a3c89bf6a2 feat(backend): Sprint 15 — seed 50 recipes + fix Sprint 12 latent-bug (main.py mount order)
Two changes:

1. Sprint 12 latent-bug fix: backend/app/main.py mount order.
   The pre-existing WIP backend/app/api/recipes.py:212 registers
   GET /{recipe_id} (UUID-typed) under /api/recipes. Sprint 12's
   recipe_search_api.router also mounts under /api/recipes. FastAPI
   matches routes in registration order, so the WIP's /{recipe_id}
   was catching /api/recipes/search and treating 'search' as a UUID,
   returning 422. This was a latent bug: Sprint 12 hasn't been
   deployed yet so the user hasn't seen the failure, but the
   frontend's 'Search the web' feature would 422 on every query.

   Fix: moved the recipe_search_api.router import to line 39 (with
   the other api imports) and the include_router call to BEFORE
   recipes_api.public_router. 3-line comment explains the why.

   Verified live: GET /api/recipes/search?q=chicken+parmesan&limit=2
   returns 200 with 2 hits. The WIP's GET /api/recipes/{uuid} still
   works (it just no longer shadows the /search and /import routes).

2. Sprint 15 content op: scripts/seed_recipes.py (NEW, ~150 lines).
   User direction (2026-06-05): 'Lets build out recipes for the
   coming 4 weeks in advance. In order to do this, lets add more
   recipes to the list of available ones.'

   The script seeds family-friendly recipes from Spoonacular into
   the local library. 50 queries (5 cuisines x 10 each: Italian,
   Mexican, Asian, American, Mediterranean/Middle Eastern).

   For each query: hit Spoonacular's complexSearch directly (avoids
   the broken backend route and the backend's quota counter), take
   the top hit, POST to the local backend's /api/recipes/import
   (which does the 1-pt /information call + idempotent ingredient
   upserts + Recipe insert). Idempotent: 409 from the import
   endpoint is logged and skipped. 1.5 sec sleep between queries.
   Stops cleanly on Spoonacular 402 (quota exhausted).

   Result: 18 recipes imported today. Spoonacular's free tier is
   50 pts/day (not 150 as I assumed; the _DAILY_LIMIT=140 in
   recipe_search.py:48 should drop to 45 — follow-up ticket).
   At 28 queries the script hit the cap. Re-running tomorrow will
   yield ~30 more (after the 18 already imported count toward 50).

   DB went 31 -> 49 total recipes. 19 Spoonacular + 30 manual.
   LLM test (Sprint 13 endpoint, week 2026-07-06):
     {picked_count: 0, filled_count: 19, failed_count: 2}
   The library fill covered 19/21 slots. The LLM (kimi-k2.6:cloud)
   returned 0 picks. Sprint 13 tolerance worked as designed.

No pre-existing WIP files touched (recipes.py, schemas/recipe.py,
nginx.conf unchanged). Only main.py was reordered (one-line + 3-line
comment). scripts/seed_recipes.py is a new file in the existing
scripts/ directory.

Deploy: git pull + docker compose up -d --build backend frontend.
The 18 new recipes are already in the DB. Re-run the seed script
on later days for the remaining 32 (after the cap resets).
2026-06-06 14:11:58 -07:00

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

  1. System generates 7-day meal plan based on sales, dietary constraints, budget, variety
  2. Email sent to both adults with meal previews
  3. One denial = meal swapped; no denials = auto-approved
  4. 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
Email SendGrid
Hosting Docker Compose, nginx
S
Description
No description provided
Readme
46 MiB
Languages
Python 72.5%
TypeScript 25.9%
JavaScript 0.5%
PLpgSQL 0.4%
CSS 0.3%
Other 0.2%