costco-grocery-list/docs/features/classification-implementation.md
2026-01-27 00:03:58 -08:00

11 KiB

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

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):

  • 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):

  • 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):

  • getClassification(req, res): GET endpoint to fetch classification
  • updateItemWithClassification(req, res): Validates and updates item + classification

Routes (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):

  • Mirrors backend constants for UI rendering
  • getItemTypeLabel(): Converts type keys to display names

API Methods (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)

    • 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)

    • 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)

    • 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):

  • 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

// 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

// 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:

{
  "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:

{
  "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:

{
  "message": "Item updated successfully"
}

Setup Instructions

1. Run Database Migration

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:

{
  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:

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.