# Item Classification Implementation Guide ## Overview This implementation adds a classification system to the grocery app allowing users to categorize items by type, group, and store zone. The system supports both creating new items with classification and editing existing items. ## Database Schema ### New Table: `item_classification` ```sql CREATE TABLE item_classification ( id INTEGER PRIMARY KEY REFERENCES grocery_list(id) ON DELETE CASCADE, item_type VARCHAR(50) NOT NULL, item_group VARCHAR(100) NOT NULL, zone VARCHAR(100), confidence DECIMAL(3,2) DEFAULT 1.0, source VARCHAR(20) DEFAULT 'user', created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); ``` **Key Points:** - One-to-one relationship with `grocery_list` (id is both PK and FK) - Cascade delete ensures classification is removed when item is deleted - Classification values are NOT enforced by DB - controlled at app layer - `confidence`: 1.0 for user input, lower values reserved for future ML features - `source`: 'user', 'ml', or 'default' ## Architecture ### Backend **Constants** ([backend/constants/classifications.js](backend/constants/classifications.js)): - `ITEM_TYPES`: 11 predefined types (produce, meat, dairy, etc.) - `ITEM_GROUPS`: Nested object mapping types to their valid groups - `ZONES`: 10 Costco store zones - Validation helpers: `isValidItemType()`, `isValidItemGroup()`, `isValidZone()` **Models** ([backend/models/list.model.js](backend/models/list.model.js)): - `getClassification(itemId)`: Returns classification for an item or null - `upsertClassification(itemId, classification)`: INSERT or UPDATE using ON CONFLICT - `updateItem(id, itemName, quantity)`: Update item name/quantity **Controllers** ([backend/controllers/lists.controller.js](backend/controllers/lists.controller.js)): - `getClassification(req, res)`: GET endpoint to fetch classification - `updateItemWithClassification(req, res)`: Validates and updates item + classification **Routes** ([backend/routes/list.routes.js](backend/routes/list.routes.js)): - `GET /list/item/:id/classification` - Fetch classification (all roles) - `PUT /list/item/:id` - Update item + classification (editor/admin) ### Frontend **Constants** ([frontend/src/constants/classifications.js](frontend/src/constants/classifications.js)): - Mirrors backend constants for UI rendering - `getItemTypeLabel()`: Converts type keys to display names **API Methods** ([frontend/src/api/list.js](frontend/src/api/list.js)): - `getClassification(id)`: Fetch classification for item - `updateItemWithClassification(id, itemName, quantity, classification)`: Update item **Components:** 1. **EditItemModal** ([frontend/src/components/EditItemModal.jsx](frontend/src/components/EditItemModal.jsx)) - Triggered by long-press (500ms) on any grocery item - Prepopulates with existing item data + classification - Cascading selects: item_type → item_group (filtered) → zone - Validation: Requires item_group if item_type is selected - Upserts classification with confidence=1.0, source='user' 2. **ItemClassificationModal** ([frontend/src/components/ItemClassificationModal.jsx](frontend/src/components/ItemClassificationModal.jsx)) - Shown after image upload in new item flow - Required fields: item_type, item_group - Optional: zone - User can skip classification entirely 3. **GroceryListItem** ([frontend/src/components/GroceryListItem.jsx](frontend/src/components/GroceryListItem.jsx)) - Long-press detection with 500ms timer - Supports both touch (mobile) and mouse (desktop) events - Cancels long-press if finger/mouse moves >10px **Main Page** ([frontend/src/pages/GroceryList.jsx](frontend/src/pages/GroceryList.jsx)): - Orchestrates all modal flows - Add flow: Name → Similar check → Image upload → **Classification** → Save - Edit flow: Long-press → Load classification → Edit → Save ## User Flows ### FEATURE 1: Edit Existing Item **Trigger:** Long-press (500ms) on any item in the list **Steps:** 1. User long-presses an item 2. System fetches existing classification (if any) 3. EditItemModal opens with prepopulated data 4. User can edit: - Item name - Quantity - Classification (type, group, zone) 5. On save: - Updates `grocery_list` if name/quantity changed - UPSERTS `item_classification` if classification provided - Sets confidence=1.0, source='user' for user-edited classification **Validation:** - Item name required - Quantity must be ≥ 1 - If item_type selected, item_group is required - item_group options filtered by selected item_type ### FEATURE 2: Add New Item with Classification **Enhanced flow:** 1. User enters item name 2. System checks for similar items (80% threshold) 3. User confirms/edits name 4. User uploads image (or skips) 5. **NEW:** ItemClassificationModal appears - User selects type, group, zone (optional) - Or skips classification 6. Item saved to `grocery_list` 7. If classification provided, saved to `item_classification` ## Data Flow Examples ### Adding Item with Classification ```javascript // 1. Add item to grocery_list const response = await addItem(itemName, quantity, imageFile); // 2. Get item ID const item = await getItemByName(itemName); // 3. Add classification await updateItemWithClassification(item.id, undefined, undefined, { item_type: 'produce', item_group: 'Fruits', zone: 'Fresh Foods Right' }); ``` ### Editing Item Classification ```javascript // Update both item data and classification in one call await updateItemWithClassification(itemId, newName, newQuantity, { item_type: 'dairy', item_group: 'Cheese', zone: 'Dairy Cooler' }); ``` ## Backend Request/Response Shapes ### GET /list/item/:id/classification **Response:** ```json { "item_type": "produce", "item_group": "Fruits", "zone": "Fresh Foods Right", "confidence": 1.0, "source": "user" } ``` Or `null` if no classification exists. ### PUT /list/item/:id **Request Body:** ```json { "itemName": "Organic Apples", "quantity": 5, "classification": { "item_type": "produce", "item_group": "Organic Produce", "zone": "Fresh Foods Right" } } ``` **Validation:** - Validates item_type against allowed values - Validates item_group is valid for the selected item_type - Validates zone against allowed zones - Returns 400 with error message if invalid **Response:** ```json { "message": "Item updated successfully" } ``` ## Setup Instructions ### 1. Run Database Migration ```bash psql -U your_user -d your_database -f backend/migrations/create_item_classification_table.sql ``` ### 2. Restart Backend The backend automatically loads the new classification constants and routes. ### 3. Test Flows **Test Edit:** 1. Long-press any item in the list 2. Verify modal opens with item data 3. Select a type, then a group from filtered list 4. Save and verify item updates **Test Add:** 1. Add a new item 2. Upload image (or skip) 3. Verify classification modal appears 4. Complete classification or skip 5. Verify item appears in list ## Validation Rules Summary 1. **Item Type → Item Group Dependency** - Must select item_type before item_group becomes available - Item group dropdown shows only groups for selected type 2. **Required Fields** - When creating: item_type and item_group are required - When editing: Classification is optional (can edit name/quantity only) 3. **No Free-Text** - All classification values are select dropdowns - Backend validates against predefined constants 4. **Graceful Handling** - Items without classification display normally - Edit modal works for both classified and unclassified items - Classification is always optional (can be skipped) ## State Management (Frontend) **GroceryList.jsx state:** ```javascript { showEditModal: false, editingItem: null, // Item + classification data showClassificationModal: false, classificationPendingItem: { itemName, quantity, imageFile } } ``` ## Long-Press Implementation Details **Timing:** - Desktop (mouse): 500ms hold - Mobile (touch): 500ms hold with <10px movement threshold **Event Handlers:** - `onTouchStart`: Start timer, record position - `onTouchMove`: Cancel if movement >10px - `onTouchEnd`: Clear timer - `onMouseDown`: Start timer - `onMouseUp`: Clear timer - `onMouseLeave`: Clear timer (prevent stuck state) ## Future Enhancements 1. **ML Predictions**: Use confidence <1.0 and source='ml' for auto-classification 2. **Bulk Edit**: Select multiple items and apply same classification 3. **Smart Suggestions**: Learn from user's classification patterns 4. **Zone Optimization**: Suggest optimal shopping route based on zones 5. **Analytics**: Most common types/groups, zone coverage ## Troubleshooting **Issue:** Classification not saving - Check browser console for validation errors - Verify item_type/item_group combination is valid - Ensure item_classification table exists **Issue:** Long-press not triggering - Check that user has editor/admin role - Verify onLongPress prop is passed to GroceryListItem - Test on both mobile (touch) and desktop (mouse) **Issue:** Item groups not filtering - Verify item_type is selected first - Check that ITEM_GROUPS constant has entries for the selected type - Ensure state updates are triggering re-render ## Files Modified/Created ### Backend - ✅ `backend/constants/classifications.js` (NEW) - ✅ `backend/models/list.model.js` (MODIFIED) - ✅ `backend/controllers/lists.controller.js` (MODIFIED) - ✅ `backend/routes/list.routes.js` (MODIFIED) - ✅ `backend/migrations/create_item_classification_table.sql` (NEW) ### Frontend - ✅ `frontend/src/constants/classifications.js` (NEW) - ✅ `frontend/src/api/list.js` (MODIFIED) - ✅ `frontend/src/components/EditItemModal.jsx` (NEW) - ✅ `frontend/src/components/ItemClassificationModal.jsx` (NEW) - ✅ `frontend/src/components/GroceryListItem.jsx` (MODIFIED) - ✅ `frontend/src/pages/GroceryList.jsx` (MODIFIED) - ✅ `frontend/src/styles/EditItemModal.css` (NEW) - ✅ `frontend/src/styles/ItemClassificationModal.css` (NEW) ## API Summary | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | GET | `/list/item/:id/classification` | All roles | Get item classification | | PUT | `/list/item/:id` | Editor/Admin | Update item + classification | ## Database Operations **Upsert Pattern:** ```sql INSERT INTO item_classification (id, item_type, item_group, zone, confidence, source) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO UPDATE SET item_type = EXCLUDED.item_type, item_group = EXCLUDED.item_group, zone = EXCLUDED.zone, confidence = EXCLUDED.confidence, source = EXCLUDED.source RETURNING *; ``` This ensures we INSERT if no classification exists, or UPDATE if it does.