All §1 consensus blockers and §2 high-risk gaps resolved: Schema fixes: - Remove RecipeIngredient join table, use JSONB for ingredients - Add family_member table for per-voter approval tracking - Add all ENUMs for status fields (no loose VARCHAR) - Add CHECK constraints (household_size, rating 1-5, day_of_week) - Add name_lower for case-insensitive ingredient matching - Add grocery_item → ingredient FK - Fix day_of_week to ISO-8601 (1=Monday, 7=Sunday) - Remove calorie_target (nutrition is non-goal) Approval flow redesign: - Email link → confirmation page (GET), not auto-approve - Actual vote is POST from confirmation page - Per-voter tokens (single-use, 72h TTL) - Record which member voted Auth model: - VPN-only for admin endpoints - Session-based for family web UI Docker hardening: - Remove direct port exposure for backend/frontend - nginx is sole entrypoint - Add docker-compose.dev.yml for local dev Skeleton fixes: - Add missing Pantry.tsx page - Add missing index.html (Vite entrypoint) - Add package-lock.json - Fix SQLAlchemy 2 text() for raw SQL - Remove create_all from startup (use migrations) - Configure Alembic properly Docs updates: - Update Lucky URL to luckysupermarkets.com - Add WCAG 2.1 AA accessibility target - Update family profile with correct mushroom preferences - Add external dependencies list to SPEC Verification: - docker compose config: PASS - docker compose build backend: PASS - docker compose build frontend: PASS - backend import: PASS - alembic context: PASS
8.2 KiB
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: Post-adversarial-review fixes applied. Ready for verification.
Adversarial Review Completed: 2026-05-04
- All consensus blockers (§1.1 - §1.8) addressed
- All high-risk gaps (§2.1 - §2.8) addressed
- Key fixes applied:
- Recipe-ingredient: JSONB only (removed join table)
- Family member: Added
family_membertable for per-voter tracking - Approval flow: Redesigned with confirmation page + POST + per-voter tokens + TTL
- Auth: VPN-only for admin endpoints, session-based for family web UI
- Schema: ENUMs, CHECKs, CITEXT for ingredients, ISO day_of_week (1=Mon)
- Lucky URL: Fixed to
https://luckysupermarkets.com - Docker: Hardened (no direct port exposure to backend/frontend)
- Alembic: Configured with migration policy
Architecture Summary
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)
- 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
- Frontend: React 18, TypeScript, Tailwind CSS, React Query, Vite
- Database: PostgreSQL 15 with ENUMs and CITEXT
- Scraping: Playwright, BeautifulSoup
- Email: SendGrid
- Hosting: Docker Compose, nginx
Family Profile
Household
- 2 adults, 2 children
- 3 of 4 members do NOT like mushrooms
- No allergies
- Goals: Calorie, budget, and health conscious; tasty but not expensive
Family Members
| Member | Role | Mushroom Preference |
|---|---|---|
| Adult 1 | Adult | Does NOT like mushrooms |
| Adult 2 | Adult | Likes mushrooms |
| Child 1 | Child | Does NOT like mushrooms |
| Child 2 | Child | OK with mushrooms |
Approval Workflow (REDESIGNED)
- System generates 7-day meal plan (Sunday)
- Email sent to all adults with meals, images, approval page links
- Email link → confirmation page (GET, not auto-approve)
- Adult clicks Approve/Deny → POST with reason
- Per-member token (single-use, 72h TTL)
- Majority approve → meal confirmed; any deny → swap
- After approval → shopping list generated
Database Schema Highlights
Core Tables
family_profile- Household configuration with household_size CHECKfamily_member- Individual family members with email, role, mushroom preferencerecipe- 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 statusmeal_plan_vote- Per-member votes (one vote per member per meal)approval_token- Single-use tokens with TTL and status trackinghome_pantry- Family's on-hand ingredientsfeedback- Ratings, denial reasons, never-suggest flagsgrocery_item- Scraped Lucky California items with FK to ingredientscrape_log/email_log- Operation history
Key Relationships
family_profile1:Nfamily_memberfamily_profile1:Nmeal_planfamily_member1:Nmeal_plan_vote(per-voter tracking)recipe1:Nmeal_plan_itemmeal_plan1:Nmeal_plan_itemmeal_plan_item1:Nmeal_plan_votemeal_plan_item1:Napproval_tokengrocery_item→ingredient(FK)
Implementation Phases
| Phase | Description | Status |
|---|---|---|
| 1 | Infrastructure (Docker, PostgreSQL, FastAPI, React, nginx) | Complete |
| 2 | Database & Models (SQLAlchemy models, Alembic migrations) | Post-review fixes applied |
| 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
# 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://luckysupermarkets.com
# AI Images (optional)
AI_IMAGE_ENABLED=false
# Auth
SECRET_KEY=change-me-in-production
Next Steps
IMMEDIATE: Verify skeleton with verification matrix
docker compose config
docker compose build backend
docker compose build frontend
docker compose up -d db
docker compose run --rm backend python -c "from app.main import app; print(app.title)"
docker compose run --rm backend alembic upgrade head
docker compose run --rm frontend npm run build
If any of these fail, fix before proceeding.
After Verification
- Phase 2: Implement real API endpoints (not placeholders)
- Phase 3: Connect database models to endpoints
- Phase 4: Spike Lucky California scrape (before committing to full schema)
Current Git State
git log --oneline
624b516 docs: update ORIENTATION.md for Phase 1 complete
1328ec3 feat: add Phase 1 infrastructure skeleton
0c5b0aa docs: add complete project documentation
Pending commit: All adversarial review fixes (models, schema, docker-compose, docs updates)
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
- Email approval links use GET → confirmation page → POST
- Per-voter tokens, not shared household tokens
Database
- Always use UUIDs for primary keys
- Timestamps with timezone (TIMESTAMPTZ)
- Use ENUMs for status fields (not loose VARCHAR)
- Use CITEXT or lowercase-on-write for name matching
Testing
- Write unit tests for services (pytest)
- Integration tests for API endpoints
- Frontend: component tests with React Testing Library
Known Issues / Open Questions
- Lucky California scraping: Feasibility not yet spiked. URL is
luckysupermarkets.com. - AI image generation: Config flag to enable/disable. Disabled by default.
- WhatsApp integration: Planned for future via Twilio. Out of scope for MVP.
- Planned scheduler: APScheduler with
--workers 1to avoid duplicate fires.
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 (post adversarial review fixes)