Phases 6-7
This commit is contained in:
parent
76a516a417
commit
029940b079
111 changed files with 17247 additions and 447 deletions
139
docs/PLAN.md
139
docs/PLAN.md
|
|
@ -26,33 +26,97 @@ MeshiTrack helps households manage medicines and food. Medicine tracking comes f
|
|||
| **Monorepo** | Turborepo 2 |
|
||||
| **Runtime** | Node.js 22+ (ESM-only) |
|
||||
|
||||
## Phase Completion Criteria (Mandatory)
|
||||
|
||||
Every phase MUST deliver ALL of the following before being considered complete. No phase may be marked done if any layer is missing.
|
||||
|
||||
### 1. Shared Package (`packages/shared`)
|
||||
|
||||
- [ ] Types defined in `src/types/`
|
||||
- [ ] Enums defined in `src/enums/` with unit tests
|
||||
- [ ] Zod validation schemas in `src/validation/` with unit tests
|
||||
- [ ] Barrel exports updated in `index.ts`
|
||||
- [ ] 100% line/function/statement coverage, 90% branch coverage
|
||||
|
||||
### 2. API Package (`packages/api`)
|
||||
|
||||
- [ ] Mongoose schema in `src/schemas/`
|
||||
- [ ] Repository with `.lean().exec()` on all reads
|
||||
- [ ] Service with business logic
|
||||
- [ ] Route plugin registered in `main.ts`
|
||||
- [ ] Unit tests for repository, service, and routes (100% coverage)
|
||||
- [ ] All queries scoped to `householdId`
|
||||
|
||||
### 3. Web Package (`packages/web`)
|
||||
|
||||
- [ ] API client service in `src/services/` with unit tests
|
||||
- [ ] Page(s) in `src/app/(dashboard)/` implementing the UI spec
|
||||
- [ ] Component tests (React Testing Library) for every page and interactive component
|
||||
- [ ] Server Components by default; `'use client'` only where interactivity is needed
|
||||
|
||||
### 4. CI Gate (must pass before phase sign-off)
|
||||
|
||||
```bash
|
||||
npm run build # All packages compile
|
||||
npm run test:cov # Coverage thresholds enforced (100/100/100/90)
|
||||
npm run lint # Zero lint errors
|
||||
```
|
||||
|
||||
### 5. Documentation
|
||||
|
||||
- [ ] Phase spec updated with `[x]` on completed tasks
|
||||
- [ ] Acceptance criteria verified
|
||||
|
||||
---
|
||||
|
||||
## Phase Overview
|
||||
|
||||
### Medicine Tracking (Phases 1-4)
|
||||
|
||||
| Phase | Name | Description | Doc |
|
||||
| ----- | ------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| 0 | Foundation & Infrastructure | Repo scaffolding, Docker, auth | [phase-0-foundation.md](phases/phase-0-foundation.md) |
|
||||
| 1 | Medicine Library | Searchable medicine catalog with dosage/form info | [phase-1-medicine-library.md](phases/phase-1-medicine-library.md) |
|
||||
| 2 | Medicine Cabinet | Inventory tracking with quantity, expiry, low-stock alerts | [phase-2-medicine-cabinet.md](phases/phase-2-medicine-cabinet.md) |
|
||||
| 3 | Regimens & Pill Organizer | Daily medication schedules, batch-dispense, burn rate | [phase-3-regimens-pill-organizer.md](phases/phase-3-regimens-pill-organizer.md) |
|
||||
| 4 | Pharmacies, Prices & Refills | Shared store infrastructure, price tracking, refill alerts | [phase-4-pharmacies-prices-refills.md](phases/phase-4-pharmacies-prices-refills.md) |
|
||||
| Phase | Name | Description | Status | Doc |
|
||||
| ----- | ------------------------------ | ---------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------- |
|
||||
| 0 | Foundation & Infrastructure | Repo scaffolding, Docker, auth | DONE | [phase-0-foundation.md](phases/phase-0-foundation.md) |
|
||||
| 1 | Medicine Library | Searchable medicine catalog with dosage/form info | DONE | [phase-1-medicine-library.md](phases/phase-1-medicine-library.md) |
|
||||
| 2 | Medicine Cabinet | Inventory tracking with quantity, expiry, low-stock alerts | DONE | [phase-2-medicine-cabinet.md](phases/phase-2-medicine-cabinet.md) |
|
||||
| 3 | Regimens & Pill Organizer | Daily medication schedules, batch-dispense, burn rate | DONE | [phase-3-regimens-pill-organizer.md](phases/phase-3-regimens-pill-organizer.md) |
|
||||
| 4 | Pharmacies, Prices & Refills | Shared store infrastructure, price tracking, refill alerts | DONE | [phase-4-pharmacies-prices-refills.md](phases/phase-4-pharmacies-prices-refills.md) |
|
||||
|
||||
### Food Tracking (Phases 5-9)
|
||||
|
||||
| Phase | Name | Description | Doc |
|
||||
| ----- | ------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| 5 | Product Library | Searchable food product catalog with nutrition data | [phase-5-product-library.md](phases/phase-5-product-library.md) |
|
||||
| 6 | Recipe Management | Recipe CRUD, nutrition auto-calculation, import | [phase-6-recipes.md](phases/phase-6-recipes.md) |
|
||||
| 7 | Pantry & Fridge Tracking | Track item lifecycle, freshness, spoilage estimation | [phase-7-pantry.md](phases/phase-7-pantry.md) |
|
||||
| 8 | Meal Planning & Waste Reduction| Suggest meals from pantry, nutrition targets, weekly planning | [phase-8-meal-planning.md](phases/phase-8-meal-planning.md) |
|
||||
| 9 | Grocery & Price Tracking | Shopping lists, price analytics, store comparison (reuses Phase 4 stores) | [phase-9-grocery.md](phases/phase-9-grocery.md) |
|
||||
| Phase | Name | Description | Status | Doc |
|
||||
| ----- | ------------------------------ | ---------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------- |
|
||||
| 5 | Product Library | Searchable food product catalog with nutrition data | DONE | [phase-5-product-library.md](phases/phase-5-product-library.md) |
|
||||
| 6 | Recipe Management | Recipe CRUD, nutrition auto-calculation, import | DONE | [phase-6-recipes.md](phases/phase-6-recipes.md) |
|
||||
| 7 | Pantry & Fridge Tracking | Track item lifecycle, freshness, spoilage estimation | DONE | [phase-7-pantry.md](phases/phase-7-pantry.md) |
|
||||
| 8 | Meal Planning & Waste Reduction| Suggest meals from pantry, nutrition targets, weekly planning | NOT STARTED | [phase-8-meal-planning.md](phases/phase-8-meal-planning.md) |
|
||||
| 9 | Grocery & Price Tracking | Shopping lists, price analytics, store comparison (reuses Phase 4 stores) | NOT STARTED | [phase-9-grocery.md](phases/phase-9-grocery.md) |
|
||||
|
||||
### Shared (Phase 10)
|
||||
|
||||
| Phase | Name | Description | Doc |
|
||||
| ----- | ------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| 10 | LLM Integration & Smart Features | Wire up LLM providers, enable smart features across both domains | [phase-10-llm.md](phases/phase-10-llm.md) |
|
||||
| Phase | Name | Description | Status | Doc |
|
||||
| ----- | ------------------------------ | ---------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------- |
|
||||
| 10 | LLM Integration & Smart Features | Wire up LLM providers, enable smart features across both domains | PARTIAL | [phase-10-llm.md](phases/phase-10-llm.md) |
|
||||
|
||||
---
|
||||
|
||||
## Current Status & Gaps
|
||||
|
||||
### Phase 8 — Meal Planning (Next Priority)
|
||||
|
||||
The foundation for food tracking (Phases 5-7) is fully implemented and integrated. The next major milestone is Phase 8, which introduces the meal planning calendar and the algorithmic suggestion engine.
|
||||
|
||||
**Immediate Goals:**
|
||||
- Implement `MealPlan` and `NutritionTarget` schemas.
|
||||
- Build the `SuggestionEngineService` for pantry-based recipe ranking.
|
||||
- Create the weekly calendar UI with drag-and-drop support.
|
||||
|
||||
### Phase 10 — LLM (PARTIAL)
|
||||
|
||||
The `ILlmProvider` interface and `NoOpLlmProvider` are implemented. Actual provider implementations and smart features are deferred.
|
||||
|
||||
### Lessons Learned
|
||||
|
||||
Previous phases (6 & 7) were completed without finishing Phase 5's full stack (API routes + web UI). This created a gap where the Product Library has no user-facing interface despite being consumed internally. **Going forward, every phase must deliver its complete vertical slice (shared -> API -> web -> tests) before the next phase starts.**
|
||||
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
|
|
@ -121,8 +185,39 @@ MeshiTrack/
|
|||
|
||||
## Verification Strategy
|
||||
|
||||
- **Per-phase**: each phase ends with a working `docker compose up` that demos the new feature
|
||||
- **Integration**: Postman/Bruno collection maintained alongside API development
|
||||
- **E2E smoke test (medicine)**: automated script that creates a user, adds medicines, stocks cabinet, creates regimen, fills organizer, checks refill alerts
|
||||
- **E2E smoke test (food)**: automated script that adds products, creates a recipe, stocks the pantry, generates a meal plan, and creates a shopping list
|
||||
- **Performance**: MongoDB indexes reviewed per phase; query profiling before phase sign-off
|
||||
### Per-Phase Gate (Blocking)
|
||||
|
||||
Every phase must pass these checks before sign-off:
|
||||
|
||||
1. **Build**: `npm run build` succeeds (all packages compile)
|
||||
2. **Coverage**: `npm run test:cov` passes with thresholds:
|
||||
- `packages/api`: 100% lines/functions/statements, 90% branches
|
||||
- `packages/shared`: 100% lines/functions/statements, 90% branches
|
||||
- `packages/web`: all component tests pass
|
||||
3. **Lint**: `npm run lint` reports zero errors
|
||||
4. **Web UI Exists**: every phase that adds API endpoints also adds corresponding web pages with component tests
|
||||
5. **Services Tested**: every `*.service.ts` in web has a corresponding `*.test.ts`
|
||||
|
||||
### Per-Phase Deliverable Checklist
|
||||
|
||||
For each phase, the task breakdown must follow this template order:
|
||||
|
||||
```
|
||||
X.1 — Shared Types & Validation (+ tests)
|
||||
X.2 — Mongoose Schema & Repository (+ tests)
|
||||
X.3 — Service (+ tests)
|
||||
X.4 — Routes (+ tests)
|
||||
X.5 — Web: API Client Service (+ tests)
|
||||
X.6 — Web: Pages & Components (+ component tests)
|
||||
X.7 — CI Verification (build + test:cov + lint)
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
|
||||
- Postman/Bruno collection maintained alongside API development
|
||||
- E2E smoke test (medicine): creates user, adds medicines, stocks cabinet, creates regimen, fills organizer, checks refill alerts
|
||||
- E2E smoke test (food): adds products, creates recipe, stocks pantry, generates meal plan, creates shopping list
|
||||
|
||||
### Performance
|
||||
|
||||
- MongoDB indexes reviewed per phase; query profiling before phase sign-off
|
||||
|
|
|
|||
|
|
@ -97,6 +97,19 @@ Separate each group with a blank line.
|
|||
| React hook | camelCase | `useProducts`, `usePantryItems` |
|
||||
| CSS class (Tailwind) | kebab-case | via Tailwind utilities |
|
||||
|
||||
## Implementation Workflow (Vertical Slice)
|
||||
|
||||
Every feature or phase implementation must follow this order to ensure consistency and type safety across the monorepo:
|
||||
|
||||
1. **Shared Layer (`packages/shared`)**: Define types, enums, and Zod schemas. Add unit tests.
|
||||
2. **Database Layer (`packages/api`)**: Create Mongoose schema and Repository. Use `.lean().exec()` on all reads. Add integration tests.
|
||||
3. **Service Layer (`packages/api`)**: Implement business logic using Awilix DI. Add unit tests.
|
||||
4. **Route Layer (`packages/api`)**: Create Fastify route plugin. Add route tests.
|
||||
5. **Web API Client (`packages/web`)**: Implement frontend service. Add unit tests.
|
||||
6. **Web UI (`packages/web`)**: Create Next.js pages/components (Server Components by default). Add component tests.
|
||||
|
||||
**Mandatory Verification**: Every task must end with `npm run build`, `npm run test:cov`, and `npm run lint` all passing with the defined coverage thresholds.
|
||||
|
||||
## Code Organization Rules
|
||||
|
||||
### API (NestJS)
|
||||
|
|
|
|||
|
|
@ -181,6 +181,8 @@ interface PaginatedResponse<T> {
|
|||
- `ImportProductsSchema` — array of `CreateProductSchema` (for JSON import)
|
||||
- All schemas imported from `'zod/v4'`; enums use `z.enum(Object.values(...))`.
|
||||
|
||||
**Status**: DONE (types, enums, validation schemas all exist with tests)
|
||||
|
||||
### 5.2 — Mongoose Schema & Repository
|
||||
|
||||
- `packages/api/src/modules/products/schemas/product.schema.ts` — Mongoose schema with `timestamps: true`, `deletedAt` index, partial unique index on `(householdId, barcode)`.
|
||||
|
|
@ -195,7 +197,35 @@ interface PaginatedResponse<T> {
|
|||
- `bulkCreate(householdId, items[])` — uses `insertMany` with `ordered: false`
|
||||
- All read queries use `.lean().exec()`.
|
||||
|
||||
### 5.3 — Barcode Lookup Service
|
||||
**Status**: DONE (repository exists with tests)
|
||||
|
||||
### 5.3 — Products Service
|
||||
|
||||
- `ProductsService` with business logic:
|
||||
- Dedup check on create (same name + brand within household)
|
||||
- Barcode collision check (409 ConflictError)
|
||||
- Soft-delete with `deletedAt` timestamp
|
||||
- Coordinate barcode lookup (call BarcodeService)
|
||||
- Coordinate import (validate + bulkCreate)
|
||||
- Smart-add (call LLM provider)
|
||||
|
||||
**Status**: NOT STARTED
|
||||
|
||||
### 5.4 — Products Routes
|
||||
|
||||
- Route plugin registered via `fp()`:
|
||||
- `GET /products` — list/search (paginated)
|
||||
- `GET /products/:id` — get single
|
||||
- `POST /products` — create
|
||||
- `PATCH /products/:id` — update
|
||||
- `DELETE /products/:id` — soft-delete (admin)
|
||||
- `GET /products/barcode/:code` — barcode lookup
|
||||
- `POST /products/import` — bulk import
|
||||
- `POST /products/smart-add` — LLM placeholder
|
||||
|
||||
**Status**: NOT STARTED
|
||||
|
||||
### 5.5 — Barcode Lookup Service
|
||||
|
||||
- `BarcodeService`:
|
||||
- First check local DB for matching barcode (per household)
|
||||
|
|
@ -208,7 +238,9 @@ interface PaginatedResponse<T> {
|
|||
- Failures (network, 404, malformed): return `{ found: false }`; do not throw
|
||||
- Outbound HTTP via `undici` with a 5s timeout and a configurable User-Agent (`MeshiTrack/<version> (+self-hosted)`)
|
||||
|
||||
### 5.4 — LLM Provider Interface
|
||||
**Status**: NOT STARTED
|
||||
|
||||
### 5.6 — LLM Provider Interface
|
||||
|
||||
- `packages/api/src/modules/llm/interfaces/llm-provider.interface.ts`:
|
||||
|
||||
|
|
@ -231,14 +263,18 @@ export const LLM_PROVIDER = Symbol('LLM_PROVIDER');
|
|||
- `NoOpLlmProvider`: implements interface, returns `null` for all methods, logs a warning
|
||||
- `LlmModule`: provides `LLM_PROVIDER` via factory, selectable by env var `LLM_PROVIDER_TYPE`
|
||||
|
||||
### 5.5 — Smart Add Endpoint
|
||||
**Status**: DONE (interface + NoOp provider exist with tests)
|
||||
|
||||
### 5.7 — Smart Add Endpoint
|
||||
|
||||
- `POST /products/smart-add` accepts `{ text?: string, image?: file }`
|
||||
- Calls `ILlmProvider.extractNutrition()`
|
||||
- If LLM returns data, pre-fill a product and return to client for review (not auto-saved)
|
||||
- If LLM unavailable (`NoOpLlmProvider`), return `{ available: false, message: 'LLM not configured' }`
|
||||
|
||||
### 5.6 — Import Endpoint
|
||||
**Status**: NOT STARTED (blocked on 5.3/5.4)
|
||||
|
||||
### 5.8 — Import Endpoint
|
||||
|
||||
- `POST /products/import` accepts multipart CSV or JSON file (max 5 MB, 5000 rows)
|
||||
- Validate each row against `CreateProductSchema`; reject rows with imperial `servingUnit` values with a clear error message
|
||||
|
|
@ -248,7 +284,16 @@ export const LLM_PROVIDER = Symbol('LLM_PROVIDER');
|
|||
- `tags` is a `;`-separated list
|
||||
- `servingUnit` ∈ `{g, ml, piece, slice}`
|
||||
|
||||
### 5.7 — Web UI: Product Library
|
||||
**Status**: NOT STARTED (blocked on 5.3/5.4)
|
||||
|
||||
### 5.9 — Web: API Client Service
|
||||
|
||||
- `packages/web/src/services/products.ts` — fetch functions for all product endpoints
|
||||
- Unit tests in `packages/web/src/services/__tests__/products.test.ts`
|
||||
|
||||
**Status**: NOT STARTED
|
||||
|
||||
### 5.10 — Web UI: Product Library
|
||||
|
||||
- `/products` page (Server Component for initial fetch; client island for filters):
|
||||
- Search bar with debounced full-text search (300ms)
|
||||
|
|
@ -263,6 +308,15 @@ export const LLM_PROVIDER = Symbol('LLM_PROVIDER');
|
|||
- Barcode field with "Lookup" button (calls `/products/barcode/:code`)
|
||||
- "Smart Add" tab (text input or image upload)
|
||||
- Import dialog: file upload with preview, row count, and error display
|
||||
- Component tests for page and interactive components
|
||||
|
||||
**Status**: NOT STARTED
|
||||
|
||||
### 5.11 — CI Verification
|
||||
|
||||
- `npm run build` passes
|
||||
- `npm run test:cov` passes (100% lines/functions/statements, 90% branches)
|
||||
- `npm run lint` passes
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -276,10 +330,12 @@ export const LLM_PROVIDER = Symbol('LLM_PROVIDER');
|
|||
- [ ] Barcode collisions within a household return 409
|
||||
- [ ] Bulk import processes a CSV with 100+ products and reports per-row errors
|
||||
- [ ] Web UI allows searching, filtering, adding, and editing products
|
||||
- [ ] Web UI has component tests for all pages and interactive components
|
||||
- [ ] `ILlmProvider` interface is defined and injectable
|
||||
- [ ] Smart Add endpoint returns graceful `{ available: false }` with the NoOp provider
|
||||
- [ ] All product queries are scoped to `householdId`
|
||||
- [ ] Unit + integration tests meet coverage targets (100% lines/functions/statements, 90% branches)
|
||||
- [ ] `npm run build`, `npm run test:cov`, and `npm run lint` all pass
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -315,7 +315,14 @@ interface StoreComparisonResult {
|
|||
- Match items to products (fuzzy), match store to stores
|
||||
- With `NoOpLlmProvider`: returns `{ available: false }`
|
||||
|
||||
### 9.10 — Web UI: Grocery Management
|
||||
### 9.10 — Web: API Client Services
|
||||
|
||||
- `packages/web/src/services/shopping-lists.ts` — fetch functions for shopping list endpoints
|
||||
- `packages/web/src/services/prices.ts` — fetch functions for price record endpoints
|
||||
- Unit tests in `packages/web/src/services/__tests__/shopping-lists.test.ts`
|
||||
- Unit tests in `packages/web/src/services/__tests__/prices.test.ts`
|
||||
|
||||
### 9.11 — Web UI: Grocery Management
|
||||
|
||||
- `/shopping-lists` page:
|
||||
- Active lists at top, completed/archived below
|
||||
|
|
@ -326,16 +333,23 @@ interface StoreComparisonResult {
|
|||
- Each item: checkbox, name, quantity, estimated price
|
||||
- Check off: expand to enter actual price (optional)
|
||||
- Real-time sync indicator ("2 members shopping")
|
||||
- "Done Shopping" button → prompts "Add items to pantry?"
|
||||
- "Done Shopping" button — prompts "Add items to pantry?"
|
||||
- `/stores` page:
|
||||
- Store list with CRUD
|
||||
- Store list with CRUD (already exists from Phase 4, extend if needed)
|
||||
- Per-store: total spent, last visit, product count
|
||||
- `/prices` page (analytics):
|
||||
- Product search → price history line chart (per store, color-coded)
|
||||
- Product search — price history line chart (per store, color-coded)
|
||||
- Store comparison table
|
||||
- Spending over time bar chart
|
||||
- Price alerts panel
|
||||
- **Shopping list widget on dashboard**: shows active lists with quick-check functionality
|
||||
- Component tests for all pages and interactive components
|
||||
|
||||
### 9.12 — CI Verification
|
||||
|
||||
- `npm run build` passes
|
||||
- `npm run test:cov` passes (100% lines/functions/statements, 90% branches)
|
||||
- `npm run lint` passes
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -349,6 +363,8 @@ interface StoreComparisonResult {
|
|||
- [ ] Price analytics show spending trends and alerts
|
||||
- [ ] Auto-generated lists from meal plans correctly reflect shopping gap
|
||||
- [ ] Receipt parsing endpoint delegates to LLM provider
|
||||
- [ ] Web UI has component tests for all pages and interactive components
|
||||
- [ ] `npm run build`, `npm run test:cov`, and `npm run lint` all pass
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue