# MealPlanner - Agent Handoff Document **Project**: MealPlanner - Self-hosted meal planning system **Last Updated**: 2026-05-04 **Last Agent**: OpenCode (MealPlanner session) --- ## Project Overview A self-hosted meal planning system for a family of 4 (2 adults, 2 children) that integrates with Lucky California grocery store. Key problem: replacing meal kit services (Blue Apron → EveryPlate → HungryRoot → Sunbasket) that suffer from 3x ingredient markup, repetitive meals, and no pantry integration. ### Core Workflow 1. System generates 7-day meal plan based on Lucky California sales, family dietary constraints, budget, and home pantry items 2. Email sent to adults with meal proposals and approval links 3. Adults click email link → confirmation page → POST vote (Approve/Deny) 4. Per-voter tokens (single-use, 72h TTL) 5. Majority approve → meal confirmed; any deny → swap 6. Shopping list generated after approval ### Family Constraints - 3 of 4 family members do NOT like mushrooms - No allergies - Budget-conscious but wants tasty food --- ## Architecture ``` User (email) ──► SendGrid ───────────────────────────────┐ User (web) ───► React UI ──► nginx ──► FastAPI ──────────┼──► PostgreSQL │ │ └──► Lucky CA scraper ──┘ (luckysupermarkets.com) ``` ### Services (Docker Compose) - **backend**: FastAPI Python app (port 8000, internal only via nginx) - **frontend**: React + Tailwind (port 3000, internal only via nginx) - **db**: PostgreSQL 15 (internal only) - **nginx**: Reverse proxy with SSL (ports 80/443) ### Tech Stack - Backend: Python 3.11, FastAPI, SQLAlchemy 2.0, Alembic, Playwright, BeautifulSoup - Frontend: React 18, TypeScript, Tailwind CSS, React Query, Vite - Database: PostgreSQL 15 with ENUMs and CITEXT - Email: SendGrid - Hosting: Docker Compose, nginx --- ## Current Git State ``` git log --oneline -10 9458edf docs: update ORIENTATION.md with Phase 3/7 progress and current git state 27bca0e docs: update ORIENTATION.md phase table 08e196b feat: implement frontend Web UI pages 933a0cc feat: implement Lucky California scraper with Playwright + BeautifulSoup c735d21 feat: implement Phase 2 - Alembic migrations, Pydantic schemas, and real API endpoints e8706d3 docs: final ORIENTATION update 624b516 docs: update ORIENTATION.md for Phase 1 complete 1328ec3 feat: add Phase 1 infrastructure skeleton 0c5b0aa docs: add complete project documentation ``` --- ## Implementation Phases | Phase | Description | Status | |-------|-------------|--------| | 1 | Infrastructure (Docker, PostgreSQL, FastAPI, React, nginx) | **Complete** | | 2 | Database & Models (Alembic migrations, Pydantic schemas, API endpoints) | **Complete** | | 3 | Lucky California Scraper (BeautifulSoup + Playwright, ScraperService) | **Complete** | | 4 | Recipe Engine (CRUD, tagging, search) | Not Started | | 5 | Meal Planner Engine (generation algorithm, substitutions) | Not Started | | 6 | SendGrid Email Integration (meal proposal emails) | Not Started | | 7 | Web UI - Core (Dashboard, Meal Detail, Pantry, Shopping List) | **Complete** | | 8 | Web UI - Feedback (Feedback Portal, Learning) | Not Started | | 9 | Meal Planner Generation Algorithm | Not Started | | 10 | Image Strategy (scraped + AI fallback) | Not Started | | 11 | Polish & Future (variety analysis, budget tracking) | Not Started | --- ## Key Files and Locations ### Backend Structure ``` backend/ ├── alembic/ │ ├── env.py # Alembic configuration │ └── versions/ │ ├── 0001_initial_migration.py # Full schema (enums, tables, indexes, constraints) │ └── 0002_seed_data.py # Basic ingredients (70+), family profile, family members ├── app/ │ ├── api/ │ │ ├── admin.py # /api/admin/* (scrape trigger, logs, stats) │ │ ├── meals.py # /api/meals/* (meal plans, voting, approval tokens) │ │ ├── pantry.py # /api/pantry/* (CRUD for home pantry) │ │ ├── profile.py # /api/profile/* (family profile, members) │ │ ├── recipes.py # /api/recipes/* (CRUD, ingredients, filtering) │ │ └── shopping_list.py # /api/shopping-list/* (aggregation, print HTML) │ ├── config.py # Pydantic Settings (reads from .env) │ ├── database.py # SQLAlchemy engine, SessionLocal, get_db │ ├── models/ │ │ └── __init__.py # All SQLAlchemy models (FamilyProfile, Recipe, MealPlan, etc.) │ ├── schemas/ │ │ └── __init__.py # All Pydantic schemas for API request/response │ ├── scraper/ │ │ ├── base.py # BaseScraper with rate limiting, retries, session management │ │ ├── lucky_ca_scraper.py # LuckyCaliforniaScraper with BeautifulSoup + Playwright │ │ └── __init__.py │ └── services/ │ └── scraper_service.py # ScraperService to save scraped items to grocery_item table ├── alembic.ini ├── Dockerfile └── requirements.txt ``` ### Frontend Structure ``` frontend/ ├── src/ │ ├── api/ │ │ └── index.ts # mealPlannerApi wrapper for all endpoints │ ├── pages/ │ │ ├── Dashboard.tsx # Weekly meal plan grid view │ │ ├── MealDetail.tsx # Recipe display with ingredients/instructions │ │ ├── Pantry.tsx # Add/remove pantry items │ │ └── ShoppingList.tsx # Grouped by aisle with sale highlighting │ ├── types/ │ │ └── index.ts # TypeScript interfaces for all models │ ├── App.tsx # React Router with /, /meals/:id, /pantry, /shopping-list │ └── vite-env.d.ts # Vite env types ├── Dockerfile ├── package.json └── vite.config.ts ``` ### Documentation ``` docs/ ├── ARCHITECTURE.md # System architecture diagram ├── database-schema.md # Complete PostgreSQL schema reference ├── implementation-plan.md # Detailed phase-by-phase plan ├── ORIENTATION.md # First-stop guide for new agents (READ THIS) ├── RUNNING.md # Deployment guide └── SPEC.md # Project specification ``` --- ## Database Schema Highlights ### Core Tables - `family_profile` - Household with CHECK(household_size > 0) - `family_member` - Individual members with email, role, mushroom preference - `recipe` - Recipes with JSONB ingredients (not join table) - `ingredient` - Master list with name_lower (CITEXT for case-insensitive matching) - `meal_plan` - Weekly plan with ISO day_of_week (1=Mon, 7=Sun) - `meal_plan_item` - Individual meal with approval_status enum - `meal_plan_vote` - Per-member votes (one vote per member per meal) - `approval_token` - Single-use tokens with TTL and status tracking - `home_pantry` - Family's on-hand ingredients - `feedback` - Ratings, denial reasons, never-suggest flags - `grocery_item` - Scraped Lucky CA items with FK to ingredient - `scrape_log` / `email_log` - Operation history ### Key Enums - `meal_plan_status_enum`: draft, pending_approval, approved, locked - `meal_plan_item_status_enum`: pending, approved, denied, swapped - `approval_token_status_enum`: active, used, expired - `denial_reason_enum`: too_expensive, boring, disliked_ingredient, cultural, other ### Approval Flow (REDESIGNED) 1. Email contains unique token per family member (not shared) 2. Token expires after 72 hours 3. Token can only be used once (marked USED after voting) 4. Email link → GET confirmation page (NOT auto-approve) 5. Vote submitted via POST --- ## Configuration ### Environment Variables (.env) ```bash DATABASE_URL=postgresql://mealplanner:password@db:5432/mealplanner SENDGRID_API_KEY=SG.xxx FAMILY_EMAIL_1=you@example.com FAMILY_EMAIL_2=spouse@example.com LUCKY_CA_URL=https://luckysupermarkets.com AI_IMAGE_ENABLED=false LOG_LEVEL=INFO SECRET_KEY=change-me-to-a-random-secret-key ``` ### Key URLs - Lucky California: https://luckysupermarkets.com (verified scrapeable) - robots.txt: Allows all --- ## Verification Commands ```bash # Docker build verification docker compose config docker compose build backend docker compose build frontend # Backend import test docker compose run --rm backend python -c "from app.main import app; print(app.title)" # Alembic migration test (requires running db) docker compose run --rm backend alembic upgrade head # Frontend build test docker compose run --rm frontend npm run build ``` --- ## Known Issues / Open Questions 1. **Lucky California scraping**: Site uses dynamic content (JS rendering). The scraper uses Playwright for browser automation, but actual scraping hasn't been tested with a live database yet. May need adjustment based on actual page structure. 2. **AI image generation**: Config flag `AI_IMAGE_ENABLED=false`. Not implemented - just a placeholder for future. 3. **WhatsApp integration**: Planned for future via Twilio. Out of scope for MVP. 4. **Planned scheduler**: APScheduler with `--workers 1` to avoid duplicate fires. Not yet implemented. 5. **Meal planner algorithm**: Not implemented. Need to create algorithm that: - Filters out never_suggest ingredients/recipes - Prioritizes home_pantry items - Incorporates sale items from grocery_item - Ensures variety (max 2 same protein/sauce per week) - Respects mushroom preference (3 of 4 don't like) 6. **SendGrid email integration**: Not implemented. Need: - Email templates for meal proposals - Approval email with per-voter tokens - Reminder emails before deadline - Confirmation emails after approval --- ## Next Steps (Priority Order) ### 1. Implement SendGrid Email Integration (Phase 6) - Create `backend/app/services/email_service.py` - Implement meal proposal email template - Connect to meal plan creation flow - Use approval tokens for email links ### 2. Implement Meal Planner Generation Algorithm (Phase 9) - Create `backend/app/services/meal_planner_service.py` - Load constraints (family profile, dietary, budget) - Filter never_suggest ingredients/recipes - Prioritize home_pantry items - Incorporate sale items - Ensure variety requirements ### 3. Implement Recipe Engine (Phase 4) - Recipe CRUD already exists but needs testing - Tag-based filtering implemented in API - Search functionality needed ### 4. Implement Web UI - Feedback (Phase 8) - Feedback Portal page - Rating submission (1-5 stars) - "Never suggest this" flag - Learning integration --- ## 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 ### API Design - RESTful endpoints with proper HTTP methods - Pydantic schemas for request/response validation - Email approval links use GET → confirmation page → POST - Per-voter tokens, not shared household tokens ### Database - UUIDs for primary keys - Timestamps with timezone (TIMESTAMPTZ) - ENUMs for status fields (not loose VARCHAR) - CITEXT or lowercase-on-write for name matching --- ## Contacts - **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) --- ## File: ORIENTATION.md **IMPORTANT**: Read `docs/ORIENTATION.md` first before doing anything else. It contains the authoritative project state, phase table, and next steps.