{
  "name": "ScoutingAPI — Cross-OTA rate-parity monitor",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1
            }
          ]
        }
      },
      "id": "rate-parity-monitor-trigger",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -380,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "assign-1",
              "name": "name",
              "value": "Hotel X Sibenik",
              "type": "string"
            },
            {
              "id": "assign-2",
              "name": "location",
              "value": "Sibenik, HR",
              "type": "string"
            },
            {
              "id": "assign-3",
              "name": "checkIn",
              "value": "",
              "type": "string"
            },
            {
              "id": "assign-4",
              "name": "checkOut",
              "value": "",
              "type": "string"
            },
            {
              "id": "assign-5",
              "name": "adults",
              "value": "2",
              "type": "number"
            },
            {
              "id": "assign-6",
              "name": "currency",
              "value": "EUR",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "rate-parity-monitor-set",
      "name": "Set Inputs",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -160,
        0
      ]
    },
    {
      "parameters": {
        "url": "https://api.scoutingapi.com/v1/price-compare",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "name",
              "value": "={{ $json.name }}"
            },
            {
              "name": "location",
              "value": "={{ $json.location }}"
            },
            {
              "name": "checkIn",
              "value": "={{ $json.checkIn }}"
            },
            {
              "name": "checkOut",
              "value": "={{ $json.checkOut }}"
            },
            {
              "name": "adults",
              "value": "={{ $json.adults }}"
            },
            {
              "name": "currency",
              "value": "={{ $json.currency }}"
            }
          ]
        },
        "options": {}
      },
      "id": "rate-parity-monitor-http",
      "name": "ScoutingAPI: Request",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        60,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_SCOUTINGAPI_CREDENTIAL_ID",
          "name": "ScoutingAPI – Header Auth"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Build a rate-parity digest from the ScoutingAPI /v1/price-compare response.\n// Input item: { data: PriceCompare, meta: SuccessMeta }. Flags OTAs priced below\n// the computed median for this property + dates (a parity exception to fix).\nconst out = [];\nfor (const item of $input.all()) {\n  const data = item.json.data || {};\n  const meta = item.json.meta || {};\n  const currency = data.currency || meta.currency || 'USD';\n  const median = typeof data.median === 'number' ? data.median : null;\n  const offers = (Array.isArray(data.offers) ? [...data.offers] : []).sort(\n    (a, b) => (a.totalPrice ?? Infinity) - (b.totalPrice ?? Infinity),\n  );\n  const rows = offers.map((o) => {\n    const delta =\n      median != null && typeof o.totalPrice === 'number'\n        ? Math.round((o.totalPrice - median) * 100) / 100\n        : null;\n    return {\n      ota: o.ota,\n      totalPrice: o.totalPrice ?? null,\n      currency: o.currency || currency,\n      url: o.url ?? null,\n      deltaVsMedian: delta,\n      belowMedian: delta != null && delta < 0,\n    };\n  });\n  const exceptions = rows.filter((r) => r.belowMedian);\n  const spread =\n    offers.length >= 2\n      ? Math.round(((offers[offers.length - 1].totalPrice ?? 0) - (offers[0].totalPrice ?? 0)) * 100) / 100\n      : 0;\n  const lines = rows.map(\n    (r) =>\n      `${r.belowMedian ? '⚠︎ ' : '  '}${r.ota}: ${r.totalPrice} ${r.currency}` +\n      (r.deltaVsMedian != null ? ` (${r.deltaVsMedian >= 0 ? '+' : ''}${r.deltaVsMedian} vs median)` : '') +\n      (r.url ? ` — ${r.url}` : ''),\n  );\n  const report =\n    `Rate-parity check — ${data.property ?? 'your property'}\\n` +\n    `Stay: ${data.checkIn} → ${data.checkOut} (${currency})\\n` +\n    `Median ${median ?? 'n/a'} · spread ${spread} ${currency}\\n\\n` +\n    (exceptions.length\n      ? `${exceptions.length} OTA(s) below the median:\\n`\n      : `No OTA is below the median — parity looks healthy.\\n`) +\n    `${lines.join('\\n')}\\n\\n` +\n    `Credits charged: ${meta.creditsCharged ?? 0} · requestId ${meta.requestId ?? ''}`;\n  out.push({\n    json: {\n      property: data.property ?? null,\n      checkIn: data.checkIn ?? null,\n      checkOut: data.checkOut ?? null,\n      currency,\n      median,\n      spread,\n      exceptionCount: exceptions.length,\n      exceptions,\n      offers: rows,\n      report,\n    },\n  });\n}\nreturn out;"
      },
      "id": "rate-parity-monitor-code",
      "name": "Build Parity-Exception Digest",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        280,
        0
      ]
    },
    {
      "id": "rate-parity-monitor-delivery",
      "position": [
        500,
        0
      ],
      "parameters": {
        "authentication": "accessToken",
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "REPLACE_WITH_SLACK_CHANNEL_ID",
          "mode": "id"
        },
        "text": "={{ $json.report }}",
        "otherOptions": {}
      },
      "name": "Send Report",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "credentials": {
        "slackApi": {
          "id": "REPLACE_WITH_SLACK_CREDENTIAL_ID",
          "name": "Slack account"
        }
      }
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Set Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Inputs": {
      "main": [
        [
          {
            "node": "ScoutingAPI: Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ScoutingAPI: Request": {
      "main": [
        [
          {
            "node": "Build Parity-Exception Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Parity-Exception Digest": {
      "main": [
        [
          {
            "node": "Send Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "versionId": "scoutingapi-rate-parity-monitor-v1",
  "tags": []
}
