{
  "name": "Sunday Grocery Assistant",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "grocery-assistant",
        "responseMode": "onReceived",
        "responseData": "allEntries"
      },
      "id": "webhook-trigger",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [240, 300],
      "webhookId": "grocery-assistant"
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://nominatim.openstreetmap.org/reverse?lat={{ $json.body.lat }}&lon={{ $json.body.lon }}&format=json",
        "options": {
          "response": {
            "response": {
              "fullResponse": false
            }
          }
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "OpenClaw-GroceryAssistant/1.0"
            }
          ]
        }
      },
      "id": "reverse-geocode",
      "name": "Reverse Geocode",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://mealie.disorganized.net/api/households/shopping/lists",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "mealie-lists",
      "name": "Get Mealie Lists",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [460, 500],
      "credentials": {
        "httpHeaderAuth": {
          "id": "MEALIE_CRED_ID",
          "name": "Mealie API Token"
        }
      }
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://mealie.disorganized.net/api/households/shopping/lists/{{ $json.items[0].id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "mealie-list-items",
      "name": "Get Shopping List Items",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [680, 500],
      "credentials": {
        "httpHeaderAuth": {
          "id": "MEALIE_CRED_ID",
          "name": "Mealie API Token"
        }
      }
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.search.brave.com/res/v1/web/search?q=grocery+stores+near+{{ encodeURIComponent($('Reverse Geocode').item.json.address.city + ', ' + $('Reverse Geocode').item.json.address.state) }}&count=5",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "search-stores",
      "name": "Search Nearby Stores",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [680, 300],
      "credentials": {
        "httpHeaderAuth": {
          "id": "BRAVE_CRED_ID",
          "name": "Brave Search API"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Extract store names from search results\nconst searchResults = $('Search Nearby Stores').item.json;\nconst geocode = $('Reverse Geocode').item.json;\nconst shoppingList = $('Get Shopping List Items').item.json;\n\n// Parse store names from results\nconst stores = [];\nif (searchResults.web && searchResults.web.results) {\n  for (const result of searchResults.web.results.slice(0, 3)) {\n    // Extract store name from title\n    const title = result.title;\n    if (title.toLowerCase().includes('walmart')) stores.push('Walmart');\n    else if (title.toLowerCase().includes('kroger')) stores.push('Kroger');\n    else if (title.toLowerCase().includes('safeway')) stores.push('Safeway');\n    else if (title.toLowerCase().includes('albertsons')) stores.push('Albertsons');\n    else if (title.toLowerCase().includes('vons')) stores.push('Vons');\n    else if (title.toLowerCase().includes('stater')) stores.push('Stater Bros');\n    else if (title.toLowerCase().includes('sprouts')) stores.push('Sprouts');\n    else if (title.toLowerCase().includes('trader')) stores.push(\"Trader Joe's\");\n    else if (title.toLowerCase().includes('costco')) stores.push('Costco');\n    else if (title.toLowerCase().includes('target')) stores.push('Target');\n    else if (title.toLowerCase().includes('aldi')) stores.push('Aldi');\n    else if (title.toLowerCase().includes('food 4 less')) stores.push('Food 4 Less');\n    else if (title.toLowerCase().includes('winco')) stores.push('WinCo');\n    else if (title.toLowerCase().includes('smith')) stores.push(\"Smith's\");\n  }\n}\n\n// Dedupe\nconst uniqueStores = [...new Set(stores)].slice(0, 3);\n\n// Get shopping list items\nconst listItems = shoppingList.listItems || [];\nconst itemNames = listItems.filter(i => !i.checked).map(i => i.display || i.note || 'unknown item');\n\nreturn {\n  city: geocode.address?.city || geocode.address?.town || 'Unknown',\n  state: geocode.address?.state || '',\n  stores: uniqueStores,\n  shoppingList: itemNames,\n  lat: geocode.lat,\n  lon: geocode.lon\n};"
      },
      "id": "parse-data",
      "name": "Parse Store & List Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [900, 400]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.search.brave.com/res/v1/web/search?q={{ encodeURIComponent($json.stores[0] + ' weekly ad ' + $json.city) }}&count=3",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "search-deals-1",
      "name": "Search Deals Store 1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1120, 200],
      "credentials": {
        "httpHeaderAuth": {
          "id": "BRAVE_CRED_ID",
          "name": "Brave Search API"
        }
      }
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.search.brave.com/res/v1/web/search?q={{ encodeURIComponent($json.stores[1] + ' weekly ad ' + $json.city) }}&count=3",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "search-deals-2",
      "name": "Search Deals Store 2",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1120, 400],
      "credentials": {
        "httpHeaderAuth": {
          "id": "BRAVE_CRED_ID",
          "name": "Brave Search API"
        }
      }
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.search.brave.com/res/v1/web/search?q={{ encodeURIComponent($json.stores[2] + ' weekly ad ' + $json.city) }}&count=3",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "search-deals-3",
      "name": "Search Deals Store 3",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1120, 600],
      "credentials": {
        "httpHeaderAuth": {
          "id": "BRAVE_CRED_ID",
          "name": "Brave Search API"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Merge all deal search results\nconst storeData = $('Parse Store & List Data').item.json;\nconst deals1 = $('Search Deals Store 1').item.json;\nconst deals2 = $('Search Deals Store 2').item.json;\nconst deals3 = $('Search Deals Store 3').item.json;\n\nfunction extractDeals(searchResult, storeName) {\n  if (!searchResult?.web?.results) return [];\n  return searchResult.web.results.map(r => ({\n    store: storeName,\n    title: r.title,\n    description: r.description,\n    url: r.url\n  }));\n}\n\nconst allDeals = [\n  ...extractDeals(deals1, storeData.stores[0]),\n  ...extractDeals(deals2, storeData.stores[1]),\n  ...extractDeals(deals3, storeData.stores[2])\n].filter(d => d.store);\n\nreturn {\n  city: storeData.city,\n  state: storeData.state,\n  stores: storeData.stores,\n  shoppingList: storeData.shoppingList,\n  deals: allDeals\n};"
      },
      "id": "merge-deals",
      "name": "Merge Deal Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1340, 400]
    },
    {
      "parameters": {
        "model": "gpt-4o-mini",
        "messages": {
          "values": [
            {
              "content": "=You are a helpful grocery shopping assistant. Based on the user's location, shopping list, and nearby store deals, provide a concise Sunday grocery briefing.\n\n**Location:** {{ $json.city }}, {{ $json.state }}\n\n**Shopping List:**\n{{ $json.shoppingList.join('\\n- ') }}\n\n**Nearby Stores:** {{ $json.stores.join(', ') }}\n\n**Deal Information:**\n{{ $json.deals.map(d => `${d.store}: ${d.title} - ${d.description}`).join('\\n') }}\n\n**Budget:** $80\n\nProvide:\n1. Recommended store to visit (considering deals and convenience)\n2. Any deals that match items on the shopping list\n3. General money-saving tips for this trip\n4. Reminder about the $80 budget\n\nKeep it concise - this will be sent via text message.",
              "role": "user"
            }
          ]
        },
        "options": {
          "temperature": 0.7,
          "maxTokens": 500
        }
      },
      "id": "ai-summary",
      "name": "AI Grocery Summary",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.4,
      "position": [1560, 400]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://192.168.7.220:18789/api/v1/message",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"channel\": \"signal\",\n  \"to\": \"+12817509483\",\n  \"message\": \"🛒 **SUNDAY GROCERY BRIEFING**\\n\\n{{ $json.message.content }}\"\n}",
        "options": {}
      },
      "id": "send-signal",
      "name": "Send via Signal",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1780, 400],
      "credentials": {
        "httpHeaderAuth": {
          "id": "OPENCLAW_CRED_ID",
          "name": "OpenClaw Gateway"
        }
      }
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Reverse Geocode",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Mealie Lists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reverse Geocode": {
      "main": [
        [
          {
            "node": "Search Nearby Stores",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Mealie Lists": {
      "main": [
        [
          {
            "node": "Get Shopping List Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Nearby Stores": {
      "main": [
        [
          {
            "node": "Parse Store & List Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Shopping List Items": {
      "main": [
        [
          {
            "node": "Parse Store & List Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Store & List Data": {
      "main": [
        [
          {
            "node": "Search Deals Store 1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search Deals Store 2",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search Deals Store 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Deals Store 1": {
      "main": [
        [
          {
            "node": "Merge Deal Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Deals Store 2": {
      "main": [
        [
          {
            "node": "Merge Deal Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Deals Store 3": {
      "main": [
        [
          {
            "node": "Merge Deal Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Deal Results": {
      "main": [
        [
          {
            "node": "AI Grocery Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Grocery Summary": {
      "main": [
        [
          {
            "node": "Send via Signal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [],
  "triggerCount": 0,
  "pinData": {}
}
