# 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
