MeshiTrack/docs/phases/phase-9-grocery.md

357 lines
12 KiB
Markdown

# Phase 9 — Grocery & Price Tracking
**Goal**: Track food shopping across stores, compare prices over time, optimize where to buy. Auto-generate shopping lists from meal plans (Phase 8) or manually. Close the loop: when items are purchased, add them to the pantry (Phase 7). Reuses Store infrastructure from Phase 4.
**Depends on**: Phase 0, Phase 4 (stores), Phase 5 (products), Phase 7 (pantry), Phase 8 (meal planning)
---
## Deliverables
1. `Store`, `PriceRecord`, `ShoppingList` MongoDB schemas
2. Shopping list CRUD with real-time sync (WebSocket)
3. Auto-generate shopping lists from meal plan gaps
4. Price entry and history tracking
5. Price analytics: cheapest store per product, per shopping list, trends
6. Shopping → Pantry flow (checked items → add to pantry)
7. Web UI: shopping lists, price history charts, store comparison
---
## Data Model
### Store Schema
```typescript
// packages/shared/src/types/store.ts
export interface Store {
id: string;
householdId: string;
name: string;
address?: string;
location?: {
lat: number;
lng: number;
};
url?: string;
notes?: string;
tags: string[]; // e.g., 'organic', 'bulk', 'discount'
isActive: boolean;
createdBy: string;
createdAt: Date;
updatedAt: Date;
}
```
### PriceRecord Schema
```typescript
// packages/shared/src/types/price.ts
export interface PriceRecord {
id: string;
householdId: string;
productId: string;
productName: string; // Denormalized
storeId: string;
storeName: string; // Denormalized
price: number;
currency: string; // Default from household settings
quantity: number; // How much for this price
unit: ServingUnit;
pricePerUnit: number; // Computed: price / quantity (normalized)
date: Date;
receiptImageUrl?: string;
notes?: string;
createdBy: string;
createdAt: Date;
}
```
### ShoppingList Schema
```typescript
// packages/shared/src/types/shopping-list.ts
export interface ShoppingList {
id: string;
householdId: string;
name: string;
items: ShoppingItem[];
status: ShoppingListStatus;
createdFrom?: ShoppingListSource;
mealPlanId?: string;
totalEstimatedCost?: number; // Sum of estimated prices
preferredStoreId?: string;
completedAt?: Date;
createdBy: string;
createdAt: Date;
updatedAt: Date;
}
export interface ShoppingItem {
id: string; // UUID for real-time sync reference
productId?: string; // Linked product (optional for custom items)
customName?: string; // For items not in product library
quantity: number;
unit: ServingUnit;
checked: boolean;
checkedAt?: Date;
checkedBy?: string; // userId who checked it off
estimatedPrice?: number; // From price history
actualPrice?: number; // Entered when checked off
storeId?: string; // Preferred store for this item
notes?: string;
category?: ProductCategory; // For grouping in shopping aisle order
addedToPantry: boolean; // Tracks if item was added to pantry after purchase
}
export enum ShoppingListStatus {
ACTIVE = 'active',
SHOPPING = 'shopping', // Currently at the store
COMPLETED = 'completed',
ARCHIVED = 'archived',
}
export interface ShoppingListSource {
type: 'meal_plan' | 'manual' | 'pantry_restock';
referenceId?: string; // MealPlan ID, etc.
}
```
### MongoDB Indexes
```javascript
// PriceRecord
{ householdId: 1, productId: 1, storeId: 1, date: -1 } // Price history per product per store
{ householdId: 1, productId: 1, date: -1 } // Price history per product (all stores)
{ householdId: 1, storeId: 1, date: -1 } // All purchases at a store
{ date: 1, expireAfterSeconds: 63072000 } // Optional TTL: 2 years
// ShoppingList
{ householdId: 1, status: 1 }
{ householdId: 1, createdAt: -1 }
// Store
{ householdId: 1, name: 1 }
```
---
## API Endpoints
### StoresModule
| Method | Path | Description | Auth |
| ------ | ------------- | ------------------------- | ------ |
| GET | `/stores` | List stores for household | member |
| POST | `/stores` | Add a store | member |
| PATCH | `/stores/:id` | Update store | member |
| DELETE | `/stores/:id` | Deactivate store | admin |
### PriceRecordsModule
| Method | Path | Description | Auth |
| ------ | ---------------------------- | ------------------------------------- | ------ |
| POST | `/prices` | Record a price | member |
| POST | `/prices/bulk` | Record multiple prices (from receipt) | member |
| GET | `/prices/history/:productId` | Price history for a product | member |
| GET | `/prices/compare/:productId` | Compare stores for a product | member |
| GET | `/prices/analytics` | Aggregated price analytics | member |
| POST | `/prices/parse-receipt` | LLM receipt parsing (placeholder) | member |
### ShoppingListsModule
| Method | Path | Description | Auth |
| ------ | ---------------------------------------- | ---------------------------------------------- | ------ |
| GET | `/shopping-lists` | List shopping lists | member |
| GET | `/shopping-lists/:id` | Get shopping list | member |
| POST | `/shopping-lists` | Create shopping list | member |
| PATCH | `/shopping-lists/:id` | Update list metadata | member |
| DELETE | `/shopping-lists/:id` | Delete list | admin |
| POST | `/shopping-lists/:id/items` | Add item to list | member |
| PATCH | `/shopping-lists/:id/items/:itemId` | Update item (check off, change qty, set price) | member |
| DELETE | `/shopping-lists/:id/items/:itemId` | Remove item from list | member |
| POST | `/shopping-lists/from-meal-plan/:planId` | Auto-generate from meal plan gap analysis | member |
| POST | `/shopping-lists/:id/add-to-pantry` | Move checked items to pantry | member |
| GET | `/shopping-lists/:id/store-comparison` | Best store(s) for this list | member |
---
## Tasks
### 9.1 — Shared Types & Validation
- Add all types above to `packages/shared`
- Zod schemas for all create/update operations
### 9.2 — Stores CRUD
- Standard CRUD, straightforward
### 9.3 — Price Record Service
```typescript
class PriceService {
/** Record a price, computing pricePerUnit */
recordPrice(data: CreatePriceRecord): Promise<PriceRecord>;
/** Get price history for a product, optionally filtered by store */
getPriceHistory(
productId: string,
householdId: string,
options?: {
storeId?: string;
startDate?: Date;
endDate?: Date;
limit?: number;
},
): Promise<PriceRecord[]>;
/** Compare current prices across stores for a product */
compareStores(productId: string, householdId: string): Promise<StoreComparison[]>;
/** Estimate price for a product based on recent history */
estimatePrice(productId: string, householdId: string, storeId?: string): Promise<number | null>;
/** Detect significant price changes */
detectPriceChanges(householdId: string): Promise<PriceAlert[]>;
}
```
### 9.4 — Shopping List CRUD & Real-Time Sync
- Standard CRUD
- **WebSocket integration**: shopping list room per list ID
- Events: `shopping:item-checked`, `shopping:item-added`, `shopping:item-removed`, `shopping:item-updated`
- Enables multiple household members to shop simultaneously with real-time checkoff sync
- Optimistic updates on frontend with server reconciliation
### 9.5 — Auto-Generate from Meal Plan
- `POST /shopping-lists/from-meal-plan/:planId`:
1. Call Phase 8's shopping gap analysis for the meal plan
2. For each item in `needToBuy`:
- Create a `ShoppingItem` linked to the product
- Call `PriceService.estimatePrice()` to pre-fill estimated price
- Set `category` for store aisle grouping
3. Optionally group by cheapest store per item
4. Return the created shopping list
### 9.6 — Shopping → Pantry Flow
- `POST /shopping-lists/:id/add-to-pantry`:
- For each checked (purchased) item with `addedToPantry: false`:
- Create a `PantryItem` in Phase 7 (status: sealed, purchaseDate: today)
- If `actualPrice` was entered, create a `PriceRecord`
- Mark `addedToPantry: true`
- Return summary: `{ addedCount, priceRecordsCreated }`
### 9.7 — Price Analytics
- `GET /prices/analytics`:
```typescript
interface PriceAnalytics {
/** Average basket cost per store over the last N trips */
averageBasketByStore: {
storeId: string;
storeName: string;
avgTotal: number;
tripCount: number;
}[];
/** Products with significant price increases */
priceAlerts: PriceAlert[];
/** Total spending per period */
spendingOverTime: { period: string; total: number }[];
/** Most expensive categories */
spendingByCategory: { category: ProductCategory; total: number; avgPerItem: number }[];
}
interface PriceAlert {
productId: string;
productName: string;
storeId: string;
storeName: string;
previousPrice: number;
currentPrice: number;
changePercent: number;
date: Date;
}
```
### 9.8 — Store Comparison for Shopping List
- `GET /shopping-lists/:id/store-comparison`:
- For each item in the list, find the cheapest recent price per store
- Calculate total list cost per store
- Suggest: "Buy everything at Store A: $X" vs "Split between stores: $Y"
- Consider: is the savings worth going to multiple stores?
```typescript
interface StoreComparisonResult {
singleStoreOptions: {
storeId: string;
storeName: string;
estimatedTotal: number;
itemsCovered: number; // Not all stores carry all products
itemsMissing: string[];
}[];
splitStoreOption?: {
stores: { storeId: string; storeName: string; items: string[]; subtotal: number }[];
estimatedTotal: number;
savingsVsBestSingleStore: number;
};
}
```
### 9.9 — Receipt Parsing Placeholder
- `POST /prices/parse-receipt`:
- Accepts image upload
- Calls `ILlmProvider.parseReceipt(image)`
- Expected return: `{ storeName, date, items[]: { name, price, quantity } }`
- Match items to products (fuzzy), match store to stores
- With `NoOpLlmProvider`: returns `{ available: false }`
### 9.10 — Web UI: Grocery Management
- `/shopping-lists` page:
- Active lists at top, completed/archived below
- "New List" button (manual or from meal plan)
- Each list card: name, item count, estimated cost, completion %
- `/shopping-lists/:id` page (the "shopping mode"):
- Items grouped by category (aisle order)
- 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?"
- `/stores` page:
- Store list with CRUD
- Per-store: total spent, last visit, product count
- `/prices` page (analytics):
- 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
---
## Acceptance Criteria
- [ ] Can create shopping lists manually and from meal plans
- [ ] Shopping list items sync in real-time across household members via WebSocket
- [ ] Can record prices and view price history per product
- [ ] Store comparison recommends cheapest store for a shopping list
- [ ] Checked off items can be added to pantry with one action
- [ ] Price analytics show spending trends and alerts
- [ ] Auto-generated lists from meal plans correctly reflect shopping gap
- [ ] Receipt parsing endpoint delegates to LLM provider
---
## Estimated Effort
Large. Real-time shopping sync, price analytics aggregations, store comparison algorithm, and the shopping-to-pantry flow involve significant logic and UI.