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

269 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (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.
```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 (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.