From 0c5b0aa5ed022e25230b843b53f350eb5edf54f0 Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Mon, 4 May 2026 19:27:22 -0700 Subject: [PATCH] docs: add complete project documentation - SPEC.md: project specification and goals - ARCHITECTURE.md: system design and component descriptions - database-schema.md: PostgreSQL schema with all tables - implementation-plan.md: 12-phase implementation guide - RUNNING.md: deployment and troubleshooting guide - ORIENTATION.md: context compaction recovery guide - README.md: project overview and quick start Family profile: 2 adults, 2 children. Mushroom avoidance for 3/4. Approval workflow: email proposals, one denial swaps meal. Tech stack: FastAPI, PostgreSQL, React, Playwright, SendGrid. --- .env.example | 23 ++ .gitignore | 145 ++++++++++ README.md | 76 +++++ docs/ARCHITECTURE.md | 404 +++++++++++++++++++++++++++ docs/ORIENTATION.md | 256 +++++++++++++++++ docs/RUNNING.md | 346 +++++++++++++++++++++++ docs/SPEC.md | 177 ++++++++++++ docs/database-schema.md | 283 +++++++++++++++++++ docs/implementation-plan.md | 542 ++++++++++++++++++++++++++++++++++++ meal-planner-plan.md | 95 +++++++ 10 files changed, 2347 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/ORIENTATION.md create mode 100644 docs/RUNNING.md create mode 100644 docs/SPEC.md create mode 100644 docs/database-schema.md create mode 100644 docs/implementation-plan.md create mode 100644 meal-planner-plan.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..160dad7 --- /dev/null +++ b/.env.example @@ -0,0 +1,23 @@ +# Database +DATABASE_URL=postgresql://mealplanner:password@db:5432/mealplanner +POSTGRES_PASSWORD=secure_password_here + +# SendGrid +SENDGRID_API_KEY=SG.your_sendgrid_api_key + +# Family Emails +FAMILY_EMAIL_1=you@example.com +FAMILY_EMAIL_2=spouse@example.com +RECIPES_EMAIL=you@example.com + +# Lucky California +LUCKY_CA_URL=https://www.luckyncal.com + +# AI Image Generation (optional) +AI_IMAGE_ENABLED=false +AI_IMAGE_PROVIDER=openai +AI_IMAGE_API_KEY=sk-your-api-key + +# Application +LOG_LEVEL=INFO +SECRET_KEY=change-me-to-a-random-secret-key diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e01fa1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,145 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pip +pip-log.txt + +# poetry +poetry.lock + +# pdm +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582 +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Docker +docker-compose.override.yml + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Project specific +*.sql.bak +backups/ +nginx/ssl/*.pem diff --git a/README.md b/README.md new file mode 100644 index 0000000..c09996c --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# 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 +- **Pantry Integration**: Specify home items to incorporate into suggestions +- **Web UI**: Modern interface for the whole family +- **Learning**: Feedback-based meal recommendations +- **Recipe Images**: Scraped from public recipe sites, AI fallback available + +## Architecture + +- **Backend**: Python/FastAPI +- **Database**: PostgreSQL +- **Frontend**: React + Tailwind CSS +- **Email**: SendGrid +- **Hosting**: Docker Compose with nginx reverse proxy + +## Documentation + +- [Project Specification](docs/SPEC.md) +- [Architecture](docs/ARCHITECTURE.md) +- [Database Schema](docs/database-schema.md) +- [Implementation Plan](docs/implementation-plan.md) +- [Running Guide](docs/RUNNING.md) + +## Quick Start + +```bash +# 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 | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..0df3a96 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,404 @@ +# Meal Planner - Architecture + +## 1. System Overview + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ USER FACING │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ +│ │ Email │ │ Web UI │ │ Future: WhatsApp │ │ +│ │ (SendGrid) │ │ (React) │ │ (Twilio) │ │ +│ └──────┬───────┘ └──────┬───────┘ └───────────┬──────────────┘ │ +└─────────┼────────────────────┼───────────────────────┼─────────────────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ BACKEND (FastAPI) │ +│ │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │ +│ │ Scraper │ │ Recipe │ │ Meal │ │ Notification │ │ +│ │ Service │ │ Engine │ │ Planner │ │ Service │ │ +│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────────┬──────────┘ │ +│ │ │ │ │ │ +│ ┌─────┴───────────────┴───────────────┴────────────────────┴────────┐ │ +│ │ SERVICE LAYER │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │ +│ │ │ Grocery │ │ Recipe │ │ MealPlan │ │ Feedback │ │ │ +│ │ │ Service │ │ Service │ │ Service │ │ Service │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘ │ │ +│ └────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌────────────────────────────────────────────────────────────────────┐ │ +│ │ DATA LAYER (PostgreSQL) │ │ +│ └────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ EXTERNAL SERVICES │ +│ │ +│ ┌───────────────┐ ┌───────────────┐ ┌────────────────────────────┐ │ +│ │ Lucky │ │ Recipe Sites │ │ AI Image Service │ │ +│ │ California │ │ (scraping) │ │ (fallback only) │ │ +│ │ (scrape) │ │ │ │ │ │ +│ └───────────────┘ └───────────────┘ └────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. Component Descriptions + +### 2.1 Scraper Service +**Responsibility**: Fetch and parse data from Lucky California and public recipe sites + +**Modules**: +- `LuckyCaliforniaScraper`: Scrapes weekly ad, product catalog, sales +- `RecipeSiteScraper`: Scrapes recipe images and metadata from public sites + +**Technology**: Playwright + BeautifulSoup + +**Data Flow**: +``` +Lucky California website → Scraper → Parse → Store in PostgreSQL +Recipe sites → Scraper → Parse → Store as recipe.image_source +``` + +**Error Handling**: +- Retry with exponential backoff (3 attempts) +- Log failures for admin review +- Graceful degradation to manual input + +--- + +### 2.2 Recipe Engine +**Responsibility**: Store, tag, and retrieve recipes + +**Capabilities**: +- CRUD operations for recipes +- Tag-based filtering (cuisine, protein, dietary, season) +- Ingredient matching against home pantry +- Image URL storage (scraped primary, AI fallback) + +**Schema**: +- Recipe: id, name, description, ingredients[], instructions, image_url, cuisine_tags[], dietary_tags[], protein_type, prep_time, cook_time, servings, created_at +- Ingredient: id, name, aisle, typical_price_range, season_months[] + +--- + +### 2.3 Meal Planner Service +**Responsibility**: Generate weekly meal plans based on constraints + +**Algorithm Inputs**: +- Family profile (size, dietary constraints, preferences) +- Scraped grocery data (sales, in-season) +- Home pantry items +- Feedback history (avoid denied meals, prioritize liked ingredients) +- Variety constraints (max 2 meals with same sauce/protein) + +**Algorithm Output**: +- 7-day meal plan with one meal per day +- Each meal includes: recipe, adjusted ingredients (substituting sale items), estimated cost per serving + +**Learning Mechanism**: +- Weight denied meals at 0 (never re-suggest) +- Weight high-rated meals higher for future weeks +- Track ingredient frequency to enforce variety + +--- + +### 2.4 Notification Service +**Responsibility**: Send emails and manage approval workflow + +**Email Triggers**: +- Weekly meal proposal (to both adults) +- Reminder email (2 days before shipping deadline) +- Final meal plan confirmation +- Shopping list ready notification + +**Approval Flow**: +``` +Generate plan → Send proposal email + → Wait for responses (48h window) + → If deny → Swap meal with alternative + → If approve/no response → Confirm meal + → After all confirmations → Generate shopping list +``` + +**Email Template Data**: +- Meal name and day +- Meal image (URL) +- Ingredient list with pricing +- Approve/Deny links (tokenized URLs) +- Estimated total cost + +--- + +### 2.5 Feedback Service +**Responsibility**: Collect and process family feedback + +**Feedback Types**: +- **Rating**: 1-5 stars per meal +- **Denial Reason**: too_expensive, boring, disliked_ingredient, cultural, other +- **Never-Suggest Flag**: per ingredient or per recipe +- **Home Pantry Update**: items currently available + +**Learning Integration**: +- Feedback stored with timestamps +- Aggregated weekly to adjust meal planner weights +- "Never-suggest" ingredients filtered from all future proposals +- Denial reasons used to improve substitutions + +--- + +### 2.6 Web UI (React + Tailwind) +**Responsibility**: Interactive interface for family members + +**Pages**: +1. **Dashboard**: Current week's meal plan, approval status, shopping list summary +2. **Meal Detail**: Full recipe view, ingredients, cooking instructions, print button +3. **Approval Center**: Pending approvals with Approve/Deny actions +4. **Pantry Manager**: Add/remove home pantry items +5. **Feedback Portal**: Rate completed meals, flag ingredients +6. **Admin/Settings**: Family profile, scraper status, email history + +**Technology**: +- React 18+ with TypeScript +- Tailwind CSS for styling +- React Query for data fetching +- React Router for navigation + +--- + +## 3. Data Architecture + +### 3.1 PostgreSQL Schema Overview + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ family_ │ │ recipe │ │ meal_plan │ +│ profile │ │ │ │ │ +├─────────────────┤ ├─────────────────┤ ├─────────────────┤ +│ id │ │ id │ │ id │ +│ name │◄────│ family_profile │ │ week_start_date│ +│ household_size │ │ name │◄─┐ │ status │ +│ dietary_notes │ │ description │ │ │ created_at │ +│ preferences │ │ image_url │ │ └────────────────┘ +│ created_at │ │ prep_time │ │ │ +└─────────────────┘ │ cook_time │ │ │ + │ │ servings │ │ │ + │ │ cuisine_tags[] │ │ │ + │ │ dietary_tags[] │ │ │ + │ │ protein_type │ │ │ + │ │ created_at │ │ │ + │ └─────────────────┘ │ │ + │ │ │ │ + │ ▼ │ │ + │ ┌─────────────────┐ │ ┌─────────────────────┐ + │ │ recipe │ │ │ meal_plan_item │ + │ │ _ingredient │◄──┘ ├─────────────────────┤ + │ ├─────────────────┤ │ id │ + │ │ recipe_id │ │ meal_plan_id │ + │ │ ingredient_id │ │ recipe_id ────┘ + └──────────────►│ quantity │ │ day_of_week │ + │ unit │ │ approval_status │ + │ is_optional │ │ approval_token │ + └─────────────────┘ │ denial_reason │ + └─────────────────────┘ + +┌─────────────────┐ ┌─────────────────┐ +│ grocery │ │ home_ │ +│ item │ │ pantry │ +├─────────────────┤ ├─────────────────┤ +│ id │ │ id │ +│ name │ │ family_profile │ +│ aisle │ │ ingredient_id │ +│ current_price │ │ quantity │ +│ is_on_sale │ │ added_at │ +│ sale_end_date │ │ expires_at │ +│ season_months[] │ └─────────────────┘ +│ scraped_at │ +└─────────────────┘ + +┌─────────────────┐ ┌─────────────────┐ +│ feedback │ │ ingredient │ +├─────────────────┤ ├─────────────────┤ +│ id │ │ id │ +│ meal_plan_item │ │ name │ +│ rating │ │ aisle │ +│ never_suggest │ │ typical_price │ +│ denial_reason │ │ season_months[] │ +│ feedback_text │ │ created_at │ +│ created_at │ └─────────────────┘ +└─────────────────┘ +``` + +### 3.2 Key Relationships +- `family_profile` 1:N `meal_plan` +- `family_profile` 1:N `home_pantry` +- `recipe` N:N `ingredient` (via `recipe_ingredient`) +- `recipe` 1:N `meal_plan_item` +- `meal_plan` 1:N `meal_plan_item` +- `meal_plan_item` 1:1 `feedback` + +--- + +## 4. API Design + +### 4.1 Core Endpoints + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/profile` | Get family profile | +| PUT | `/api/profile` | Update family profile | +| GET | `/api/meals/planned` | Get current week's meal plan | +| GET | `/api/meals/{id}` | Get meal details with recipe | +| POST | `/api/meals/{id}/approve` | Approve a meal | +| POST | `/api/meals/{id}/deny` | Deny a meal with reason | +| GET | `/api/shopping-list` | Get current week's shopping list | +| GET | `/api/shopping-list/print` | Get printable shopping list | +| GET | `/api/pantry` | Get home pantry items | +| POST | `/api/pantry` | Add item to pantry | +| DELETE | `/api/pantry/{id}` | Remove item from pantry | +| POST | `/api/feedback` | Submit meal feedback | +| GET | `/api/recipes` | Search recipes | +| POST | `/api/admin/scrape` | Trigger grocery scrape (admin) | +| GET | `/api/admin/logs` | Get scraper logs | + +### 4.2 Email Approval Links +- `GET /api/approve/{token}` → Mark meal approved, redirect to confirmation page +- `GET /api/deny/{token}` → Show denial reason form +- `POST /api/deny/{token}` → Process denial with reason + +--- + +## 5. Deployment Architecture + +### 5.1 Docker Compose Services + +```yaml +services: + backend: + build: ./backend + ports: + - "8000:8000" + environment: + - DATABASE_URL=postgresql://user:pass@db:5432/mealplanner + - SENDGRID_API_KEY=${SENDGRID_API_KEY} + depends_on: + - db + restart: unless-stopped + + frontend: + build: ./frontend + ports: + - "3000:80" # nginx serves built React app + depends_on: + - backend + restart: unless-stopped + + db: + image: postgres:15-alpine + volumes: + - postgres_data:/var/lib/postgresql/data + environment: + - POSTGRES_USER=mealplanner + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=mealplanner + restart: unless-stopped + + nginx: + image: nginx:alpine + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + - ./ssl:/etc/nginx/ssl + depends_on: + - frontend + - backend + restart: unless-stopped + +volumes: + postgres_data: +``` + +### 5.2 Reverse Proxy (Nginx) + +Nginx handles: +- SSL termination for remote access +- Routing `/api/*` to backend +- Routing `/*` to frontend +- Rate limiting on approval endpoints + +### 5.3 Remote Access Security +- Caddy or Nginx with Let's Encrypt +- VPN option for additional security +- IP whitelist capability + +--- + +## 6. Image Strategy + +### 6.1 Primary: Scraped Images +- Recipe images scraped from public recipe sites +- Lucky California product images where available +- Stored as URLs (not downloaded), hotlink protection handled gracefully + +### 6.2 Fallback: AI Generation +- Triggered only when scraped image unavailable +- Uses configured AI service (DALL-E, Anthropic, etc.) +- Generated images cached in database +- Config flag to enable/disable + +### 6.3 Email Image Handling +- Images embedded via CDN or inline base64 +- Alt text provided for email clients that block images +- Link to web UI for full image gallery + +--- + +## 7. Error Handling Strategy + +### 7.1 Scraper Failures +1. Log error with full context +2. Retry 3 times with exponential backoff +3. If all fail, flag for manual review +4. Send admin notification after 3 failures +5. Graceful degradation: allow manual sale input + +### 7.2 Email Failures +1. Log send attempt +2. Retry via SendGrid's built-in retry +3. If permanently failed, mark meal as "pending email confirmation" +4. Web UI remains as fallback for approval + +### 7.3 Database Failures +1. Connection pooling with automatic reconnect +2. Read operations can fall back to cache if available +3. Write operations queued for retry + +--- + +## 8. Logging & Monitoring + +### 8.1 Log Categories +- `scraper`: All scraping operations and results +- `email`: SendGrid API calls, delivery status +- `meal_planner`: Plan generation inputs/outputs +- `api`: All HTTP requests +- `feedback`: Feedback submissions + +### 8.2 Log Storage +- JSON logs to stdout (Docker log driver) +- Centralized logging optional (Papertrail, Datadog) +- Log rotation: 7 days local + +### 8.3 Monitoring (Future) +- Uptime monitoring +- Scraping success rate +- Email delivery rate +- Approval response rate diff --git a/docs/ORIENTATION.md b/docs/ORIENTATION.md new file mode 100644 index 0000000..2fe8c58 --- /dev/null +++ b/docs/ORIENTATION.md @@ -0,0 +1,256 @@ +# Meal Planner - Orientation Guide + +This document is the **first stop** for any agent resuming work on this project after context compaction. Read it before doing anything else. + +--- + +## Project Overview + +**MealPlanner** is a self-hosted meal planning system for a family of 4 (2 adults, 2 children) that integrates with Lucky California grocery store to source ingredients from weekly sales, sends meal proposals via email, generates shopping lists, and learns from feedback. + +### Key Problem Being Solved + +The family has been using meal kit services (Blue Apron → EveryPlate → HungryRoot → Sunbasket) which suffer from: +- 3x ingredient markup in cost +- Repetitive meals and sauces +- Forcing app login to manage meal selection +- No home pantry integration + +### Current Status + +**Phase**: Documentation complete. Implementation NOT started. + +All planning documents are written and committed. The next step is Phase 1: Infrastructure & Foundation (setting up Docker, PostgreSQL, FastAPI skeleton, React frontend). + +--- + +## Key Files + +| File | Purpose | +|------|---------| +| `docs/SPEC.md` | Full project specification (goals, constraints, user stories) | +| `docs/ARCHITECTURE.md` | System architecture, component descriptions, data flow | +| `docs/database-schema.md` | PostgreSQL schema with all tables, indexes, relationships | +| `docs/implementation-plan.md` | 12-phase implementation plan with verification commands | +| `docs/RUNNING.md` | Deployment guide, troubleshooting, environment setup | +| `README.md` | Project overview and quick start | +| `meal-planner-plan.md` | Original planning file (may be superseded) | + +--- + +## Architecture Summary + +``` +User (email) ──► SendGrid ───────────────────────────────┐ +User (web) ───► React UI ──► nginx ──► FastAPI ──────────┼──► PostgreSQL + │ │ + └──► Lucky California scraper ──┘ +``` + +### Services (Docker Compose) +- **backend**: FastAPI Python app (port 8000) +- **frontend**: React + Tailwind (port 3000, served via nginx) +- **db**: PostgreSQL 15 (port 5432) +- **nginx**: Reverse proxy with SSL (ports 80/443) + +### Tech Stack +- Backend: Python 3.11, FastAPI, SQLAlchemy, Alembic +- Frontend: React 18, TypeScript, Tailwind CSS, React Query +- Database: PostgreSQL 15 +- Scraping: Playwright, BeautifulSoup +- Email: SendGrid +- Hosting: Docker Compose, nginx + +--- + +## Family Profile + +- **Household**: 2 adults, 2 children +- **Dietary**: One adult + one child like mushrooms; other adult + one child do NOT +- **Goals**: Calorie, budget, and health conscious; tasty but not expensive +- **No allergies** + +### Approval Workflow +1. System generates 7-day meal plan (Sunday) +2. Email sent to both adults with meals, images, Approve/Deny links +3. One denial → meal swapped with alternative +4. No denials (or silence) → meal auto-approved +5. After all approvals → shopping list generated + +--- + +## Database Schema Highlights + +### Core Tables +- `family_profile` - Household configuration +- `recipe` - All recipes with ingredients (JSONB), instructions, image URLs +- `ingredient` - Master ingredient list with aisle, price, season +- `meal_plan` - Weekly plan (7 days) +- `meal_plan_item` - Individual meal with approval_token, status +- `home_pantry` - Family's on-hand ingredients +- `feedback` - Ratings, denial reasons, never-suggest flags +- `grocery_item` - Scraped Lucky California items with sale prices +- `scrape_log` / `email_log` - Operation history + +### Key Relationships +- `family_profile` 1:N `meal_plan` +- `family_profile` 1:N `home_pantry` +- `recipe` N:N `ingredient` (via `recipe_ingredient` junction table) +- `meal_plan` 1:N `meal_plan_item` +- `meal_plan_item` 1:1 `feedback` + +--- + +## Implementation Phases + +| Phase | Description | Status | +|-------|-------------|--------| +| 1 | Infrastructure (Docker, PostgreSQL, FastAPI, React, nginx) | **Not Started** | +| 2 | Database & Models (SQLAlchemy models, Alembic migrations) | Not Started | +| 3 | API Endpoints (CRUD, meal plans, shopping list, feedback) | Not Started | +| 4 | Lucky California Scraper (weekly ad, Playwright) | Not Started | +| 5 | Recipe Engine (CRUD, tagging, search) | Not Started | +| 6 | Meal Planner Engine (generation algorithm, substitutions) | Not Started | +| 7 | SendGrid Email Integration | Not Started | +| 8 | Web UI - Core (Dashboard, Meal Detail, Approval, Pantry) | Not Started | +| 9 | Web UI - Feedback (Feedback Portal, Learning) | Not Started | +| 10 | Shopping List & Print | Not Started | +| 11 | Image Strategy (scraped + AI fallback) | Not Started | +| 12 | Polish & Future (variety analysis, budget tracking) | Not Started | + +--- + +## Environment Variables Required + +```bash +# Database +DATABASE_URL=postgresql://mealplanner:password@db:5432/mealplanner +POSTGRES_PASSWORD=secure_password + +# SendGrid +SENDGRID_API_KEY=SG.xxx + +# Family +FAMILY_EMAIL_1=user@example.com +FAMILY_EMAIL_2=spouse@example.com + +# Scraping +LUCKY_CA_URL=https://www.luckyncal.com + +# AI Images (optional) +AI_IMAGE_ENABLED=false +``` + +--- + +## Next Steps + +### IMMEDIATE: Start Phase 1 + +1. Create `docker-compose.yml` with all 4 services +2. Create backend directory structure and FastAPI skeleton +3. Create frontend with Vite + React + TypeScript + Tailwind +4. Set up nginx reverse proxy config +5. Create `.env.example` file + +### After Phase 1 Complete + +- Verify all services start: `docker-compose up -d && docker-compose ps` +- Test health endpoint: `curl localhost:8000/health` +- Test frontend builds: `cd frontend && npm run build` + +### Current Git State + +```bash +git log --oneline +# (empty - no commits yet) +``` + +All documentation is written but NOT YET COMMITTED. Commit with: +```bash +git add docs/ README.md meal-planner-plan.md +git commit -m "docs: add complete project documentation + +- SPEC.md: project specification and goals +- ARCHITECTURE.md: system design and component descriptions +- database-schema.md: PostgreSQL schema with all tables +- implementation-plan.md: 12-phase implementation guide +- RUNNING.md: deployment and troubleshooting guide +- README.md: project overview + +Family profile: 2 adults, 2 children. Mushroom avoidance for 3/4. +Approval workflow: email proposals, one denial swaps meal. +Tech stack: FastAPI, PostgreSQL, React, Playwright, SendGrid." +``` + +--- + +## Important Conventions + +### Code Style +- Python: Black formatter, isort for imports +- TypeScript: Prettier, ESLint +- No comments unless explaining non-obvious logic + +### Git Commits +- Conventional commits: `feat:`, `fix:`, `docs:`, `refactor:`, `test:` +- One logical change per commit where possible + +### API Design +- RESTful endpoints with proper HTTP methods and status codes +- Pydantic schemas for request/response validation +- JWT-free for MVP (simple token-based auth for email approval links) + +### Database +- Always use UUIDs for primary keys +- Timestamps with timezone (TIMESTAMPTZ) +- Soft deletes preferred over hard deletes where applicable + +### Testing +- Write unit tests for services (pytest) +- Integration tests for API endpoints +- Frontend: component tests with React Testing Library + +--- + +## Common Tasks + +### Run full stack +```bash +docker-compose up -d +``` + +### Create database migration +```bash +docker-compose exec backend alembic revision --autogenerate -m "description" +``` + +### Trigger scrape manually +```bash +curl -X POST http://localhost:8000/api/admin/scrape \ + -H "Content-Type: application/json" \ + -d '{"source": "lucky_california", "type": "weekly_ad"}' +``` + +### View all logs +```bash +docker-compose logs -f +``` + +--- + +## Known Issues / Open Questions + +1. **Lucky California scraping**: May need adjustment if website structure changes. Has fallback to manual input. +2. **AI image generation**: Config flag to enable/disable. Disabled by default. +3. **WhatsApp integration**: Planned for future via Twilio. Out of scope for MVP. + +--- + +## Contact / Context + +- **Primary user**: Peter (tech-savvy, hosts the system) +- **Wife**: Non-technical, will use web UI and email +- **Children**: 2, eating habits vary (one OK with mushrooms) + +Last updated: 2026-05-04 (after documentation completion) diff --git a/docs/RUNNING.md b/docs/RUNNING.md new file mode 100644 index 0000000..ddda99a --- /dev/null +++ b/docs/RUNNING.md @@ -0,0 +1,346 @@ +# Running the Meal Planner + +## Prerequisites + +- Docker and Docker Compose +- Git +- SendGrid account (for email) +- Lucky California store access (for scraping) + +## Environment Setup + +### 1. Clone Repository + +```bash +git clone +cd MealPlanner +``` + +### 2. Create Environment File + +```bash +cp .env.example .env +``` + +Edit `.env` with your values: + +```bash +# Database +POSTGRES_PASSWORD=your_secure_password + +# SendGrid +SENDGRID_API_KEY=SG.your_sendgrid_api_key + +# Family Emails +FAMILY_EMAIL_1=you@example.com +FAMILY_EMAIL_2=spouse@example.com + +# Lucky California (for scraping) +LUCKY_CA_URL=https://www.luckyncal.com + +# AI Images (optional) +AI_IMAGE_ENABLED=false +AI_IMAGE_PROVIDER=openai +AI_IMAGE_API_KEY=sk-your-key +``` + +### 3. Create SSL Certificates (for remote access) + +```bash +mkdir -p nginx/ssl +# Option 1: Let's Encrypt with Certbot +certbot certonly --nginx -d your-domain.com + +# Option 2: Self-signed for local testing +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout nginx/ssl/key.pem -out nginx/ssl/cert.pem +``` + +## Starting Services + +### Local Development + +```bash +# Start all services +docker-compose up -d + +# View logs +docker-compose logs -f backend +docker-compose logs -f frontend + +# Stop all services +docker-compose down +``` + +### Production Deployment + +```bash +# Start with production settings +docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d + +# Check service status +docker-compose ps + +# View resource usage +docker stats +``` + +## Accessing the Application + +### Local Access + +- **Web UI**: http://localhost:3000 +- **API**: http://localhost:8000 +- **API Docs**: http://localhost:8000/docs + +### Remote Access (with reverse proxy) + +Configure your domain and SSL in nginx/nginx.conf, then access via: +- **Web UI**: https://your-domain.com +- **API**: https://your-domain.com/api + +## Database Management + +### Initial Migration + +```bash +# Run migrations +docker-compose exec backend alembic upgrade head + +# Check current migration +docker-compose exec backend alembic current + +# Create new migration after model changes +docker-compose exec backend alembic revision --autogenerate -m "Description" +``` + +### Backup Database + +```bash +# Backup to file +docker-compose exec db pg_dump -U mealplanner mealplanner > backup_$(date +%Y%m%d).sql + +# Restore from backup +cat backup_20240101.sql | docker-compose exec -T db psql -U mealplanner mealplanner +``` + +### Reset Database + +```bash +# Danger: Drops and recreates all data +docker-compose down -v +docker-compose up -d +docker-compose exec backend alembic upgrade head +``` + +## Scraping + +### Manual Scrape Trigger + +```bash +# Scrape Lucky California weekly ad +curl -X POST http://localhost:8000/api/admin/scrape \ + -H "Content-Type: application/json" \ + -d '{"source": "lucky_california", "type": "weekly_ad"}' +``` + +### Check Scrape Logs + +```bash +# View recent scrape operations +curl http://localhost:8000/api/admin/logs?limit=10 +``` + +### Scheduling + +Scrape runs automatically: +- Weekly: Sunday at 8 PM (before meal planning) +- Daily: 6 AM (price updates) + +## Email Testing + +### Test Email Send + +```bash +# Send test email +curl -X POST http://localhost:8000/api/admin/test-email \ + -H "Content-Type: application/json" \ + -d '{"to": "test@example.com", "template": "meal_proposal"}' +``` + +### View Email Logs + +```bash +curl http://localhost:8000/api/admin/email-logs +``` + +## Troubleshooting + +### Backend Won't Start + +```bash +# Check logs +docker-compose logs backend + +# Common issues: +# - Database not ready: wait for db to be healthy +# - Port conflict: check if port 8000 is in use +# - Missing env vars: verify .env file exists and is valid +``` + +### Frontend Build Fails + +```bash +# Check for Node version issues +node --version # Should be 18+ + +# Clear cache and rebuild +docker-compose exec frontend npm cache clean --force +docker-compose exec frontend rm -rf node_modules package-lock.json +docker-compose exec frontend npm install +``` + +### Database Connection Errors + +```bash +# Verify database is running +docker-compose ps db + +# Test connection from backend +docker-compose exec backend python -c "from app.database import engine; print(engine.url)" + +# Check credentials +docker-compose exec backend python -c "from app.database import SessionLocal; print('OK')" +``` + +### Scraping Failures + +```bash +# Check Lucky California is accessible +curl -I https://www.luckyncal.com + +# Verify Playwright browser installed +docker-compose exec backend python -c "from playwright.sync_api import sync_playwright; print('OK')" + +# Manual retry +docker-compose exec backend python -c "from app.scraper.lucky_ca import LuckyCaliforniaScraper; s = LuckyCaliforniaScraper(); s.scrape_weekly_ad()" +``` + +### Email Not Sending + +```bash +# Verify SendGrid API key +docker-compose exec backend python -c "import sendgrid; print('SendGrid imported')" + +# Check SendGrid dashboard for failures +# Ensure sender email is verified in SendGrid +``` + +## Development + +### Backend Development + +```bash +# Enter backend container +docker-compose exec backend bash + +# Run tests +pytest + +# Run with hot reload +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +### Frontend Development + +```bash +# Enter frontend container +docker-compose exec frontend sh + +# Run dev server with hot reload +npm run dev +``` + +### Database Migrations + +```bash +# Create migration +alembic revision --autogenerate -m "add_new_table" + +# Upgrade +alembic upgrade head + +# Downgrade +alembic downgrade -1 + +# Show migration history +alembic history +``` + +## Health Checks + +```bash +# Check backend health +curl http://localhost:8000/health + +# Check database connectivity +curl http://localhost:8000/health/db + +# Check all services +docker-compose ps +``` + +## Logs + +### View All Logs + +```bash +docker-compose logs -f +``` + +### View Specific Service + +```bash +docker-compose logs -f backend +docker-compose logs -f frontend +docker-compose logs -f db +``` + +### Configure Log Level + +In `backend/app/config.py`: +```python +LOG_LEVEL=DEBUG # DEBUG, INFO, WARNING, ERROR +``` + +## Security Notes + +- Change default passwords in `.env` +- Use strong SSL certificates for production +- Consider VPN for remote database access +- Regularly update Docker images +- Review nginx access logs for suspicious activity + +## Updating + +```bash +# Pull latest code +git pull + +# Rebuild images +docker-compose build + +# Run migrations +docker-compose exec backend alembic upgrade head + +# Restart services +docker-compose up -d +``` + +## Stopping Completely + +```bash +docker-compose down # Stop containers +docker-compose down -v # Stop and remove volumes (DELETES DATA) +docker-compose down --rmi all # Stop and remove images +``` diff --git a/docs/SPEC.md b/docs/SPEC.md new file mode 100644 index 0000000..f4b4d55 --- /dev/null +++ b/docs/SPEC.md @@ -0,0 +1,177 @@ +# Meal Planner - Project Specification + +## 1. Overview + +**Project Name**: MealPlanner +**Type**: Self-hosted meal planning system +**Core Functionality**: Weekly meal plan generation that integrates with Lucky California grocery sales, sends approval requests to family members via email, generates shopping lists, and learns from feedback. +**Target Users**: A family of 4 (2 adults, 2 children) seeking to optimize meal planning for cost, health, and variety. + +--- + +## 2. Problem Statement + +Current meal kit services (Blue Apron → EveryPlate → HungryRoot → Sunbasket) suffer from: +- Escalating costs (3x ingredient markup) +- Repetitive meals and sauces +- Forcing users to log into apps to manage meal selection +- No offline pantry integration + +**Desired Solution**: A self-hosted system that: +- Sources ingredients from local grocery store (Lucky California) sales and in-season products +- Proposes meals via email with approval workflow +- Generates actionable shopping lists +- Incorporates existing home pantry items +- Learns from family feedback over time + +--- + +## 3. Goals + +### Primary Goals +1. **Weekly Meal Planning**: Automatically generate a 7-day meal plan each week +2. **Grocery Integration**: Scrape Lucky California weekly ads and product catalog for sales/in-season items +3. **Family Approval Workflow**: Send email to both adults with meal proposal, image, and details; one denial swaps the meal +4. **Shopping List Generation**: Create weekly shopping list grouped by Lucky California aisles, highlighting sales +5. **Pantry Integration**: Allow users to specify items they have at home to incorporate into meal suggestions +6. **Web UI**: Modern interface for non-technical family members to interact with meals, feedback, and recipes +7. **Recipe & Image Generation**: Display meal images and printable recipes + +### Secondary Goals +1. **Feedback Learning**: System learns from "never suggest", ratings, and denial reasons +2. **Variety Analysis**: Detect when meals fall into similar rhythms (same sauces/ingredients) +3. **Budget Optimization**: Track and optimize meal costs per serving +4. **WhatsApp Integration**: Future expansion via Twilio (out of scope for initial build) + +--- + +## 4. Non-Goals (Out of Scope) + +- Actual online ordering/payment at Lucky California +- Native mobile apps (responsive web UI only) +- Recipe parsing from personal recipe collections (Paprika, etc.) +- AI image generation as primary source (scraped images first) +- Multi-language support +- Nutrition tracking beyond high-level calorie awareness + +--- + +## 5. User Stories + +### As a family member, I can: +- Receive weekly email with proposed meals for the week +- View meal details including: name, image, ingredients, cook time, servings +- Approve or deny a meal with optional feedback reason +- Access the web UI to adjust home pantry items +- View printable recipe cards +- Provide feedback on meals ("Loved it", "Too bland", "Never again") + +### As the system administrator, I can: +- View scraping logs and debug failed scrapes +- Adjust family profile settings (dietary restrictions, household size) +- See meal plan approval history +- Monitor system health via logs + +--- + +## 6. Family Profile + +### Household +- 2 adults, 2 children +- Calorie, budget, and health conscious eating +- Food should be tasty but not overly expensive + +### Dietary Constraints +- One adult likes mushrooms +- One child is OK with mushrooms +- Two adults and one child do NOT like mushrooms +- No allergies + +### Preference Signals +- "Never suggest this ingredient" flags +- Per-meal ratings (1-5 stars) +- Denial reasons (too expensive, looks boring, contains disliked ingredient, etc.) +- Home pantry items to incorporate + +--- + +## 7. Approval Workflow + +### Weekly Flow +1. System generates 7-day meal plan based on: + - Lucky California sales and in-season items + - Family dietary constraints (no mushrooms for 3/4 members) + - Budget constraints + - Variety requirements (avoid sauce/ingredient repetition) + - Home pantry items to use + +2. Email sent to both adults containing: + - All 7 meals listed with images + - Each meal has: Approve / Deny buttons (via email links or web UI) + - Denial requires a reason selection or free-text + +3. Approval handling: + - If both approve OR no response → meal confirmed + - If either denies → meal swapped with alternative suggestion + - Denied meals logged for learning + +4. After approval deadline: + - Final meal plan locked + - Shopping list generated + - Recipes made available in web UI + +--- + +## 8. Technical Constraints + +### Self-Hosting Requirements +- Must run on local infrastructure (homelab, NUC, Synology, etc.) +- Remote access via reverse proxy (Caddy or nginx) +- No external cloud services except SendGrid for email + +### Lucky California Integration +- Primary: Scrape weekly ad and product catalog +- Store scraped data locally +- Respect robots.txt and rate limiting +- Fallback: Manual sale input if scraping fails + +### Email +- SendGrid for transactional email +- HTML email templates with meal images +- Plain text fallback for email clients that block images + +--- + +## 9. Data Retention + +### Stored Data +- All recipes (scraped and manually added) +- Meal plans (weekly history) +- Approval/denial history with reasons +- Feedback (ratings, flags, pantry items) +- Scraped grocery data (weekly refresh) + +### Retention Period +- Meal plans: 12 weeks rolling +- Feedback: Indefinite (for learning) +- Scraped grocery data: 2 weeks (to compare week-over-week) + +--- + +## 10. Success Metrics + +1. **Adoption**: Family consistently uses the system weekly +2. **Meal variety**: No more than 2 meals/week share the same sauce or primary protein +3. **Cost efficiency**: Average cost per serving within 150% of equivalent grocery-store meal +4. **Approval rate**: >80% of proposed meals approved without changes +5. **Learning**: After 4 weeks, system should not propose previously denied meals + +--- + +## 11. Future Considerations + +- Twilio WhatsApp integration for wife who prefers messaging +- Direct Lucky California online ordering +- Meal kit comparison (show cost difference vs. meal kit services) +- Nutritional tracking (macros, sodium, etc.) +- Grocery price history and trend analysis diff --git a/docs/database-schema.md b/docs/database-schema.md new file mode 100644 index 0000000..bbeff00 --- /dev/null +++ b/docs/database-schema.md @@ -0,0 +1,283 @@ +# Meal Planner - Database Schema + +## 1. Schema Overview + +PostgreSQL 15+ with the following extensions: +- `uuid-ossp` for UUID generation +- `pg_trgm` for fuzzy text search (if needed) + +--- + +## 2. Tables + +### 2.1 `family_profile` + +Primary household configuration. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK, default uuid_generate_v4() | Primary key | +| name | VARCHAR(100) | NOT NULL | Household name | +| household_size | INTEGER | NOT NULL, CHECK (household_size > 0) | Number of people | +| adult_count | INTEGER | NOT NULL | Number of adults | +| child_count | INTEGER | NOT NULL | Number of children | +| dietary_notes | TEXT | | Free-text dietary notes | +| budget_per_meal | NUMERIC(10,2) | DEFAULT 50.00 | Budget target per meal (in dollars) | +| calorie_target | INTEGER | | Daily calorie target per adult | +| created_at | TIMESTAMPTZ | DEFAULT NOW() | | +| updated_at | TIMESTAMPTZ | DEFAULT NOW() | | + +### 2.2 `ingredient` + +Master list of all ingredients. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK | | +| name | VARCHAR(200) | NOT NULL, UNIQUE | Ingredient name | +| plural_name | VARCHAR(200) | | For shopping list grouping | +| aisle | VARCHAR(100) | | Lucky California aisle | +| typical_price | NUMERIC(10,2) | | Price per unit | +| unit | VARCHAR(50) | | e.g., "lb", "oz", "bunch" | +| season_months | INTEGER[] | | Array of month numbers 1-12 | +| created_at | TIMESTAMPTZ | DEFAULT NOW() | | + +### 2.3 `recipe` + +All recipes in the system. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK | | +| family_profile_id | UUID | FK → family_profile(id) | Optional, for family-specific recipes | +| name | VARCHAR(300) | NOT NULL | Recipe name | +| description | TEXT | | Short description | +| image_url | TEXT | | URL to recipe image | +| image_source | VARCHAR(50) | | 'scraped', 'ai_generated', 'manual' | +| prep_time_minutes | INTEGER | | Prep time | +| cook_time_minutes | INTEGER | | Cook time | +| total_time_minutes | INTEGER | | Computed: prep + cook | +| servings | INTEGER | NOT NULL | | +| servings_scaled | INTEGER | | For scaling recipes | +| cuisine_tags | VARCHAR(50)[] | | Array: 'italian', 'asian', etc. | +| dietary_tags | VARCHAR(50)[] | | Array: 'vegetarian', 'gluten_free', etc. | +| protein_type | VARCHAR(50) | | 'chicken', 'beef', 'vegetarian', 'seafood' | +| spice_level | INTEGER | CHECK (spice_level BETWEEN 1 AND 5) | 1=mild, 5=very spicy | +| ingredients | JSONB | NOT NULL | [{ingredient_id, quantity, unit, is_optional}] | +| instructions | TEXT[] | NOT NULL | Array of step strings | +| source_url | TEXT | | Original recipe URL if scraped | +| scraped_at | TIMESTAMPTZ | | When originally scraped | +| is_manually_added | BOOLEAN | DEFAULT FALSE | User-created vs scraped | +| created_at | TIMESTAMPTZ | DEFAULT NOW() | | +| updated_at | TIMESTAMPTZ | DEFAULT NOW() | | + +### 2.4 `meal_plan` + +A generated weekly meal plan. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK | | +| family_profile_id | UUID | FK → family_profile(id) | | +| week_start_date | DATE | NOT NULL | Monday of the week | +| status | VARCHAR(20) | NOT NULL, DEFAULT 'draft' | 'draft', 'pending_approval', 'approved', 'locked' | +| approval_deadline | TIMESTAMPTZ | | When approval period ends | +| total_estimated_cost | NUMERIC(10,2) | | Sum of all meal costs | +| notes | TEXT | | Admin notes | +| created_at | TIMESTAMPTZ | DEFAULT NOW() | | +| updated_at | TIMESTAMPTZ | DEFAULT NOW() | | + +**Unique constraint**: `(family_profile_id, week_start_date)` + +### 2.5 `meal_plan_item` + +Individual meal within a plan. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK | | +| meal_plan_id | UUID | FK → meal_plan(id) ON DELETE CASCADE | | +| recipe_id | UUID | FK → recipe(id) | | +| day_of_week | INTEGER | NOT NULL, CHECK (day_of_week BETWEEN 0 AND 6) | 0=Monday, 6=Sunday | +| meal_type | VARCHAR(20) | NOT NULL | 'breakfast', 'lunch', 'dinner' | +| approval_status | VARCHAR(20) | DEFAULT 'pending' | 'pending', 'approved', 'denied', 'swapped' | +| approval_token | UUID | UNIQUE, DEFAULT uuid_generate_v4() | Token for email approval links | +| approval_token_expires | TIMESTAMPTZ | | | +| denial_reason | VARCHAR(50) | | 'too_expensive', 'boring', 'disliked_ingredient', 'other' | +| denial_details | TEXT | | Free-text explanation | +| estimated_cost | NUMERIC(10,2) | | Per serving cost | +| used_pantry_items | UUID[] | | Home pantry items used | +| created_at | TIMESTAMPTZ | DEFAULT NOW() | | +| updated_at | TIMESTAMPTZ | DEFAULT NOW() | | + +**Unique constraint**: `(meal_plan_id, day_of_week, meal_type)` + +### 2.6 `home_pantry` + +Items the family has at home. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK | | +| family_profile_id | UUID | FK → family_profile(id) ON DELETE CASCADE | | +| ingredient_id | UUID | FK → ingredient(id) | | +| quantity | NUMERIC(10,2) | | How much on hand | +| unit | VARCHAR(50) | | e.g., "cans", "lb" | +| expires_at | DATE | | Perishable expiry date | +| added_at | TIMESTAMPTZ | DEFAULT NOW() | | +| created_at | TIMESTAMPTZ | DEFAULT NOW() | | + +**Unique constraint**: `(family_profile_id, ingredient_id)` + +### 2.7 `feedback` + +Meal feedback and ratings. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK | | +| family_profile_id | UUID | FK → family_profile(id) | | +| meal_plan_item_id | UUID | FK → meal_plan_item(id) ON DELETE CASCADE | | +| rating | INTEGER | CHECK (rating BETWEEN 1 AND 5) | 1-5 stars | +| never_suggest | BOOLEAN | DEFAULT FALSE | Add to "never suggest" list | +| denial_reason | VARCHAR(50) | | Same as meal_plan_item | +| feedback_text | TEXT | | Free-text feedback | +| created_at | TIMESTAMPTZ | DEFAULT NOW() | | + +**Unique constraint**: `(meal_plan_item_id)` — one feedback per meal + +### 2.8 `never_suggest` + +Global "never suggest" flags per family. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK | | +| family_profile_id | UUID | FK → family_profile(id) ON DELETE CASCADE | | +| ingredient_id | UUID | FK → ingredient(id) | Optionally block specific ingredients | +| recipe_id | UUID | FK → recipe(id) | Or block entire recipes | +| reason | VARCHAR(50) | | 'allergy', 'dislike', 'tried_too_much', 'other' | +| notes | TEXT | | | +| created_at | TIMESTAMPTZ | DEFAULT NOW() | | + +### 2.9 `grocery_item` + +Scraped items from Lucky California. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK | | +| name | VARCHAR(300) | NOT NULL | Product name | +| brand | VARCHAR(200) | | Brand name | +| current_price | NUMERIC(10,2) | | Current sale price | +| regular_price | NUMERIC(10,2) | | Normal price | +| unit | VARCHAR(50) | | Price unit | +| aisle | VARCHAR(100) | | Store aisle | +| image_url | TEXT | | Product image | +| product_url | TEXT | | Lucky California product page | +| is_on_sale | BOOLEAN | DEFAULT FALSE | Currently on sale | +| sale_start_date | DATE | | | +| sale_end_date | DATE | | | +| in_season | BOOLEAN | DEFAULT FALSE | Currently in season | +| scraped_at | TIMESTAMPTZ | DEFAULT NOW() | | +| scraped_url | TEXT | | Source URL | + +**Index**: `CREATE INDEX idx_grocery_item_is_on_sale ON grocery_item(is_on_sale) WHERE is_on_sale = TRUE` + +### 2.10 `scrape_log` + +Scraping operation history. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK | | +| source | VARCHAR(50) | NOT NULL | 'lucky_california', 'recipe_site' | +| scrape_type | VARCHAR(50) | NOT NULL | 'weekly_ad', 'product_catalog', 'recipe' | +| status | VARCHAR(20) | NOT NULL | 'started', 'success', 'failed' | +| items_scraped | INTEGER | DEFAULT 0 | | +| error_message | TEXT | | | +| started_at | TIMESTAMPTZ | DEFAULT NOW() | | +| completed_at | TIMESTAMPTZ | | | +| duration_seconds | INTEGER | | | + +### 2.11 `email_log` + +Email sending history. + +| Column | Type | Constraints | Description | +|--------|------|-------------|-------------| +| id | UUID | PK | | +| recipient_email | VARCHAR(300) | NOT NULL | | +| recipient_name | VARCHAR(200) | | | +| template | VARCHAR(100) | NOT NULL | 'meal_proposal', 'reminder', 'confirmation' | +| meal_plan_id | UUID | FK → meal_plan(id) | Related meal plan | +| meal_plan_item_id | UUID | FK → meal_plan_item(id) | Optional: specific meal | +| sendgrid_message_id | VARCHAR(100) | | SendGrid message ID | +| status | VARCHAR(20) | NOT NULL | 'sent', 'delivered', 'failed', 'bounced' | +| error_message | TEXT | | | +| created_at | TIMESTAMPTZ | DEFAULT NOW() | | +| delivered_at | TIMESTAMPTZ | | | + +--- + +## 3. Indexes + +### 3.1 Primary Indexes +All PKs have default B-tree indexes. + +### 3.2 Foreign Key Indexes +```sql +CREATE INDEX idx_recipe_family_profile ON recipe(family_profile_id); +CREATE INDEX idx_meal_plan_family_profile ON meal_plan(family_profile_id); +CREATE INDEX idx_meal_plan_item_meal_plan ON meal_plan_item(meal_plan_id); +CREATE INDEX idx_meal_plan_item_recipe ON meal_plan_item(recipe_id); +CREATE INDEX idx_home_pantry_family_profile ON home_pantry(family_profile_id); +CREATE INDEX idx_feedback_family_profile ON feedback(family_profile_id); +CREATE INDEX idx_feedback_meal_plan_item ON feedback(meal_plan_item_id); +CREATE INDEX idx_never_suggest_family_profile ON never_suggest(family_profile_id); +CREATE INDEX idx_grocery_item_sale ON grocery_item(is_on_sale) WHERE is_on_sale = TRUE; +``` + +### 3.3 Full-Text Search Indexes +```sql +CREATE INDEX idx_ingredient_name_fts ON ingredient USING gin(to_tsvector('english', name)); +CREATE INDEX idx_recipe_name_fts ON recipe USING gin(to_tsvector('english', name)); +``` + +--- + +## 4. Data Migration Strategy + +### 4.1 Initial Schema +Use SQLAlchemy or Alembic for schema management. + +### 4.2 Future Migrations +- Alembic for version-controlled migrations +- All migrations must be reversible + +### 4.3 Seed Data +- Basic ingredient list pre-populated +- Sample recipes for initial testing (5-10 meals) +- Default family profile template + +--- + +## 5. Data Retention + +| Data Type | Retention | Action After Expiry | +|----------|-----------|---------------------| +| Meal plans | 12 weeks | Archive to JSON, delete rows | +| Feedback | Indefinite | Keep for learning | +| Scraped grocery items | 2 weeks | Delete old items | +| Scrape logs | 30 days | Delete old logs | +| Email logs | 90 days | Delete old logs | +| Never-suggest | Indefinite | Keep | + +--- + +## 6. Row-Level Security (Future) + +If multi-family support is added: +- RLS on all tables +- Policies based on `family_profile_id` +- Backend enforces tenant isolation diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md new file mode 100644 index 0000000..e9061a2 --- /dev/null +++ b/docs/implementation-plan.md @@ -0,0 +1,542 @@ +# Meal Planner - Implementation Plan + +## Overview + +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. + +--- + +## Phase 1: Infrastructure & Foundation + +### 1.1 Project Setup +- [ ] Initialize Git repository with `.gitignore` (Python, Node, Docker) +- [ ] Create `docker-compose.yml` with: backend (FastAPI), frontend (React), PostgreSQL, nginx +- [ ] Create `backend/` directory structure +- [ ] Create `frontend/` directory structure +- [ ] Set up Alembic for database migrations +- [ ] Create initial database migration with full schema + +**Verify**: `docker-compose up -d` starts all services; `docker-compose ps` shows all running + +### 1.2 Backend Skeleton +- [ ] Create FastAPI app with directory structure: + ``` + backend/app/ + __init__.py + main.py + config.py + database.py + models/ + schemas/ + api/ + services/ + scraper/ + ``` +- [ ] Create SQLAlchemy models matching database schema +- [ ] Create Pydantic schemas for API requests/responses +- [ ] Set up database connection with session management +- [ ] Create health check endpoint `GET /health` + +**Verify**: `curl localhost:8000/health` returns `{"status": "ok"}` + +### 1.3 Frontend Skeleton +- [ ] Create React app with TypeScript (`npx create-react-app` or Vite) +- [ ] Install dependencies: Tailwind CSS, React Router, React Query, Axios +- [ ] Create basic directory structure: + ``` + frontend/src/ + components/ + pages/ + hooks/ + api/ + types/ + ``` +- [ ] Set up React Query provider and routing +- [ ] Create basic layout with navigation + +**Verify**: Frontend builds without errors; `docker-compose up` serves frontend on port 3000 + +### 1.4 Reverse Proxy (Nginx) +- [ ] Create `nginx.conf` with routing rules: + - `/api/*` → backend:8000 + - `/*` → frontend:80 + - SSL configuration for remote access +- [ ] Create Caddyfile alternative for easier Let's Encrypt setup + +**Verify**: Nginx starts without config errors; routing works correctly + +--- + +## Phase 2: Database & Models + +### 2.1 SQLAlchemy Models +- [ ] Create `backend/app/models/__init__.py` with all model imports +- [ ] Implement `FamilyProfile` model +- [ ] Implement `Ingredient` model +- [ ] Implement `Recipe` model with JSONB ingredients column +- [ ] Implement `MealPlan` model +- [ ] Implement `MealPlanItem` model +- [ ] Implement `HomePantry` model +- [ ] Implement `Feedback` model +- [ ] Implement `NeverSuggest` model +- [ ] Implement `GroceryItem` model +- [ ] Implement `ScrapeLog` model +- [ ] Implement `EmailLog` model + +### 2.2 Pydantic Schemas +- [ ] Create request/response schemas for each model +- [ ] Create nested schemas for related objects (e.g., `MealPlanWithItems`) +- [ ] Create pagination schemas for list endpoints + +### 2.3 Database Migrations +- [ ] Create Alembic configuration +- [ ] Create initial migration from SQLAlchemy models +- [ ] Add seed data migration (basic ingredients, sample recipes) + +**Verify**: `alembic upgrade head` runs without errors; tables created in PostgreSQL + +--- + +## Phase 3: API Endpoints + +### 3.1 Core CRUD Endpoints +- [ ] `GET/PUT /api/profile` - Family profile management +- [ ] `GET/POST /api/ingredients` - Ingredient list +- [ ] `GET/POST /api/recipes` - Recipe management +- [ ] `GET /api/recipes/{id}` - Single recipe with full details +- [ ] `GET /api/pantry` - Home pantry items +- [ ] `POST/DELETE /api/pantry/{id}` - Add/remove pantry items + +### 3.2 Meal Plan Endpoints +- [ ] `GET /api/meals/planned` - Current week's meal plan +- [ ] `POST /api/meals/{id}/approve` - Approve a meal +- [ ] `GET /api/deny/{token}` - Denial form (for email links) +- [ ] `POST /api/deny/{token}` - Process denial with reason + +### 3.3 Shopping List Endpoints +- [ ] `GET /api/shopping-list` - Current week's shopping list +- [ ] `GET /api/shopping-list/print` - Printable format (PDF-ready HTML) + +### 3.4 Feedback Endpoints +- [ ] `POST /api/feedback` - Submit feedback for a meal +- [ ] `GET /api/feedback` - View feedback history + +### 3.5 Admin Endpoints +- [ ] `POST /api/admin/scrape` - Trigger Lucky California scrape +- [ ] `GET /api/admin/logs` - View scrape and email logs + +**Verify**: All endpoints return expected status codes and data shapes + +--- + +## Phase 4: Lucky California Scraper + +### 4.1 Scraper Infrastructure +- [ ] Create `backend/app/scraper/base.py` with base scraper class +- [ ] Create `backend/app/scraper/playwright_setup.py` for browser automation +- [ ] Implement retry logic with exponential backoff +- [ ] Create logging for scrape operations +- [ ] Implement rate limiting (respect Lucky California's limits) + +### 4.2 Weekly Ad Scraper +- [ ] Scrape Lucky California homepage for weekly ad link +- [ ] Parse weekly ad page for sale items +- [ ] Extract: product name, sale price, regular price, image URL, product URL +- [ ] Map products to existing ingredients or create new ones +- [ ] Store in `grocery_item` table + +### 4.3 Product Catalog Scraper (Future) +- [ ] Scrape product search results for specific ingredients +- [ ] Parse product detail pages for pricing and availability +- [ ] Implement pagination handling + +### 4.4 Scrape Scheduling +- [ ] Create scheduler service (APScheduler or similar) +- [ ] Schedule weekly scrape (Sunday night before meal planning) +- [ ] Schedule ad-hoc scrape via admin endpoint + +### 4.5 Error Handling +- [ ] Log all scrape failures with full context +- [ ] Create alert if scrape fails 3 consecutive times +- [ ] Store failed URLs for manual review + +**Verify**: Scraper successfully pulls 10+ sale items from Lucky California website + +--- + +## Phase 5: Recipe Engine + +### 5.1 Recipe Database +- [ ] Create recipe CRUD service +- [ ] Implement tag-based filtering (cuisine, dietary, protein) +- [ ] Create search functionality (name, ingredients) +- [ ] Store recipe images (URL reference) + +### 5.2 Recipe Importing +- [ ] Create basic recipe input form in web UI +- [ ] Support manual recipe entry (name, ingredients, instructions) +- [ ] Support pasting recipe URL for future scraping + +### 5.3 Ingredient Mapping +- [ ] Create ingredient matching service +- [ ] Map recipe ingredients to `ingredient` table entries +- [ ] Handle unit conversions (cups → oz, etc.) + +**Verify**: Can add a recipe manually and retrieve it via API + +--- + +## Phase 6: Meal Planner Engine + +### 6.1 Plan Generation Algorithm +- [ ] Create `MealPlannerService` with generation logic +- [ ] Load constraints: family profile, dietary restrictions, budget +- [ ] Filter out `never_suggest` ingredients and recipes +- [ ] Prioritize ingredients from `home_pantry` +- [ ] Select meals based on variety (max 2 same protein/sauce per week) +- [ ] Incorporate sale items from `grocery_item` +- [ ] Generate 7-day plan (dinner only for MVP) + +### 6.2 Substitution Logic +- [ ] When meal denied, find similar substitute +- [ ] Criteria: same protein, similar cuisine, within budget +- [ ] Avoid previously denied meals + +### 6.3 Cost Calculation +- [ ] Calculate per-serving cost using grocery prices +- [ ] Sum total weekly meal cost +- [ ] Flag meals exceeding budget + +### 6.4 Plan Persistence +- [ ] Save generated plan as `draft` status +- [ ] Generate unique approval tokens per meal +- [ ] Set approval deadline (48h from email send) + +**Verify**: Plan generation produces valid 7-day plan respecting all constraints + +--- + +## Phase 7: SendGrid Email Integration + +### 7.1 SendGrid Setup +- [ ] Create SendGrid API key (if not already done) +- [ ] Create `backend/app/services/email_service.py` +- [ ] Implement email sending via SendGrid API +- [ ] Create HTML email templates + +### 7.2 Meal Proposal Email +- [ ] Create email template with: + - Week overview + - All 7 meals listed with images + - Each meal: name, day, image, ingredients list, cost estimate + - Approve/Deny links with tokens + - Deadline reminder +- [ ] Send to both adult email addresses + +### 7.3 Reminder Email +- [ ] Send reminder 2 days before deadline +- [ ] Include current approval status +- [ ] Include link to web UI for approval + +### 7.4 Confirmation Email +- [ ] Send after all meals approved +- [ ] Include link to shopping list +- [ ] Include link to recipes + +**Verify**: Emails sent and received; links work correctly + +--- + +## Phase 8: Web UI - Core Features + +### 8.1 Dashboard +- [ ] Create `Dashboard` page +- [ ] Show current week's meal plan +- [ ] Show approval status for each meal +- [ ] Quick action buttons (approve/deny) +- [ ] Show shopping list summary + +### 8.2 Meal Detail Page +- [ ] Create `MealDetail` page +- [ ] Show full recipe with ingredients and instructions +- [ ] Display meal image +- [ ] Show nutritional hints (calories, protein) +- [ ] Print recipe button + +### 8.3 Approval Center +- [ ] Create `ApprovalCenter` page +- [ ] List pending approvals +- [ ] Approve/Deny buttons with reason selection +- [ ] Show denial history and reasons + +### 8.4 Pantry Manager +- [ ] Create `PantryManager` page +- [ ] Add items with quantity and unit +- [ ] Remove items +- [ ] Set expiration dates for perishables + +**Verify**: All pages render correctly; actions update database + +--- + +## Phase 9: Web UI - Feedback & Learning + +### 9.1 Feedback Portal +- [ ] Create `FeedbackPortal` page +- [ ] Rate completed meals (1-5 stars) +- [ ] Select "Never suggest this" option +- [ ] Free-text feedback field + +### 9.2 Learning Integration +- [ ] Update meal planner weights based on feedback +- [ ] Block denied recipes from future proposals +- [ ] Block denied ingredients from future proposals + +### 9.3 Recipe Discovery +- [ ] Create `RecipeBrowser` page +- [ ] Filter by cuisine, protein, dietary tags +- [ ] Search by name or ingredient +- [ ] "Surprise me" random recipe button + +**Verify**: Feedback submitted and reflected in future meal plans + +--- + +## Phase 10: Shopping List & Print + +### 10.1 Shopping List Generation +- [ ] Aggregate all ingredients from week's meals +- [ ] Subtract home pantry items +- [ ] Group by Lucky California aisle +- [ ] Highlight sale items +- [ ] Show estimated total cost + +### 10.2 Printable Shopping List +- [ ] Create print-optimized CSS +- [ ] Generate clean HTML for printing +- [ ] Include checkbox squares for manual marking +- [ ] Show aisle location for each item + +### 10.3 Web UI Shopping List View +- [ ] Create `ShoppingList` page +- [ ] Show items grouped by aisle +- [ ] Show sale price vs regular price +- [ ] Show total estimated cost +- [ ] Print/export button + +**Verify**: Shopping list accuracy (matches meal plan ingredients minus pantry) + +--- + +## Phase 11: Image Strategy Implementation + +### 11.1 Scraped Images +- [ ] Store image URLs in `recipe.image_url` +- [ ] Implement graceful fallback if image unavailable +- [ ] Lazy-load images in web UI + +### 11.2 AI Image Generation (Fallback) +- [ ] Create AI image service abstraction +- [ ] Implement DALL-E or similar API integration +- [ ] Generate images only when scraped image unavailable +- [ ] Cache generated images in database +- [ ] Add config flag to enable/disable AI generation + +### 11.3 Email Image Handling +- [ ] Embed images via CDN URL or inline base64 +- [ ] Provide alt text for blocked images +- [ ] Include link to web UI for full image gallery + +**Verify**: Meals display appropriate images in both web UI and email + +--- + +## Phase 12: Polish & Future Features + +### 12.1 Variety Analysis +- [ ] Track sauce/ingredient usage per week +- [ ] Warn if meals are too similar +- [ ] Suggest diverse alternatives + +### 12.2 Budget Tracking +- [ ] Track actual spending vs estimated +- [ ] Show cost per serving over time +- [ ] Alert if significantly over budget + +### 12.3 WhatsApp Integration (Future) +- [ ] Set up Twilio WhatsApp Business API +- [ ] Create WhatsApp message templates +- [ ] Implement approval/denial via WhatsApp + +### 12.4 Recipe Scraping (Future) +- [ ] Implement recipe site scraper +- [ ] Parse recipe structured data (JSON-LD) +- [ ] Auto-import recipes from web + +--- + +## Verification Commands + +### Backend Tests +```bash +# Health check +curl localhost:8000/health + +# API tests +curl localhost:8000/api/profile +curl localhost:8000/api/recipes +curl localhost:8000/api/shopping-list +``` + +### Frontend Tests +```bash +# Build test +cd frontend && npm run build + +# Dev server +cd frontend && npm run dev +``` + +### Full Stack Tests +```bash +# Start all services +docker-compose up -d + +# Check all services running +docker-compose ps + +# View logs +docker-compose logs -f + +# Stop all services +docker-compose down +``` + +--- + +## File Structure + +``` +mealplanner/ +├── docker-compose.yml +├── docker-compose.override.yml +├── .env.example +├── .gitignore +├── README.md +├── RUNNING.md +├── docs/ +│ ├── SPEC.md +│ ├── ARCHITECTURE.md +│ ├── database-schema.md +│ └── implemenation-plan.md +├── nginx/ +│ ├── nginx.conf +│ └── ssl/ +├── backend/ +│ ├── Dockerfile +│ ├── requirements.txt +│ ├── alembic.ini +│ ├── alembic/ +│ │ ├── env.py +│ │ └── versions/ +│ └── app/ +│ ├── __init__.py +│ ├── main.py +│ ├── config.py +│ ├── database.py +│ ├── models/ +│ │ ├── __init__.py +│ │ └── [model files] +│ ├── schemas/ +│ │ ├── __init__.py +│ │ └── [schema files] +│ ├── api/ +│ │ ├── __init__.py +│ │ └── [endpoint files] +│ ├── services/ +│ │ ├── __init__.py +│ │ ├── email_service.py +│ │ ├── meal_planner_service.py +│ │ └── [other services] +│ └── scraper/ +│ ├── __init__.py +│ ├── base.py +│ ├── lucky_ca_scraper.py +│ └── [other scrapers] +└── frontend/ + ├── Dockerfile + ├── package.json + ├── vite.config.ts + ├── tailwind.config.js + └── src/ + ├── App.tsx + ├── main.tsx + ├── index.css + ├── components/ + ├── pages/ + ├── hooks/ + ├── api/ + └── types/ +``` + +--- + +## Dependencies + +### Backend (Python 3.11+) +- fastapi +- uvicorn +- sqlalchemy +- alembic +- psycopg2-binary +- pydantic +- sendgrid (sendgrid-python) +- playwright +- beautifulsoup4 +- apscheduler +- python-dotenv +- pytest (dev) + +### Frontend (Node 18+) +- react +- react-dom +- react-router-dom +- @tanstack/react-query +- axios +- tailwindcss +- postcss +- autoprefixer +- typescript +- vite +- @types/react +- @types/node + +--- + +## Environment Variables + +```bash +# Backend +DATABASE_URL=postgresql://mealplanner:password@db:5432/mealplanner +SENDGRID_API_KEY=SG.xxx +RECIPES_EMAIL=you@example.com +FAMILY_EMAIL_1=wife@example.com +FAMILY_EMAIL_2=you@example.com +AI_IMAGE_ENABLED=false +AI_IMAGE_PROVIDER=openai + +# Frontend +VITE_API_URL=http://localhost:8000 + +# Docker +POSTGRES_PASSWORD=secure_password_here +``` + +--- + +## Notes + +- All code commits should follow conventional commit format +- Test at each phase before proceeding to next +- Document any deviations from this plan +- Keep ORIENTATION.md updated as implementation progresses diff --git a/meal-planner-plan.md b/meal-planner-plan.md new file mode 100644 index 0000000..a9e1ae2 --- /dev/null +++ b/meal-planner-plan.md @@ -0,0 +1,95 @@ +# Meal Planner System + +## Goal +Self-hosted meal planning system that sources ingredients from Lucky California sales, generates weekly meal plans, sends approval requests to you and your wife via email, and creates actionable shopping lists. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ MealPlanner App │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Scraper │ │ Recipe │ │ Meal │ │ Notif. │ │ +│ │ Service │ │ Engine │ │ Planner │ │ Service │ │ +│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ +│ │ │ │ │ │ +│ ┌────┴─────────────┴─────────────┴─────────────┴────┐ │ +│ │ PostgreSQL Database │ │ +│ └───────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ + │ │ │ + ▼ ▼ ▼ + Lucky California SendGrid Email Web UI (React) + (scraper) / Twilio WA (local + reverse proxy) +``` + +## Tech Stack +- **Backend**: Python/FastAPI +- **Database**: PostgreSQL +- **Frontend**: React + Tailwind CSS +- **Scraping**: Playwright for Lucky California +- **Email**: SendGrid +- **Image Gen**: AI (on-demand fallback only) +- **Hosting**: Docker Compose + reverse proxy + +## Project Structure +``` +mealplanner/ +├── docker-compose.yml +├── backend/ +│ ├── app/ +│ │ ├── main.py +│ │ ├── scraper/ +│ │ │ ├── lucky_ca.py +│ │ │ └── recipe_scraper.py +│ │ ├── models/ +│ │ ├── api/ +│ │ └── services/ +│ └── requirements.txt +└── frontend/ + ├── src/ + └── package.json +``` + +## Tasks + +### Phase 1: Foundation +- [ ] Set up Docker Compose with PostgreSQL, backend, frontend services +- [ ] Create PostgreSQL schema (recipes, meal_plans, family_profiles, home_items, feedback) +- [ ] Build FastAPI skeleton with basic CRUD endpoints +- [ ] Set up reverse proxy (nginx or Caddy) for local + remote access + +### Phase 2: Scraping +- [ ] Build Lucky California scraper (weekly ad + product catalog) +- [ ] Scrape recipe images from public recipe sites (NYT, AllRecipes, etc.) as primary image source +- [ ] Store scraped data in PostgreSQL + +### Phase 3: Recipe & Meal Engine +- [ ] Recipe database with ingredient tags, dietary info, cuisine types +- [ ] Meal planner that selects meals based on: family profile (mushroom avoidance), budget, seasonal ingredients, variety +- [ ] "Never suggest" and rating feedback loop to train preferences +- [ ] Incorporate home pantry items as constraints + +### Phase 4: Notifications & Approval +- [ ] SendGrid email integration for weekly meal approval +- [ ] Approval workflow: meal proposed → email to both → one deny = swap meal +- [ ] Email contains: meal name, image, ingredients, cooking time +- [ ] Store approval/denial history for learning + +### Phase 5: Shopping List & UI +- [ ] Generate weekly shopping list grouped by Lucky California aisle/sales +- [ ] React web UI for family members to: view meals, approve/deny, adjust home pantry items, view recipes +- [ ] Print-friendly recipe view +- [ ] Feedback mechanism ("Never suggest this", "Loved it", etc.) + +### Phase 6: Polish +- [ ] AI image generation as fallback when scraped images unavailable +- [ ] Meal variety analysis (sauce/ingredient rhythm detection) +- [ ] Budget tracking and optimization + +## Done When +- [ ] Family receives email each week with proposed meals +- [ ] Shopping list reflects actual Lucky California sales and in-season items +- [ ] Home pantry items influence meal suggestions +- [ ] Web UI accessible to non-technical family members +- [ ] System learns from feedback over time