Phases 6-7

This commit is contained in:
Aerilyn Weber 2026-05-14 14:47:23 +09:00
parent 76a516a417
commit 029940b079
111 changed files with 17247 additions and 447 deletions

View file

@ -6,264 +6,99 @@
---
## Deliverables
## Implementation Workflow (Vertical Slice)
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
Following the project's standard workflow, Phase 8 will be implemented in the following order:
### 8.1 — Shared Layer: Types & Validation
- **Types**: Define `MealPlan` and `NutritionTarget` in `packages/shared/src/types/`.
- **Enums**: Define `MealType` and `MealPlanStatus` in `packages/shared/src/enums/`.
- **Validation**: Create Zod schemas in `packages/shared/src/validation/`:
- `CreateMealPlanSchema`: Ensures 7 days, valid `weekStartDate`.
- `UpdateMealPlanSchema`: Supports granular updates (add/move/remove meal).
- `NutritionTargetSchema`: Validates daily macros and calorie goals.
- **Unit Tests**: Test Zod schemas for all edge cases (e.g., negative calories, overlapping weeks).
### 8.2 — Database Layer: Repositories
- **MealPlanRepository**:
- `findByHousehold(householdId, weekStartDate)`: Unique constraint per household per week.
- `findCurrent(householdId)`: Returns the plan for the current calendar week.
- `updateMeal(id, dayIndex, mealId, data)`: Granular update for a specific meal slot.
- **NutritionTargetRepository**:
- `findByUser(userId, householdId)`: Get active targets for a specific user.
- `upsert(userId, householdId, data)`: Update or create targets.
- **Integration Tests**: Verify MongoDB unique indexes and household scoping.
### 8.3 — Service Layer: Suggestion Engine & Planning
- **MealPlanService**:
- Manage CRUD operations and status transitions (Draft -> Active).
- Compute `dailyNutritionTotal` dynamically using the `NutritionCalculatorService` from Phase 6.
- **NutritionTargetService**:
- Manage user-specific nutritional goals and presets (Maintenance, Loss, Gain).
- **SuggestionEngineService** (Core Algorithm):
- **Scoring Algorithm**:
- `IngredientCoverage` (40%): Matches recipe ingredients vs current pantry items.
- `FreshnessUrgency` (30%): Bonus for using items with `urgency: urgent` or `expiringSoon`.
- `NutritionBalance` (20%): Complements existing meals in the day vs user targets.
- `Variety` (10%): Penalizes recipes used in the last 14 days.
- Uses `RecipesService` to fetch catalog and `PantryService` to fetch current inventory.
- **ShoppingGapService**:
- Analyze meal plans vs pantry to identify missing ingredients for Phase 9 integration.
- **Unit Tests**: Test scoring weights and ingredient matching logic (100% coverage required).
### 8.4 — Route Layer: API Endpoints
- **MealPlanRoutes**:
- `GET /api/v1/households/:householdId/meal-plans`: List plans.
- `GET /api/v1/households/:householdId/meal-plans/current`: Get current week.
- `POST /api/v1/households/:householdId/meal-plans`: Create plan.
- `PATCH /api/v1/households/:householdId/meal-plans/:id`: Update structure.
- `GET /api/v1/households/:householdId/meal-plans/suggestions`: Get algorithmic suggestions.
- **NutritionTargetRoutes**:
- `GET /api/v1/households/:householdId/nutrition-targets`: Get user targets.
- `POST /api/v1/households/:householdId/nutrition-targets`: Set targets.
- **Integration Tests**: Verify all endpoints return correctly shaped responses (using `toProductResponse` style mappers).
### 8.5 — Web Layer: API Client & Components
- **API Client**: Implement `meal-plans.ts` and `nutrition-targets.ts` in `packages/web/src/services/`.
- **Hooks**: Create `useMealPlan` and `useSuggestions` hooks for state management.
- **Components**:
- `CalendarGrid`: Weekly view using CSS Grid and drag-and-drop.
- `NutritionProgress`: Daily progress bars vs targets.
- `SuggestionPanel`: Sidebar showing ranked recipes with "reasoning" tooltips.
- `ShoppingGapView`: Breakdown of missing items.
- **Component Tests**: Use React Testing Library to verify drag-and-drop and progress bar calculations.
### 8.6 — Verification Gate
- `npm run build`
- `npm run test:cov` (100% API/Shared, 90% Web thresholds)
- `npm run lint`
---
## Data Model
### MealPlan Schema
## Technical Details
### Scoring Algorithm Constants
```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,
const WEIGHTS = {
COVERAGE: 0.4, // Do I have the items?
URGENCY: 0.3, // Should I use these items now?
NUTRITION: 0.2, // Does it fit my macros?
VARIETY: 0.1 // Have I eaten this too much?
};
```
### 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
### Integration Points
- **PantryService**: Fetch items where `status` is `sealed` or `opened`.
- **RecipesService**: Fetch recipes and their computed nutrition/ingredients.
- **NutritionCalculator**: Reuse for summing daily plan totals.
---
## 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.
- [ ] Weekly meal plans can be created and managed via a calendar UI.
- [ ] Drag-and-drop allows moving recipes between days and meal types.
- [ ] The suggestion engine prioritizes recipes using urgent pantry items.
- [ ] Daily nutrition bars reflect progress against user-defined macro targets.
- [ ] Shopping gap analysis identifies missing ingredients for the planned week.
- [ ] Full unit and integration test coverage as per project standards.
- [ ] Build, Lint, and Coverage checks all pass.