# Phase 7 — Pantry & Fridge Tracking

**Goal**: Track the lifecycle of physical food items — purchase, opening, preparation, consumption, or disposal. Estimate freshness/spoilage timelines. Provide a real-time dashboard of what's in the household's storage.

**Depends on**: Phase 0, Phase 5

---

## Deliverables

1. `PantryItem` and `FreshnessRule` MongoDB schemas
2. Full CRUD API with status transition workflow
3. Freshness estimation and urgency scoring
4. Scheduled freshness check job (cron) with in-app notifications
5. Pantry dashboard web UI with color-coded freshness
6. Waste analysis history

---

## Data Model

### PantryItem Schema

```typescript
// packages/shared/src/types/pantry.ts
export interface PantryItem {
  id: string;
  householdId: string;
  productId: string;
  productName: string; // Denormalized
  storageLocation: StorageLocation;
  quantity: number;
  unit: ServingUnit;
  purchaseDate: Date;
  expirationDate?: Date; // From packaging, if known
  openedDate?: Date;
  preparedDate?: Date;
  status: ItemStatus;
  freshnessEstimate: FreshnessEstimate;
  notes?: string;
  purchasePrice?: number; // Links to grocery tracking (Phase 9)
  storeId?: string; // Where it was bought
  createdBy: string;
  createdAt: Date;
  updatedAt: Date;
}

export enum StorageLocation {
  PANTRY = 'pantry',
  FRIDGE = 'fridge',
  FREEZER = 'freezer',
  COUNTER = 'counter',
}

export enum ItemStatus {
  SEALED = 'sealed',
  OPENED = 'opened',
  PREPARED = 'prepared',
  CONSUMED = 'consumed',
  DISCARDED = 'discarded',
  EXPIRED = 'expired',
}

export interface FreshnessEstimate {
  estimatedExpiryDate: Date; // Computed from rules
  daysRemaining: number; // Computed
  urgency: FreshnessUrgency; // Computed
  source: 'packaging' | 'rule' | 'manual';
}

export enum FreshnessUrgency {
  FRESH = 'fresh', // > 5 days
  USE_SOON = 'use_soon', // 2-5 days
  URGENT = 'urgent', // 0-2 days
  CHECK = 'check', // Past estimated date, may still be ok
  EXPIRED = 'expired', // Way past date
}
```

### FreshnessRule Schema

```typescript
// packages/shared/src/types/freshness.ts
export interface FreshnessRule {
  id: string;
  householdId?: string; // null = system default
  category: ProductCategory;
  storageLocation: StorageLocation;
  shelfLifeDays: number; // When sealed
  openedLifeDays: number; // After opening
  freezerLifeDays?: number; // If moved to freezer
  spoilageSignsToCheck: string[]; // e.g., ['smell', 'discoloration', 'texture change']
  tips?: string; // Storage tips
  source: 'system' | 'household'; // System defaults vs household overrides
}
```

### Status Transition Rules

```
           ┌──────────┐
           │  SEALED   │
           └─────┬─────┘
                 │
        ┌────────┼────────┐
        ▼        ▼        ▼
   ┌─────────┐ ┌──────┐ ┌──────────┐
   │ OPENED  │ │CONSUMED│ │DISCARDED │
   └────┬────┘ └──────┘ └──────────┘
        │
   ┌────┼────────┐
   ▼    ▼        ▼
┌──────┐┌──────────┐┌──────────┐
│PREPARED│ │CONSUMED │ │DISCARDED │
└───┬──┘ └──────────┘└──────────┘
    │
    ├──────────┐
    ▼          ▼
┌──────────┐┌──────────┐
│ CONSUMED ││ DISCARDED│
└──────────┘└──────────┘
```

Valid transitions:

- `sealed → opened | consumed | discarded`
- `opened → prepared | consumed | discarded`
- `prepared → consumed | discarded`
- Any status → `expired` (set by system cron)

### MongoDB Indexes

```javascript
{ householdId: 1, status: 1, 'freshnessEstimate.urgency': 1 }
{ householdId: 1, storageLocation: 1, status: 1 }
{ householdId: 1, productId: 1, status: 1 }
{ householdId: 1, 'freshnessEstimate.estimatedExpiryDate': 1 }   // For cron job
{ 'freshnessRule.category': 1, 'freshnessRule.storageLocation': 1 } // For rule lookup
```

---

## API Endpoints

### PantryModule

| Method | Path                       | Description                                                   | Auth   |
| ------ | -------------------------- | ------------------------------------------------------------- | ------ |
| GET    | `/pantry`                  | List pantry items (filtered, paginated)                       | member |
| GET    | `/pantry/:id`              | Get single item                                               | member |
| POST   | `/pantry`                  | Add item to pantry                                            | member |
| PATCH  | `/pantry/:id`              | Update item details                                           | member |
| POST   | `/pantry/:id/transition`   | Change status (open, consume, discard, etc.)                  | member |
| POST   | `/pantry/batch-transition` | Bulk status change (e.g., mark all as consumed after cooking) | member |
| DELETE | `/pantry/:id`              | Hard delete (admin)                                           | admin  |
| GET    | `/pantry/expiring-soon`    | Items expiring within N days                                  | member |
| GET    | `/pantry/stats`            | Waste analysis summary                                        | member |

### FreshnessRulesModule

| Method | Path                   | Description                                  | Auth   |
| ------ | ---------------------- | -------------------------------------------- | ------ |
| GET    | `/freshness-rules`     | List rules (system + household overrides)    | member |
| POST   | `/freshness-rules`     | Create household override                    | admin  |
| PATCH  | `/freshness-rules/:id` | Update household rule                        | admin  |
| DELETE | `/freshness-rules/:id` | Remove household override (revert to system) | admin  |

### Query Parameters for GET `/pantry`

```
?storageLocation=fridge          # Filter by location
&status=sealed,opened            # Filter by status (comma-separated)
&urgency=urgent,use_soon         # Filter by freshness urgency
&productId=abc123                # Filter by product
&sort=-freshnessEstimate.daysRemaining  # Sort by urgency (most urgent first)
&cursor=abc123
&limit=20
```

---

## Tasks

### 7.1 — Shared Types & Validation

- Add pantry types to `packages/shared/src/types/pantry.ts`
- Add freshness types to `packages/shared/src/types/freshness.ts`
- Zod schemas:
  - `CreatePantryItemSchema`
  - `UpdatePantryItemSchema`
  - `TransitionPantryItemSchema` — `{ status: ItemStatus, date?: Date, notes?: string }`
  - `CreateFreshnessRuleSchema`

### 7.2 — Freshness Rule Seed Data

- Seed `FreshnessRule` collection with defaults based on USDA/StillTasty guidelines:

| Category   | Location | Sealed (days) | Opened (days) | Freezer (days) |
| ---------- | -------- | ------------- | ------------- | -------------- |
| Dairy      | Fridge   | 14            | 7             | 90             |
| Meat       | Fridge   | 3             | 2             | 180            |
| Poultry    | Fridge   | 2             | 1             | 270            |
| Seafood    | Fridge   | 2             | 1             | 180            |
| Fruits     | Counter  | 7             | 3             | 270            |
| Vegetables | Fridge   | 7             | 4             | 270            |
| Grains     | Pantry   | 180           | 90            | 365            |
| Legumes    | Pantry   | 365           | 7             | 365            |
| Bakery     | Counter  | 5             | 3             | 90             |
| ...        | ...      | ...           | ...           | ...            |

- Household can override any rule

### 7.3 — Freshness Calculation Service

```typescript
class FreshnessService {
  /**
   * Given a pantry item and its applicable freshness rule:
   * 1. If packaging expirationDate exists, use it
   * 2. Else compute: purchaseDate + shelfLifeDays (sealed) or openedDate + openedLifeDays (opened)
   * 3. If in freezer, use freezerLifeDays from purchaseDate
   * 4. Calculate daysRemaining = estimatedExpiryDate - today
   * 5. Map to urgency: >5 = FRESH, 2-5 = USE_SOON, 0-2 = URGENT, <0 = CHECK/EXPIRED
   */
  calculateFreshness(item: PantryItem, rule: FreshnessRule): FreshnessEstimate;

  /**
   * Find the most specific rule: household override > system default
   * Match by category + storageLocation
   */
  findApplicableRule(
    householdId: string,
    category: ProductCategory,
    location: StorageLocation,
  ): FreshnessRule;
}
```

### 7.4 — Status Transition Service

```typescript
class PantryTransitionService {
  /**
   * Validate transition is allowed, apply side effects:
   * - sealed → opened: set openedDate, recalculate freshness with openedLifeDays
   * - * → consumed: record consumption date, update quantity
   * - * → discarded: record discard date, log for waste analysis
   */
  transition(item: PantryItem, newStatus: ItemStatus, metadata?: TransitionMetadata): PantryItem;
}
```

### 7.5 — Freshness Cron Job

- NestJS `@Cron('0 6 * * *')` (daily at 6 AM, configurable):
  1. Query all active pantry items (status: sealed/opened/prepared)
  2. Recalculate freshness estimates
  3. Items past expiry → update status to `expired`
  4. Items in `urgent` or `check` → create in-app notifications
- Notification model (simple for now, expand for push in mobile phase):

```typescript
export interface Notification {
  id: string;
  householdId: string;
  userId?: string; // null = all household members
  type: 'freshness_warning' | 'item_expired';
  title: string;
  body: string;
  relatedEntityId: string; // PantryItem ID
  isRead: boolean;
  createdAt: Date;
}
```

### 7.6 — Waste Analysis

- `GET /pantry/stats` returns:

```typescript
interface WasteStats {
  period: { start: Date; end: Date };
  totalItemsConsumed: number;
  totalItemsDiscarded: number;
  wastePercentage: number; // discarded / (consumed + discarded) * 100
  topWastedCategories: { category: ProductCategory; count: number }[];
  topWastedProducts: { productId: string; productName: string; count: number }[];
  trendVsPreviousPeriod: number; // % change
}
```

- Query parameters: `?period=week|month|quarter|year`

### 7.7 — WebSocket Events (Initial)

- Set up NestJS `@WebSocketGateway` with household-scoped rooms
- Events:
  - `pantry:item-added` — when a new item is added
  - `pantry:item-updated` — when item status changes
  - `pantry:freshness-alert` — when cron detects urgent items
- Frontend subscribes on pantry page for real-time updates across household members

### 7.8 — Web UI: Pantry Dashboard

- `/pantry` page:
  - **Storage tabs**: Fridge | Freezer | Pantry | Counter | All
  - **View modes**: Grid (cards) | List (table)
  - Each item shows:
    - Product name, quantity
    - Freshness indicator: color-coded chip (green/yellow/orange/red)
    - Days remaining
    - Status badge
    - Quick-action buttons: Open | Consume | Discard
  - **Sort**: by urgency (default), name, purchase date
  - **Filter**: by urgency level, category
  - Floating "Add Item" button → modal:
    - Product autocomplete (from library)
    - Storage location picker
    - Purchase date (default today)
    - Expiration date (optional, from packaging)
    - Quantity + unit
- `/pantry/stats` page:
  - Waste percentage gauge
  - Top wasted categories bar chart
  - Trend line chart (weekly waste over time)
- **Notification bell** in top bar: shows freshness warnings, mark as read

---

## Acceptance Criteria

- [ ] Can add items to pantry linked to products
- [ ] Freshness estimate is calculated on creation and updates
- [ ] Status transitions follow valid workflow rules
- [ ] Daily cron job flags expiring items and creates notifications
- [ ] Pantry dashboard shows items color-coded by freshness urgency
- [ ] Waste stats endpoint returns correct aggregation
- [ ] WebSocket broadcasts pantry changes to household members
- [ ] Freshness rules can be overridden per household
- [ ] Items sorted by urgency show most critical first

---

## Estimated Effort

Medium-large. Freshness logic, cron job, notifications, and WebSocket add significant complexity beyond basic CRUD.
