9.5 KiB
9.5 KiB
Phase 8 — Meal Planning & Waste Reduction
Goal: Enable weekly meal planning with automatic nutrition tracking vs targets. The core feature is a suggestion engine that recommends recipes prioritizing ingredients already in the pantry (especially those expiring soon), reducing food waste while maintaining nutritional balance.
Depends on: Phase 5 (products), Phase 6 (recipes), Phase 7 (pantry)
Deliverables
MealPlanandNutritionTargetMongoDB schemas- Meal plan CRUD API with daily/weekly views
- Recipe suggestion engine (algorithmic, not LLM-dependent)
- Nutrition targets per user with daily tracking
- Shopping gap analysis (what's needed beyond pantry)
- Meal plan web UI with weekly calendar and suggestion panel
Data Model
MealPlan Schema
// packages/shared/src/types/meal-plan.ts
export interface MealPlan {
id: string;
householdId: string;
weekStartDate: Date; // Monday of the planning week
days: MealPlanDay[];
status: MealPlanStatus;
shoppingListId?: string; // Auto-generated shopping list (Phase 9 link)
createdBy: string;
createdAt: Date;
updatedAt: Date;
}
export interface MealPlanDay {
date: Date;
meals: PlannedMeal[];
dailyNutritionTotal: NutritionInfo; // Computed
}
export interface PlannedMeal {
id: string; // UUID for drag-and-drop reference
type: MealType;
recipeId?: string; // Linked recipe
recipeName: string; // Denormalized
servings: number;
customName?: string; // For non-recipe meals
customNutrition?: NutritionInfo; // Manual override for non-recipe meals
perServingNutrition: NutritionInfo; // From recipe or custom
notes?: string;
}
export enum MealType {
BREAKFAST = 'breakfast',
LUNCH = 'lunch',
DINNER = 'dinner',
SNACK = 'snack',
}
export enum MealPlanStatus {
DRAFT = 'draft',
ACTIVE = 'active',
COMPLETED = 'completed',
}
NutritionTarget Schema
// packages/shared/src/types/nutrition-target.ts
export interface NutritionTarget {
id: string;
userId: string;
householdId: string;
dailyCalories: number;
proteinG: number;
carbsG: number;
fatG: number;
fiberG?: number;
sodiumMg?: number;
sugarG?: number;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
MongoDB Indexes
{ householdId: 1, weekStartDate: 1 } // unique per household per week
{ householdId: 1, status: 1 }
{ householdId: 1, 'days.meals.recipeId': 1 } // Find plans using a recipe
API Endpoints
MealPlanModule
| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /meal-plans |
List meal plans (paginated) | member |
| GET | /meal-plans/current |
Get current week's plan | member |
| GET | /meal-plans/:id |
Get specific plan | member |
| POST | /meal-plans |
Create new week plan | member |
| PATCH | /meal-plans/:id |
Update plan (add/move/remove meals) | member |
| DELETE | /meal-plans/:id |
Delete plan (draft only) | admin |
| POST | /meal-plans/:id/activate |
Set plan as active | member |
| GET | /meal-plans/:id/shopping-gap |
What's needed beyond current pantry | member |
| GET | /meal-plans/suggestions |
Get recipe suggestions for current pantry | member |
| POST | /meal-plans/suggest-with-llm |
LLM-powered meal plan generation (placeholder) | member |
NutritionTargetModule
| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /nutrition-targets |
Get current user's active target | member |
| POST | /nutrition-targets |
Set nutrition targets | member |
| PATCH | /nutrition-targets/:id |
Update targets | member |
Tasks
8.1 — Shared Types & Validation
- Add all types above to
packages/shared - Zod schemas for create/update operations
- Meal plan day validation (7 days per plan, valid dates)
8.2 — Meal Plan CRUD
- Standard CRUD with computed
dailyNutritionTotalper day:- Sum
perServingNutrition * servingsfor all meals in each day
- Sum
- On meal plan creation: default to 7 empty days (Monday–Sunday)
- On update: support granular operations:
addMeal(dayIndex, meal)removeMeal(dayIndex, mealId)moveMeal(fromDay, toDay, mealId)— for drag-and-dropupdateMeal(dayIndex, mealId, updates)
8.3 — Recipe Suggestion Engine (Core Algorithm)
This is the key differentiating feature — algorithmic, no LLM required.
class RecipeSuggestionService {
/**
* Score and rank recipes based on pantry state and user preferences.
*
* Input:
* - Current pantry items (with freshness urgency)
* - Recipe catalog for the household
* - User's nutrition targets (optional)
* - Already planned meals this week (to avoid repetition)
*
* Scoring per recipe:
* ingredientCoverageScore (0-40 pts): % of ingredients available in pantry
* freshnessUrgencyScore (0-30 pts): bonus for using urgent/use-soon items
* nutritionBalanceScore (0-20 pts): how well it complements the day's existing meals vs targets
* varietyScore (0-10 pts): penalty for recently planned recipes
*
* Output per suggestion:
* - recipe (id, name, perServingNutrition)
* - score (total)
* - availableIngredients[]: items from pantry that match
* - missingIngredients[]: items not in pantry (with estimated cost from Phase 9 if available)
* - urgentIngredients[]: pantry items with urgency=urgent that this recipe would use
* - reasoning: human-readable explanation of why this recipe was suggested
*/
suggestRecipes(context: SuggestionContext): Promise<RecipeSuggestion[]>;
}
Ingredient matching logic:
- Match recipe ingredient's
productIdagainst pantry items withstatus: sealed|opened - Check quantity: is there enough? (approximate — compare units, flag if unclear)
- Prefer items with higher freshness urgency
Scoring weights (configurable per household):
const DEFAULT_WEIGHTS = {
ingredientCoverage: 40,
freshnessUrgency: 30,
nutritionBalance: 20,
variety: 10,
};
8.4 — Shopping Gap Analysis
GET /meal-plans/:id/shopping-gap:- For each recipe in the meal plan, list required ingredients
- Cross-reference with current pantry (available quantity vs needed quantity)
- Return:
interface ShoppingGap {
coveredByPantry: ShoppingGapItem[]; // Already have enough
needToBuy: ShoppingGapItem[]; // Partially or fully missing
pantryItemsUsed: PantryItemUsage[]; // Which pantry items will be consumed
}
interface ShoppingGapItem {
productId: string;
productName: string;
totalNeeded: { quantity: number; unit: ServingUnit };
availableInPantry: { quantity: number; unit: ServingUnit };
shortfall: { quantity: number; unit: ServingUnit };
usedInRecipes: string[]; // Recipe names
}
- This output feeds directly into Phase 9's auto-generated shopping lists
8.5 — LLM Suggestion Placeholder
POST /meal-plans/suggest-with-llm:- Builds a context object: pantry summary, nutrition targets, dietary preferences
- Calls
ILlmProvider.suggestMealPlan(context) - With
NoOpLlmProvider: returns{ available: false } - When wired (Phase 10): returns a full week meal plan draft
8.6 — Web UI: Meal Planning
/meal-planspage:- Weekly calendar grid: 7 columns (Mon–Sun) × 4 rows (Breakfast, Lunch, Dinner, Snack)
- Each cell: drop zone for recipes, shows meal name + calorie badge
- Drag-and-drop: drag recipes from suggestion panel or between cells
- Daily nutrition summary row at bottom: calories, protein, carbs, fat bars
- Color-coded vs user's nutrition targets (under = blue, on-target = green, over = red)
- Week navigation: previous/next week arrows
- Suggestion panel (sidebar or drawer):
- "Suggestions based on your pantry" — ranked list from suggestion engine
- Each suggestion shows: recipe name, match score, "Uses: [urgent items]", "Need to buy: [missing items]"
- Click to expand: full ingredient match breakdown
- "Add to plan" button → pick day + meal type
- Shopping gap tab: shows what's needed beyond pantry, "Generate shopping list" button (Phase 9 integration)
/nutrition-targetssettings:- Daily macro targets form (calories, protein, carbs, fat)
- Preset templates: "Maintenance", "Weight loss", "Muscle gain", "Custom"
- Visual preview: donut chart of macro ratios
Acceptance Criteria
- Can create a weekly meal plan and add meals to specific days/slots
- Daily nutrition totals are computed and displayed
- Suggestion engine returns ranked recipes based on pantry state
- Suggestions prioritize recipes using soon-to-expire pantry items
- Shopping gap analysis correctly identifies missing ingredients
- Drag-and-drop works in the weekly calendar UI
- Nutrition targets can be set per user
- Daily nutrition bars show progress vs targets
- Variety scoring penalizes recently used recipes
Estimated Effort
Large. The suggestion engine scoring algorithm, shopping gap analysis, and calendar UI with drag-and-drop are all significant features.