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,23 @@
|
|||||||
|
# Database
|
||||||
|
DATABASE_URL=postgresql://mealplanner:password@db:5432/mealplanner
|
||||||
|
POSTGRES_PASSWORD=secure_password_here
|
||||||
|
|
||||||
|
# SendGrid
|
||||||
|
SENDGRID_API_KEY=SG.your_sendgrid_api_key
|
||||||
|
|
||||||
|
# Family Emails
|
||||||
|
FAMILY_EMAIL_1=you@example.com
|
||||||
|
FAMILY_EMAIL_2=spouse@example.com
|
||||||
|
RECIPES_EMAIL=you@example.com
|
||||||
|
|
||||||
|
# Lucky California
|
||||||
|
LUCKY_CA_URL=https://www.luckyncal.com
|
||||||
|
|
||||||
|
# AI Image Generation (optional)
|
||||||
|
AI_IMAGE_ENABLED=false
|
||||||
|
AI_IMAGE_PROVIDER=openai
|
||||||
|
AI_IMAGE_API_KEY=sk-your-api-key
|
||||||
|
|
||||||
|
# Application
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
SECRET_KEY=change-me-to-a-random-secret-key
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pip
|
||||||
|
pip-log.txt
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
.pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
|
# PEP 582
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker-compose.override.yml
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Project specific
|
||||||
|
*.sql.bak
|
||||||
|
backups/
|
||||||
|
nginx/ssl/*.pem
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# Meal Planner
|
||||||
|
|
||||||
|
Self-hosted meal planning system that integrates with Lucky California grocery store, sends weekly meal proposals via email to family members, generates shopping lists, and learns from feedback.
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
This project was born out of frustration with meal kit services (Blue Apron → EveryPlate → HungryRoot → Sunbasket) that:
|
||||||
|
- Escalate costs to 3x ingredient markup
|
||||||
|
- Fall into repetitive meal rhythms
|
||||||
|
- Force users to log into apps to manage selections
|
||||||
|
- Don't integrate with home pantry items
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Grocery Integration**: Scrapes Lucky California weekly ads and sales
|
||||||
|
- **Family Approval Workflow**: Email proposals with approve/deny; one denial swaps the meal
|
||||||
|
- **Shopping List Generation**: Weekly list grouped by store aisles, highlighting sales
|
||||||
|
- **Pantry Integration**: Specify home items to incorporate into suggestions
|
||||||
|
- **Web UI**: Modern interface for the whole family
|
||||||
|
- **Learning**: Feedback-based meal recommendations
|
||||||
|
- **Recipe Images**: Scraped from public recipe sites, AI fallback available
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- **Backend**: Python/FastAPI
|
||||||
|
- **Database**: PostgreSQL
|
||||||
|
- **Frontend**: React + Tailwind CSS
|
||||||
|
- **Email**: SendGrid
|
||||||
|
- **Hosting**: Docker Compose with nginx reverse proxy
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Project Specification](docs/SPEC.md)
|
||||||
|
- [Architecture](docs/ARCHITECTURE.md)
|
||||||
|
- [Database Schema](docs/database-schema.md)
|
||||||
|
- [Implementation Plan](docs/implementation-plan.md)
|
||||||
|
- [Running Guide](docs/RUNNING.md)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone and start
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Stop
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Family Profile
|
||||||
|
|
||||||
|
Household: 2 adults, 2 children
|
||||||
|
- One adult likes mushrooms, one child OK with them
|
||||||
|
- Three family members do NOT like mushrooms
|
||||||
|
- No allergies
|
||||||
|
- Calorie, budget, and health conscious eating
|
||||||
|
|
||||||
|
## Approval Workflow
|
||||||
|
|
||||||
|
1. System generates 7-day meal plan based on sales, dietary constraints, budget, variety
|
||||||
|
2. Email sent to both adults with meal previews
|
||||||
|
3. One denial = meal swapped; no denials = auto-approved
|
||||||
|
4. Shopping list generated after approval
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
| Component | Technology |
|
||||||
|
|-----------|------------|
|
||||||
|
| Backend | Python 3.11, FastAPI |
|
||||||
|
| Database | PostgreSQL 15 |
|
||||||
|
| Frontend | React 18, TypeScript, Tailwind |
|
||||||
|
| Scraping | Playwright, BeautifulSoup |
|
||||||
|
| Email | SendGrid |
|
||||||
|
| Hosting | Docker Compose, nginx |
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Meal Planner System
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Self-hosted meal planning system that sources ingredients from Lucky California sales, generates weekly meal plans, sends approval requests to you and your wife via email, and creates actionable shopping lists.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ MealPlanner App │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||||
|
│ │ Scraper │ │ Recipe │ │ Meal │ │ Notif. │ │
|
||||||
|
│ │ Service │ │ Engine │ │ Planner │ │ Service │ │
|
||||||
|
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ ┌────┴─────────────┴─────────────┴─────────────┴────┐ │
|
||||||
|
│ │ PostgreSQL Database │ │
|
||||||
|
│ └───────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
Lucky California SendGrid Email Web UI (React)
|
||||||
|
(scraper) / Twilio WA (local + reverse proxy)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
- **Backend**: Python/FastAPI
|
||||||
|
- **Database**: PostgreSQL
|
||||||
|
- **Frontend**: React + Tailwind CSS
|
||||||
|
- **Scraping**: Playwright for Lucky California
|
||||||
|
- **Email**: SendGrid
|
||||||
|
- **Image Gen**: AI (on-demand fallback only)
|
||||||
|
- **Hosting**: Docker Compose + reverse proxy
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
```
|
||||||
|
mealplanner/
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── backend/
|
||||||
|
│ ├── app/
|
||||||
|
│ │ ├── main.py
|
||||||
|
│ │ ├── scraper/
|
||||||
|
│ │ │ ├── lucky_ca.py
|
||||||
|
│ │ │ └── recipe_scraper.py
|
||||||
|
│ │ ├── models/
|
||||||
|
│ │ ├── api/
|
||||||
|
│ │ └── services/
|
||||||
|
│ └── requirements.txt
|
||||||
|
└── frontend/
|
||||||
|
├── src/
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
### Phase 1: Foundation
|
||||||
|
- [ ] Set up Docker Compose with PostgreSQL, backend, frontend services
|
||||||
|
- [ ] Create PostgreSQL schema (recipes, meal_plans, family_profiles, home_items, feedback)
|
||||||
|
- [ ] Build FastAPI skeleton with basic CRUD endpoints
|
||||||
|
- [ ] Set up reverse proxy (nginx or Caddy) for local + remote access
|
||||||
|
|
||||||
|
### Phase 2: Scraping
|
||||||
|
- [ ] Build Lucky California scraper (weekly ad + product catalog)
|
||||||
|
- [ ] Scrape recipe images from public recipe sites (NYT, AllRecipes, etc.) as primary image source
|
||||||
|
- [ ] Store scraped data in PostgreSQL
|
||||||
|
|
||||||
|
### Phase 3: Recipe & Meal Engine
|
||||||
|
- [ ] Recipe database with ingredient tags, dietary info, cuisine types
|
||||||
|
- [ ] Meal planner that selects meals based on: family profile (mushroom avoidance), budget, seasonal ingredients, variety
|
||||||
|
- [ ] "Never suggest" and rating feedback loop to train preferences
|
||||||
|
- [ ] Incorporate home pantry items as constraints
|
||||||
|
|
||||||
|
### Phase 4: Notifications & Approval
|
||||||
|
- [ ] SendGrid email integration for weekly meal approval
|
||||||
|
- [ ] Approval workflow: meal proposed → email to both → one deny = swap meal
|
||||||
|
- [ ] Email contains: meal name, image, ingredients, cooking time
|
||||||
|
- [ ] Store approval/denial history for learning
|
||||||
|
|
||||||
|
### Phase 5: Shopping List & UI
|
||||||
|
- [ ] Generate weekly shopping list grouped by Lucky California aisle/sales
|
||||||
|
- [ ] React web UI for family members to: view meals, approve/deny, adjust home pantry items, view recipes
|
||||||
|
- [ ] Print-friendly recipe view
|
||||||
|
- [ ] Feedback mechanism ("Never suggest this", "Loved it", etc.)
|
||||||
|
|
||||||
|
### Phase 6: Polish
|
||||||
|
- [ ] AI image generation as fallback when scraped images unavailable
|
||||||
|
- [ ] Meal variety analysis (sauce/ingredient rhythm detection)
|
||||||
|
- [ ] Budget tracking and optimization
|
||||||
|
|
||||||
|
## Done When
|
||||||
|
- [ ] Family receives email each week with proposed meals
|
||||||
|
- [ ] Shopping list reflects actual Lucky California sales and in-season items
|
||||||
|
- [ ] Home pantry items influence meal suggestions
|
||||||
|
- [ ] Web UI accessible to non-technical family members
|
||||||
|
- [ ] System learns from feedback over time
|
||||||
Reference in New Issue
Block a user