{
  "name": "Outscraper - Review sentiment analysis workflow",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks"
            }
          ]
        }
      },
      "id": "b7035c8b-7a79-4874-b6a4-b887bef8a090",
      "name": "Weekly Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        1680,
        272
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "business_url",
              "name": "business_url",
              "type": "string",
              "value": "https://www.google.com/maps/place/The+Manhattan+at+Times+Square+Hotel/@40.7315435,-74.0846167,12z/data=!3m1!5s0x89c258f9ba587e33:0x6992b0beff04f494!4m13!1m2!2m1!1z0JPQvtGB0YLQuNC90LjRhtGL!3m9!1s0x89c258f88254f2c3:0xe0e14597c5400022!5m2!4m1!1i2!8m2!3d40.7622856!4d-73.9826404!15sChLQk9C-0YHRgtC40L3QuNGG0YuSAQVob3RlbKoBUhABKhYiEtCz0L7RgdGC0LjQvdC40YbRiygNMh4QASIauqvaSbDcn9ZlmNzFbbwkcitXCy3j5BpJzugyFhACIhLQs9C-0YHRgtC40L3QuNGG0YvgAQA!16s%2Fg%2F1hhg_3mgs?entry=ttu&g_ep=EgoyMDI2MDQyNy4wIKXMDSoASAFQAw%3D%3D"
            },
            {
              "id": "reviews_limit",
              "name": "reviews_limit",
              "type": "number",
              "value": 50
            }
          ]
        },
        "options": {}
      },
      "id": "c77ba2d8-7101-4595-a6de-465467ee4011",
      "name": "Set Business",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1904,
        272
      ]
    },
    {
      "parameters": {
        "operation": "reviews",
        "query": "={{$json.business_url}}",
        "reviewsLimit": "={{$json.reviews_limit}}",
        "sort": "newest",
        "requestOptions": {}
      },
      "id": "745cbfc1-c188-4cbb-af8d-4eebfe239486",
      "name": "Outscraper - Google Maps Reviews",
      "type": "n8n-nodes-outscraper.outscraper",
      "typeVersion": 2,
      "position": [
        2144,
        272
      ],
      "notesInFlow": true,
      "credentials": {
        "outscraperApi": {
          "id": "8AvSJfwIww0gbyf7",
          "name": "Outscraper account"
        }
      },
      "notes": "Can be swapped for Trustpilot, Yelp, TripAdvisor, Booking, G2, AppStore, GetApp, Walmart, or Target review operations."
    },
    {
      "parameters": {
        "jsCode": "function isReviewRecord(value) {\n  return Boolean(\n    value &&\n      typeof value === 'object' &&\n      !Array.isArray(value) &&\n      (value.review_id || value.review_text || value.review_rating || value.author_title)\n  );\n}\n\nfunction collectReviews(value, output = []) {\n  if (!value) return output;\n\n  if (Array.isArray(value)) {\n    for (const item of value) collectReviews(item, output);\n    return output;\n  }\n\n  if (isReviewRecord(value)) {\n    output.push(value);\n    return output;\n  }\n\n  if (typeof value === 'object') {\n    for (const key of ['reviews_data', 'reviews', 'data', 'results', 'items']) {\n      if (key in value) collectReviews(value[key], output);\n    }\n  }\n\n  return output;\n}\n\nconst reviews = $input.all().flatMap((item) => collectReviews(item.json));\n\nreturn reviews\n  .map((review) => ({\n    json: {\n      business_name: review.name || review.business_name || review.owner_title || '',\n      author: review.author_title || review.author || review.user || '',\n      rating: review.review_rating ?? review.rating ?? '',\n      text: review.review_text || review.text || review.comment || '',\n      date: review.review_datetime_utc || review.date || review.created_at || '',\n      owner_answer: review.owner_answer || '',\n      review_link: review.review_link || '',\n      source: review.author_link && review.author_link.includes('tripadvisor') ? 'tripadvisor_via_google_maps' : 'google_maps',\n      raw: review,\n    },\n  }))\n  .filter((item) => item.json.text || item.json.rating !== '');"
      },
      "id": "e61cd09e-d5e5-4677-8cc7-d0b2d35d7d9c",
      "name": "Normalize Reviews",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2384,
        272
      ]
    },
    {
      "parameters": {
        "jsCode": "const reviews = $input.all().map((item) => item.json);\nconst averageRating = reviews.reduce((sum, review) => sum + Number(review.rating || 0), 0) / Math.max(reviews.length, 1);\nreturn [{\n  json: {\n    review_count: reviews.length,\n    average_rating: Number(averageRating.toFixed(2)),\n    reviews,\n  },\n}];"
      },
      "id": "4eb713ad-239b-4ba1-af32-6ad31c70d0dc",
      "name": "Aggregate Reviews",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2624,
        272
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Analyze this Outscraper review aggregate. Use the connected structured output parser and return only the requested JSON shape. Focus on sentiment, urgent issues, praise, actions, and a Slack-ready summary. Input JSON: {{ JSON.stringify($json) }}",
        "hasOutputParser": true,
        "options": {
          "enableStreaming": false
        }
      },
      "id": "252b5855-9418-4ad7-9575-17d4bebed015",
      "name": "AI Agent - Analyze Sentiment",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        2864,
        272
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-5.4-mini",
          "mode": "list",
          "cachedResultName": "gpt-5.4-mini"
        },
        "builtInTools": {},
        "options": {}
      },
      "id": "76530843-07cf-4971-b8b9-98f47a71c3a4",
      "name": "OpenAI Chat Model - Review Analysis",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        2816,
        512
      ],
      "credentials": {
        "openAiApi": {
          "id": "cF6D8w13EQm9nSZt",
          "name": "OpenAI account"
        }
      }
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"additionalProperties\": false,\n  \"required\": [\n    \"sentiment\",\n    \"sentiment_summary\",\n    \"urgent_issues\",\n    \"praise\",\n    \"suggested_actions\",\n    \"slack_summary\"\n  ],\n  \"properties\": {\n    \"sentiment\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"positive\",\n        \"mixed\",\n        \"negative\",\n        \"insufficient_data\"\n      ]\n    },\n    \"sentiment_summary\": {\n      \"type\": \"string\"\n    },\n    \"urgent_issues\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"praise\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"suggested_actions\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"slack_summary\": {\n      \"type\": \"string\"\n    }\n  }\n}"
      },
      "id": "d32167aa-bbec-4187-bdc6-7cf7759aeb7f",
      "name": "Structured Output Parser - Review Sentiment",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        3056,
        512
      ]
    },
    {
      "parameters": {
        "jsCode": "const result = $json.output || $json;\nreturn [{\n  json: {\n    text: `Review sentiment digest\\n\\n${result.slack_summary || JSON.stringify(result, null, 2)}`,\n    analysis: result,\n  },\n}];"
      },
      "id": "0fcd7601-f6f4-4ba1-81dc-5aeee1094e1e",
      "name": "Format Slack Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3216,
        272
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://hooks.slack.com/services/REPLACE/ME",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{JSON.stringify({ text: $json.text })}}",
        "options": {}
      },
      "id": "68bd0493-814c-4cc7-aae9-c82f1f77e246",
      "name": "Slack Notification",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3440,
        272
      ],
      "notesInFlow": true,
      "notes": "Replace the webhook URL or swap this for the native Slack node."
    }
  ],
  "pinData": {},
  "connections": {
    "Weekly Schedule": {
      "main": [
        [
          {
            "node": "Set Business",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Business": {
      "main": [
        [
          {
            "node": "Outscraper - Google Maps Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Outscraper - Google Maps Reviews": {
      "main": [
        [
          {
            "node": "Normalize Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Reviews": {
      "main": [
        [
          {
            "node": "Aggregate Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Reviews": {
      "main": [
        [
          {
            "node": "AI Agent - Analyze Sentiment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Slack Message": {
      "main": [
        [
          {
            "node": "Slack Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Analyze Sentiment": {
      "main": [
        [
          {
            "node": "Format Slack Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model - Review Analysis": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Analyze Sentiment",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser - Review Sentiment": {
      "ai_outputParser": [
        [
          {
            "node": "AI Agent - Analyze Sentiment",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "107ee398-acd2-46cf-b16b-a0c3da713bc1",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "bd51ba3d599fc63602ae14309acd27ef3912f3068784a786f28ba05657ba8dbb"
  },
  "id": "59bwKFDBsRWZe50g",
  "tags": []
}
