269 lines
9.5 KiB
Markdown
269 lines
9.5 KiB
Markdown
# 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
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```javascript
|
||
{ 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 (Monday–Sunday)
|
||
- 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.
|
||
|
||
```typescript
|
||
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):
|
||
|
||
```typescript
|
||
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:
|
||
|
||
```typescript
|
||
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 (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-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.
|