Public Access
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:
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user