# 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: 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
  - "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 (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)
  - 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

---

## 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
- [ ] Web UI has component tests for all pages and interactive components
- [ ] `npm run build`, `npm run test:cov`, and `npm run lint` all pass

---

## Estimated Effort

Large. Real-time shopping sync, price analytics aggregations, store comparison algorithm, and the shopping-to-pantry flow involve significant logic and UI.
