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.
This commit is contained in:
2026-05-04 19:27:22 -07:00
commit 0c5b0aa5ed
10 changed files with 2347 additions and 0 deletions
+23
View File
@@ -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
+145
View File
@@ -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
+76
View File
@@ -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 |
+404
View File
@@ -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
+256
View File
@@ -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)
+346
View File
@@ -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 <repository-url>
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
```
+177
View File
@@ -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
+283
View File
@@ -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
+542
View File
@@ -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
+95
View File
@@ -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