# 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 (per-member tokens) → Member clicks email link → lands on confirmation page → Member submits vote (POST, not GET) → Token marked USED, vote recorded → If majority approve → meal confirmed → If any deny → meal swapped with alternative → After deadline → Generate shopping list ``` **Email Security**: - Email links are GET to confirmation page (not direct approval) - Actual vote is a POST from the confirmation page - Tokens are single-use, expire after 72 hours - Per-member tokens (not shared) **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_ │ │ family_member │ │ recipe │ │ profile │ │ │ │ │ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │ id │◄────│ family_profile │ │ id │ │ name │ │ id │ │ name │ │ household_size │ │ name │ │ description │ │ adult_count │ │ email │ │ image_url │ │ child_count │ │ role │ │ ingredients │ │ dietary_notes │ │ likes_mushrooms │ │ (JSONB) │ │ budget_per_meal │ │ created_at │ │ instructions[] │ │ created_at │ └────────┬────────┘ │ cuisine_tags[] │ └────────┬────────┘ │ │ dietary_tags[] │ │ │ │ protein_type │ │ ▼ │ prep/cook_time │ │ ┌─────────────────┐ │ servings │ │ │ meal_plan_vote │ │ created_at │ │ ├─────────────────┤ └────────┬────────┘ │ │ id │ │ │ │ meal_plan_item │ │ │ │ family_member │ │ │ │ vote (bool) │ │ │ │ voted_at │ │ │ └─────────────────┘ │ │ ▲ │ │ │ ▼ │ ┌─────────────────┐ ┌─────────────────────┐ │ │ meal_plan_item │ │ ingredient │ │ ├─────────────────┤ ├─────────────────────┤ │ │ id │ │ id │ │ │ meal_plan_id │ │ name │ │ │ recipe_id │ │ name_lower (unique) │ └─────────────►│ day_of_week │ │ aisle │ │ meal_type │ │ typical_price │ │ approval_status│ │ unit │ │ denial_reason │ │ season_months[] │ │ estimated_cost │ └─────────────────────┘ └────────┬────────┘ ▲ │ │ ▼ │ ┌─────────────────┐ ┌───────┴─────────────┐ │ meal_plan │ │ grocery_item │ ├─────────────────┤ ├───────────────────┤ │ id │ │ id │ │ week_start_date │ │ ingredient_id (FK) │ │ status │ │ name │ │ total_cost │ │ current_price │ │ approval_deadline│ │ regular_price │ └─────────────────┘ │ is_on_sale │ │ sale_end_date │ │ scraped_at │ └───────────────────┘ ┌─────────────────┐ │ home_pantry │ ├─────────────────┤ │ id │ │ family_profile │ │ ingredient_id │ │ quantity │ │ unit │ │ expires_at │ │ added_at │ └─────────────────┘ ┌─────────────────┐ ┌─────────────────┐ │ feedback │ │ approval_token │ ├─────────────────┤ ├─────────────────┤ │ id │ │ id │ │ family_member │ │ meal_plan_item │ │ meal_plan_item │ │ family_member │ │ rating │ │ token │ │ never_suggest │ │ status │ │ denial_reason │ │ expires_at │ │ feedback_text │ │ used_at │ │ created_at │ └─────────────────┘ └─────────────────┘ ``` ### 3.2 Key Relationships - `family_profile` 1:N `family_member` - `family_profile` 1:N `meal_plan` - `family_profile` 1:N `home_pantry` - `family_member` 1:N `meal_plan_vote` (per-member voting) - `recipe` 1:N `meal_plan_item` - `meal_plan` 1:N `meal_plan_item` - `meal_plan` 1:N `meal_plan_vote` - `meal_plan_item` 1:N `meal_plan_vote` - `meal_plan_item` 1:N `approval_token` - `meal_plan_item` 1:1 `feedback` - `grocery_item` → `ingredient` (FK) - `ingredient` 1:N `home_pantry` - `ingredient` 1:N `grocery_item` --- ## 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 | | POST | `/api/ingredients` | Create or resolve ingredient (public, idempotent) | | 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