Phases 6-7
This commit is contained in:
parent
76a516a417
commit
029940b079
111 changed files with 17247 additions and 447 deletions
|
|
@ -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 (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,
|
||||
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 (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
|
||||
### 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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue