MeshiTrack/docs/phases/phase-8-meal-planning.md

9.5 KiB
Raw Blame History

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

  1. MealPlan and NutritionTarget MongoDB schemas
  2. Meal plan CRUD API with daily/weekly views
  3. Recipe suggestion engine (algorithmic, not LLM-dependent)
  4. Nutrition targets per user with daily tracking
  5. Shopping gap analysis (what's needed beyond pantry)
  6. 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 dailyNutritionTotal per day:
    • Sum perServingNutrition * servings for all meals in each day
  • On meal plan creation: default to 7 empty days (MondaySunday)
  • On update: support granular operations:
    • addMeal(dayIndex, meal)
    • removeMeal(dayIndex, mealId)
    • moveMeal(fromDay, toDay, mealId) — for drag-and-drop
    • updateMeal(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 productId against pantry items with status: 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-plans page:
    • Weekly calendar grid: 7 columns (MonSun) × 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-targets settings:
    • 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.