# Database Schema Relationship Analysis

## Executive Summary

This document analyzes the database schema to identify missing table relationships and JOINs that aren't properly implemented in the codebase. This analysis was triggered by discovering that `WatchedItemDeal` was using a `store_name` string instead of a proper `store` object with nested locations.

## Key Findings

### ✅ CORRECTLY IMPLEMENTED

#### 1. Store → Store Locations → Addresses (3-table normalization)

**Schema:**

```sql
stores (store_id) → store_locations (store_location_id) → addresses (address_id)
```

**Implementation:**

- [src/services/db/storeLocation.db.ts](src/services/db/storeLocation.db.ts) properly JOINs all three tables
- [src/types.ts](src/types.ts) defines `StoreWithLocations` interface with nested address objects
- Recent fixes corrected `WatchedItemDeal` to use `store` object instead of `store_name` string

**Queries:**

```typescript
// From storeLocation.db.ts
FROM public.stores s
LEFT JOIN public.store_locations sl ON s.store_id = sl.store_id
LEFT JOIN public.addresses a ON sl.address_id = a.address_id
```

#### 2. Shopping Trips → Shopping Trip Items

**Schema:**

```sql
shopping_trips (shopping_trip_id) → shopping_trip_items (shopping_trip_item_id) → master_grocery_items
```

**Implementation:**

- [src/services/db/shopping.db.ts:513-518](src/services/db/shopping.db.ts#L513-L518) properly JOINs shopping_trips → shopping_trip_items → master_grocery_items
- Uses `json_agg` to nest items array within trip object
- [src/types.ts:639-647](src/types.ts#L639-L647) `ShoppingTrip` interface includes nested `items: ShoppingTripItem[]`

**Queries:**

```typescript
FROM public.shopping_trips st
LEFT JOIN public.shopping_trip_items sti ON st.shopping_trip_id = sti.shopping_trip_id
LEFT JOIN public.master_grocery_items mgi ON sti.master_item_id = mgi.master_grocery_item_id
```

#### 3. Receipts → Receipt Items

**Schema:**

```sql
receipts (receipt_id) → receipt_items (receipt_item_id)
```

**Implementation:**

- [src/types.ts:649-662](src/types.ts#L649-L662) `Receipt` interface includes optional `items?: ReceiptItem[]`
- Receipt items are fetched separately via repository methods
- Proper foreign key relationship maintained

---

### ❌ MISSING / INCORRECT IMPLEMENTATIONS

#### 1. **CRITICAL: Flyers → Flyer Locations → Store Locations (Many-to-Many)**

**Schema:**

```sql
CREATE TABLE IF NOT EXISTS public.flyer_locations (
    flyer_id BIGINT NOT NULL REFERENCES public.flyers(flyer_id) ON DELETE CASCADE,
    store_location_id BIGINT NOT NULL REFERENCES public.store_locations(store_location_id) ON DELETE CASCADE,
    PRIMARY KEY (flyer_id, store_location_id),
    ...
);
COMMENT: 'A linking table associating a single flyer with multiple store locations where its deals are valid.'
```

**Problem:**

- The schema defines a **many-to-many relationship** - a flyer can be valid at multiple store locations
- Current implementation in [src/services/db/flyer.db.ts](src/services/db/flyer.db.ts) **IGNORES** the `flyer_locations` table entirely
- Queries JOIN `flyers` directly to `stores` via `store_id` foreign key
- This means flyers can only be associated with ONE store, not multiple locations

**Current (Incorrect) Queries:**

```typescript
// From flyer.db.ts:315-362
FROM public.flyers f
JOIN public.stores s ON f.store_id = s.store_id  // ❌ Wrong - ignores flyer_locations
```

**Expected (Correct) Queries:**

```typescript
// Should be:
FROM public.flyers f
JOIN public.flyer_locations fl ON f.flyer_id = fl.flyer_id
JOIN public.store_locations sl ON fl.store_location_id = sl.store_location_id
JOIN public.stores s ON sl.store_id = s.store_id
JOIN public.addresses a ON sl.address_id = a.address_id
```

**TypeScript Type Issues:**

- [src/types.ts](src/types.ts) `Flyer` interface has `store` object, but it should have `locations: StoreLocation[]` array
- Current structure assumes one store per flyer, not multiple locations

**Files Affected:**

- [src/services/db/flyer.db.ts](src/services/db/flyer.db.ts) - All flyer queries
- [src/types.ts](src/types.ts) - `Flyer` interface definition
- Any component displaying flyer locations

---

#### 2. **User Submitted Prices → Store Locations (MIGRATED)**

**Status**: ✅ **FIXED** - Migration created

**Schema:**

```sql
CREATE TABLE IF NOT EXISTS public.user_submitted_prices (
    ...
    store_id BIGINT NOT NULL REFERENCES public.stores(store_id) ON DELETE CASCADE,
    ...
);
```

**Solution Implemented:**

- Created migration [sql/migrations/005_add_store_location_to_user_submitted_prices.sql](sql/migrations/005_add_store_location_to_user_submitted_prices.sql)
- Added `store_location_id` column to table (NOT NULL after migration)
- Migrated existing data: linked each price to first location of its store
- Updated TypeScript interface [src/types.ts:270-282](src/types.ts#L270-L282) to include both fields
- Kept `store_id` for backward compatibility during transition

**Benefits:**

- Prices are now specific to individual store locations
- "Walmart Toronto" and "Walmart Vancouver" prices are tracked separately
- Improves geographic specificity for price comparisons
- Enables proximity-based price recommendations

**Next Steps:**

- Application code needs to be updated to use `store_location_id` when creating new prices
- Once all code is migrated, can drop the legacy `store_id` column
- User-submitted prices feature is not yet implemented in the UI

---

#### 3. **Receipts → Store Locations (MIGRATED)**

**Status**: ✅ **FIXED** - Migration created

**Schema:**

```sql
CREATE TABLE IF NOT EXISTS public.receipts (
    ...
    store_id BIGINT REFERENCES public.stores(store_id) ON DELETE CASCADE,
    store_location_id BIGINT REFERENCES public.store_locations(store_location_id) ON DELETE SET NULL,
    ...
);
```

**Solution Implemented:**

- Created migration [sql/migrations/006_add_store_location_to_receipts.sql](sql/migrations/006_add_store_location_to_receipts.sql)
- Added `store_location_id` column to table (nullable - receipts may not have matched store)
- Migrated existing data: linked each receipt to first location of its store
- Updated TypeScript interface [src/types.ts:661-675](src/types.ts#L661-L675) to include both fields
- Kept `store_id` for backward compatibility during transition

**Benefits:**

- Receipts can now be tied to specific store locations
- "Loblaws Queen St" and "Loblaws Bloor St" are tracked separately
- Enables location-specific shopping pattern analysis
- Improves receipt matching accuracy with address data

**Next Steps:**

- Receipt scanning code needs to determine specific store_location_id from OCR text
- May require address parsing/matching logic in receipt processing
- Once all code is migrated, can drop the legacy `store_id` column
- OCR confidence and pattern matching should prefer location-specific data

---

#### 4. Item Price History → Store Locations (Already Correct!)

**Schema:**

```sql
CREATE TABLE IF NOT EXISTS public.item_price_history (
    ...
    store_location_id BIGINT REFERENCES public.store_locations(store_location_id) ON DELETE CASCADE,
    ...
);
```

**Status:**

- ✅ **CORRECTLY IMPLEMENTED** - This table already uses `store_location_id`
- Properly tracks price history per location
- Good example of how other tables should be structured

---

## Summary Table

| Table                 | Foreign Key                 | Should Use                            | Status          | Priority |
| --------------------- | --------------------------- | ------------------------------------- | --------------- | -------- |
| **flyer_locations**   | flyer_id, store_location_id | Many-to-many link                     | ✅ **FIXED**    | ✅ Done  |
| flyers                | store_id                    | ~~store_id~~ Now uses flyer_locations | ✅ **FIXED**    | ✅ Done  |
| user_submitted_prices | store_id                    | store_location_id                     | ✅ **MIGRATED** | ✅ Done  |
| receipts              | store_id                    | store_location_id                     | ✅ **MIGRATED** | ✅ Done  |
| item_price_history    | store_location_id           | ✅ Already correct                    | ✅ Correct      | ✅ Good  |
| shopping_trips        | (no store ref)              | N/A                                   | ✅ Correct      | ✅ Good  |
| store_locations       | store_id, address_id        | ✅ Already correct                    | ✅ Correct      | ✅ Good  |

---

## Impact Assessment

### Critical (Must Fix)

1. **Flyer Locations Many-to-Many**
   - **Impact:** Flyers can't be associated with multiple store locations
   - **User Impact:** Users can't see which specific store locations have deals
   - **Business Logic:** Breaks core assumption that one flyer can be valid at multiple stores
   - **Fix Complexity:** High - requires schema migration, type changes, query rewrites

### Medium (Should Consider)

2. **User Submitted Prices & Receipts**
   - **Impact:** Loss of location-specific data
   - **User Impact:** Can't distinguish between different locations of same store chain
   - **Business Logic:** Reduces accuracy of proximity-based recommendations
   - **Fix Complexity:** Medium - requires migration and query updates

---

## Recommended Actions

### Phase 1: Fix Flyer Locations (Critical)

1. Create migration to properly use `flyer_locations` table
2. Update `Flyer` TypeScript interface to support multiple locations
3. Rewrite all flyer queries in [src/services/db/flyer.db.ts](src/services/db/flyer.db.ts)
4. Update flyer creation/update endpoints to manage `flyer_locations` entries
5. Update frontend components to display multiple locations per flyer
6. Update tests to use new structure

### Phase 2: Consider Store Location Specificity (Optional)

1. Evaluate if location-specific receipts and prices provide value
2. If yes, create migrations to change `store_id` → `store_location_id`
3. Update repository queries
4. Update TypeScript interfaces
5. Update tests

---

## Related Documents

- [ADR-013: Store Address Normalization](../docs/adr/0013-store-address-normalization.md)
- [STORE_ADDRESS_IMPLEMENTATION_PLAN.md](../STORE_ADDRESS_IMPLEMENTATION_PLAN.md)
- [TESTING.md](../docs/TESTING.md)

---

## Analysis Methodology

This analysis was conducted by:

1. Extracting all foreign key relationships from [sql/master_schema_rollup.sql](sql/master_schema_rollup.sql)
2. Comparing schema relationships against TypeScript interfaces in [src/types.ts](src/types.ts)
3. Auditing database queries in [src/services/db/](src/services/db/) for proper JOIN usage
4. Identifying gaps where schema relationships exist but aren't used in queries

Commands used:

```bash
# Extract all foreign keys
podman exec -it flyer-crawler-dev bash -c "grep -n 'REFERENCES' sql/master_schema_rollup.sql"

# Check specific table structures
podman exec -it flyer-crawler-dev bash -c "grep -A 15 'CREATE TABLE.*table_name' sql/master_schema_rollup.sql"

# Verify query patterns
podman exec -it flyer-crawler-dev bash -c "grep -n 'JOIN.*table_name' src/services/db/*.ts"
```

---

**Last Updated:** 2026-01-19
**Analyzed By:** Claude Code (via user request after discovering store_name → store bug)
