{
  "name": "Google Trends daily digest to Google Sheets",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "17 6 * * *"
            }
          ]
        }
      },
      "id": "digest-1",
      "name": "Every day at 06:17",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        200
      ]
    },
    {
      "parameters": {},
      "id": "digest-2",
      "name": "Manual run",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        400
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "actor-id",
              "name": "actorId",
              "value": "automa-flow~google-trends-monitor",
              "type": "string"
            },
            {
              "id": "spreadsheet-id",
              "name": "spreadsheetId",
              "value": "REPLACE_WITH_SPREADSHEET_ID",
              "type": "string"
            },
            {
              "id": "monitor-id",
              "name": "monitorId",
              "value": "n8n-daily-digest",
              "type": "string"
            },
            {
              "id": "geo",
              "name": "geo",
              "value": "US",
              "type": "string"
            },
            {
              "id": "timeframe",
              "name": "timeframe",
              "value": "today 3-m",
              "type": "string"
            },
            {
              "id": "max-charge",
              "name": "maxChargeUsd",
              "value": 1,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "digest-3",
      "name": "Configure",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configure').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Topics",
          "mode": "name"
        },
        "options": {}
      },
      "id": "digest-4",
      "name": "Read topics",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        440,
        300
      ],
      "alwaysOutputData": true,
      "executeOnce": true
    },
    {
      "parameters": {
        "jsCode": "// Topics tab -> one Actor input. Checked here so a bad row never starts a paid run.\nconst config = $('Configure').first().json;\nif (!/^[A-Za-z0-9]{17}$|^[a-z0-9-]+~[a-z0-9-]+$/.test(config.actorId || '')) throw new Error('Set actorId in Configure, for example automa-flow~google-trends-monitor.');\nif (!/^[A-Za-z0-9_-]{25,}$/.test(config.spreadsheetId || '') || /^REPLACE_/.test(config.spreadsheetId)) throw new Error('Set spreadsheetId in Configure: the long ID in your Google Sheet URL.');\nif (!/^[A-Za-z0-9_.-]{1,64}$/.test(config.monitorId || '')) throw new Error('Set monitorId in Configure (letters, numbers, \".\", \"_\", \"-\") and keep it: it holds this watchlist\\'s history.');\nconst cap = Number(config.maxChargeUsd);\nif (!(cap > 0)) throw new Error('Set maxChargeUsd in Configure, the most one run may cost, for example 1.');\nconst queries = [];\nconst ids = new Set();\nconst groups = new Set();\nfor (const row of $input.all().map(item => item.json)) {\n  const where = row.row_number ? `Topics row ${row.row_number}` : 'A Topics row';\n  const text = value => String(value ?? '').trim();\n  const [raw, externalId, geo] = [text(row.keywords), text(row.externalId), text(row.geo).toUpperCase()];\n  if (!raw && !externalId && !geo) continue;\n  const keywords = raw.split(',').map(k => k.trim()).filter(Boolean);\n  if (keywords.length < 1 || keywords.length > 5 || keywords.some(k => k.length > 100)) throw new Error(`${where}: put 1-5 keywords, comma-separated, each up to 100 characters.`);\n  if (!externalId || externalId.length > 200) throw new Error(`${where}: externalId is required (up to 200 characters); the digest tabs are keyed by it.`);\n  if (ids.has(externalId)) throw new Error(`${where}: externalId \"${externalId}\" is used twice.`);\n  if (geo && !/^[A-Z]{2}(-[A-Z0-9]{1,3})?$/.test(geo)) throw new Error(`${where}: geo must be a country code such as US or DE, or empty for ${config.geo}.`);\n  const group = JSON.stringify([keywords.map(k => k.toLowerCase()).sort(), geo || config.geo]);\n  if (groups.has(group)) throw new Error(`${where}: the same keywords and geo are already listed.`);\n  ids.add(externalId);\n  groups.add(group);\n  queries.push(geo ? {keywords, geo, externalId} : {keywords, externalId});\n}\nif (!queries.length) throw new Error('Add at least one topic to the Topics tab: keywords, geo, externalId.');\nif (queries.length > 200) throw new Error(`The Topics tab has ${queries.length} topics; one run checks up to 200.`);\n// monitor + changesOnly: the first run saves a baseline, later runs return what is new.\n// keywordSummaries: API runs leave summaries out unless asked; the Summary tab needs them.\nconst input = {queries, geo: config.geo, timeframe: config.timeframe, dataTypes: ['interest_over_time', 'related_queries'], mode: 'monitor', outputMode: 'changesOnly', monitorId: config.monitorId, keywordSummaries: true};\nreturn [{json: {actorId: config.actorId, maxChargeUsd: cap, topics: queries.length, input}}];\n"
      },
      "id": "digest-5",
      "name": "Build run input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.apify.com/v2/acts/{{ $json.actorId }}/runs",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "waitForFinish",
              "value": "60"
            },
            {
              "name": "maxTotalChargeUsd",
              "value": "={{ $json.maxChargeUsd }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.input) }}",
        "options": {
          "timeout": 90000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "digest-6",
      "name": "Start run",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Polled once a minute; a run that is not finished after about 90 polls stops the digest.\nconst run = ($input.first().json || {}).data;\nif (!run || !/^[A-Za-z0-9]{17}$/.test(run.id || '')) throw new Error('Apify did not return a run.');\nconst done = ['SUCCEEDED', 'FAILED', 'ABORTED', 'TIMED-OUT'].includes(run.status);\nif (done && run.status !== 'SUCCEEDED') throw new Error(`Run ${run.id} ended ${run.status}; nothing was written. Open it in Apify Console.`);\nif (!done && $runIndex >= 90) throw new Error(`Run ${run.id} is still ${run.status} after about 90 minutes; nothing was written.`);\nif (done && ![run.defaultDatasetId, run.defaultKeyValueStoreId].every(id => /^[A-Za-z0-9]{17}$/.test(id || ''))) throw new Error('The run has no dataset or key-value store ID.');\nreturn [{json: {runId: run.id, status: run.status, done, datasetId: run.defaultDatasetId, storeId: run.defaultKeyValueStoreId, startedAt: run.startedAt || null, finishedAt: run.finishedAt || null}}];\n"
      },
      "id": "digest-7",
      "name": "Run state",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "run-finished",
              "leftValue": "={{ $json.done }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "digest-8",
      "name": "Run finished?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1320,
        300
      ]
    },
    {
      "parameters": {
        "url": "=https://api.apify.com/v2/actor-runs/{{ $json.runId }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "waitForFinish",
              "value": "60"
            }
          ]
        },
        "options": {
          "timeout": 90000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "digest-9",
      "name": "Wait for run",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        520
      ]
    },
    {
      "parameters": {
        "jsCode": "// The loop above runs several times; later nodes read this node, which runs once.\nreturn [{json: $input.first().json}];\n"
      },
      "id": "digest-10",
      "name": "Finished run",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1540,
        300
      ]
    },
    {
      "parameters": {
        "url": "=https://api.apify.com/v2/key-value-stores/{{ $json.storeId }}/records/RUN_SUMMARY",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 60000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "digest-11",
      "name": "Read run summary",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1760,
        300
      ]
    },
    {
      "parameters": {
        "url": "=https://api.apify.com/v2/datasets/{{ $('Finished run').first().json.datasetId }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 60000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "digest-12",
      "name": "Read dataset metadata",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1980,
        300
      ]
    },
    {
      "parameters": {
        "url": "=https://api.apify.com/v2/datasets/{{ $('Finished run').first().json.datasetId }}/items",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "format",
              "value": "json"
            },
            {
              "name": "clean",
              "value": "false"
            },
            {
              "name": "unwind",
              "value": "rows"
            }
          ]
        },
        "options": {
          "timeout": 120000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "digest-13",
      "name": "Read all rows",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2200,
        300
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configure').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Signals",
          "mode": "name"
        },
        "options": {}
      },
      "id": "digest-14",
      "name": "Read Signals",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2420,
        300
      ],
      "alwaysOutputData": true,
      "executeOnce": true
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configure').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Runs",
          "mode": "name"
        },
        "options": {}
      },
      "id": "digest-15",
      "name": "Read Runs",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2640,
        300
      ],
      "alwaysOutputData": true,
      "executeOnce": true
    },
    {
      "parameters": {
        "jsCode": "// Everything the digest writes, computed before any write: a failed check writes nothing.\nconst config = $('Configure').first().json;\nconst plan = $('Build run input').first().json;\nconst run = $('Finished run').first().json;\nconst report = $('Read run summary').first().json;\nconst meta = ($('Read dataset metadata').first().json || {}).data;\nif (!report || !report.completeness || !report.counters) throw new Error('RUN_SUMMARY has no completeness: this digest needs google-trends-monitor 1.10.16 or later.');\nif (!meta || meta.id !== run.datasetId || !Number.isInteger(meta.itemCount)) throw new Error('Missing dataset metadata; nothing was written.');\nif (meta.itemCount > 10000) throw new Error('The run stored more than 10,000 items, this template\\'s limit; nothing was written.');\nconst rows = $('Read all rows').all().flatMap(item => Array.isArray(item.json) ? item.json : [item.json]);\nif (rows.some(r => r.record_type === 'row_page')) throw new Error('Rows were not unwound (unwind=rows); nothing was written.');\nif (rows.length !== report.counters.output_writes) throw new Error(`Read ${rows.length} rows but the run wrote ${report.counters.output_writes}; nothing was written.`);\nconst statuses = rows.filter(r => r.record_type === 'group_status');\nconst missing = plan.input.queries.filter(q => !statuses.some(s => s.external_id === q.externalId));\nif (missing.length) throw new Error(`No status row for ${missing.map(q => q.externalId).join(', ')}; nothing was written.`);\nconst cell = value => value === null || value === undefined ? '' : (typeof value === 'object' ? JSON.stringify(value) : value);\nconst ALERTS = {BREAKOUT_NEW: 'New Breakout', RISING_QUERY_NEW: 'New rising query', RISING_QUERY_CHANGED: 'Growth level changed', TRENDING_TOPIC_NEW: 'New trending topic'};\nconst summary = rows.filter(r => r.record_type === 'keyword_summary').map(r => ({\n  summary_id: r.summary_id, run_id: run.runId, scraped_at: r.scraped_at, external_id: cell(r.external_id),\n  keyword: r.keyword, geo: r.geo, timeframe: r.timeframe, summary_status: r.summary_status,\n  direction: cell(r.direction), change_pct: cell(r.change_pct), latest_value: cell(r.latest_value),\n  latest_at: cell(r.latest_at), peak_value: cell(r.peak_value), peak_at: cell(r.peak_at),\n  top_rising_query: cell(r.top_rising_query), top_rising_formatted: cell(r.top_rising_formatted),\n  explanation: r.explanation,\n}));\n// A signal already in the Signals tab keeps its first_seen_at and is not new.\nconst known = new Map($('Read Signals').all().map(item => item.json).filter(r => r.event_id).map(r => [String(r.event_id), r]));\nconst signals = new Map();\nfor (const r of rows) {\n  if (r.record_type !== 'observation' || !(r.change_type in ALERTS)) continue;\n  if (!r.event_id) throw new Error('A signal has no event_id: this digest needs google-trends-monitor 1.10.16 or later.');\n  const before = known.get(r.event_id);\n  signals.set(r.event_id, {\n    event_id: r.event_id, change_type: r.change_type, external_id: cell(r.external_id),\n    keyword: cell(r.keyword), query: cell(r.related_query ?? r.trend_query),\n    formatted_value: cell(r.formatted_value), geo: r.geo, timeframe: r.timeframe,\n    first_seen_at: before ? cell(before.first_seen_at) : r.scraped_at, last_seen_at: r.scraped_at,\n    last_run_id: run.runId, source_url: cell(r.source_url),\n  });\n}\nconst fresh = [...signals.values()].filter(s => !known.has(s.event_id));\nconst errors = statuses.filter(s => ['FAILED', 'CACHED', 'PARTIAL'].includes(s.status)).map(s => ({\n  error_key: `${run.runId}:${cell(s.external_id) || s.source_id}`, run_id: run.runId,\n  checked_at: s.scraped_at, external_id: cell(s.external_id), comparison_group: cell(s.comparison_group),\n  status: s.status, error_code: cell(s.error && s.error.code), error_message: cell(s.error && s.error.message),\n  data_freshness: cell(s.data_freshness), last_verified_at: cell(s.last_verified_at),\n}));\nconst runs = $('Read Runs').all().map(item => item.json).filter(r => r.run_id);\nconst c = report.completeness;\nconst runRow = {\n  run_id: run.runId, started_at: cell(run.startedAt), finished_at: cell(run.finishedAt), completeness: c.text,\n  fresh_groups: c.freshGroups, cached_groups: c.cachedGroups, partial_groups: c.partialGroups,\n  failed_groups: c.failedGroups, not_checked_groups: c.notCheckedGroups, invalid_groups: c.invalidGroups,\n  new_signals: fresh.length, errors: errors.length, summaries: summary.length,\n};\nconst lines = [`Google Trends digest, ${String(run.finishedAt || '').slice(0, 10)}`, c.text];\nif (fresh.length) {\n  lines.push(`New signals: ${fresh.length}`);\n  for (const s of fresh.slice(0, 10)) {\n    const value = s.formatted_value && s.formatted_value !== 'Breakout' ? ` ${s.formatted_value}` : '';\n    lines.push(`- ${ALERTS[s.change_type]}: \"${s.query}\"${value} (${s.external_id || s.keyword}, ${s.geo})`);\n  }\n  if (fresh.length > 10) lines.push(`...and ${fresh.length - 10} more in the Signals tab.`);\n} else if (!runs.length) {\n  const saved = c.freshGroups + c.partialGroups;\n  lines.push(`Baseline saved for ${saved} ${saved === 1 ? 'topic' : 'topics'}; alerts start with the next run.`);\n} else {\n  lines.push('No new signals since the last run.');\n}\nconst movers = summary.filter(s => s.summary_status === 'OK' && s.direction !== 'STABLE' && typeof s.change_pct === 'number')\n  .sort((a, b) => Math.abs(b.change_pct) - Math.abs(a.change_pct)).slice(0, 3);\nif (movers.length) lines.push(`Biggest changes: ${movers.map(s => `${s.keyword} ${s.change_pct > 0 ? '+' : ''}${s.change_pct}%`).join(', ')}`);\nif (errors.length) lines.push(`${errors.length} ${errors.length === 1 ? 'topic' : 'topics'} without full fresh data: see the Errors tab.`);\nlines.push(`https://docs.google.com/spreadsheets/d/${config.spreadsheetId}`);\n// Reprocessing a run already in the Runs tab updates the tabs but sends no second message.\nconst replay = runs.some(r => String(r.run_id) === run.runId);\nreturn [{json: {runId: run.runId, notify: !replay, text: lines.join('\\n'), summary, signals: [...signals.values()], errors, runRow}}];\n"
      },
      "id": "digest-16",
      "name": "Prepare digest",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2860,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "return $input.first().json.summary.map(json => ({json}));\n"
      },
      "id": "digest-17",
      "name": "Summary rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        0
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configure').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Summary",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "summary_id"
          ],
          "schema": [
            {
              "id": "summary_id",
              "displayName": "summary_id",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "run_id",
              "displayName": "run_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "scraped_at",
              "displayName": "scraped_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "external_id",
              "displayName": "external_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "keyword",
              "displayName": "keyword",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "geo",
              "displayName": "geo",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "timeframe",
              "displayName": "timeframe",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "summary_status",
              "displayName": "summary_status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "direction",
              "displayName": "direction",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "change_pct",
              "displayName": "change_pct",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "latest_value",
              "displayName": "latest_value",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "latest_at",
              "displayName": "latest_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "peak_value",
              "displayName": "peak_value",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "peak_at",
              "displayName": "peak_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "top_rising_query",
              "displayName": "top_rising_query",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "top_rising_formatted",
              "displayName": "top_rising_formatted",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "explanation",
              "displayName": "explanation",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {
          "cellFormat": "RAW",
          "handlingExtraData": "error"
        }
      },
      "id": "digest-18",
      "name": "Upsert Summary",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        3300,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "return $input.first().json.signals.map(json => ({json}));\n"
      },
      "id": "digest-19",
      "name": "Signals rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        200
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configure').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Signals",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "event_id"
          ],
          "schema": [
            {
              "id": "event_id",
              "displayName": "event_id",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "change_type",
              "displayName": "change_type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "external_id",
              "displayName": "external_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "keyword",
              "displayName": "keyword",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "query",
              "displayName": "query",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "formatted_value",
              "displayName": "formatted_value",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "geo",
              "displayName": "geo",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "timeframe",
              "displayName": "timeframe",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "first_seen_at",
              "displayName": "first_seen_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "last_seen_at",
              "displayName": "last_seen_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "last_run_id",
              "displayName": "last_run_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "source_url",
              "displayName": "source_url",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {
          "cellFormat": "RAW",
          "handlingExtraData": "error"
        }
      },
      "id": "digest-20",
      "name": "Upsert Signals",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        3300,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "return $input.first().json.errors.map(json => ({json}));\n"
      },
      "id": "digest-21",
      "name": "Errors rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        400
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configure').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Errors",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "error_key"
          ],
          "schema": [
            {
              "id": "error_key",
              "displayName": "error_key",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "run_id",
              "displayName": "run_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "checked_at",
              "displayName": "checked_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "external_id",
              "displayName": "external_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "comparison_group",
              "displayName": "comparison_group",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "error_code",
              "displayName": "error_code",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "error_message",
              "displayName": "error_message",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "data_freshness",
              "displayName": "data_freshness",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "last_verified_at",
              "displayName": "last_verified_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {
          "cellFormat": "RAW",
          "handlingExtraData": "error"
        }
      },
      "id": "digest-22",
      "name": "Upsert Errors",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        3300,
        400
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{json: $input.first().json.runRow}];\n"
      },
      "id": "digest-23",
      "name": "Runs rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        600
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configure').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Runs",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "run_id"
          ],
          "schema": [
            {
              "id": "run_id",
              "displayName": "run_id",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "started_at",
              "displayName": "started_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "finished_at",
              "displayName": "finished_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "completeness",
              "displayName": "completeness",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "fresh_groups",
              "displayName": "fresh_groups",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "cached_groups",
              "displayName": "cached_groups",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "partial_groups",
              "displayName": "partial_groups",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "failed_groups",
              "displayName": "failed_groups",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "not_checked_groups",
              "displayName": "not_checked_groups",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "invalid_groups",
              "displayName": "invalid_groups",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "new_signals",
              "displayName": "new_signals",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "errors",
              "displayName": "errors",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "summaries",
              "displayName": "summaries",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {
          "cellFormat": "RAW",
          "handlingExtraData": "error"
        }
      },
      "id": "digest-24",
      "name": "Upsert Runs",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        3300,
        600
      ]
    },
    {
      "parameters": {
        "jsCode": "// One message per run: reprocessing a run already in the Runs tab stays silent.\nconst digest = $('Prepare digest').first().json;\nreturn digest.notify ? [{json: {text: digest.text}}] : [];\n"
      },
      "id": "digest-25",
      "name": "Digest message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3520,
        600
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "#trends-digest",
          "mode": "name"
        },
        "text": "={{ $json.text }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "id": "digest-26",
      "name": "Send to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        3740,
        500
      ]
    },
    {
      "parameters": {
        "fromEmail": "REPLACE_WITH_SENDER@example.com",
        "toEmail": "REPLACE_WITH_YOUR_ADDRESS@example.com",
        "subject": "Google Trends digest",
        "emailFormat": "text",
        "text": "={{ $json.text }}",
        "options": {}
      },
      "id": "digest-27",
      "name": "Send email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        3740,
        700
      ],
      "disabled": true
    },
    {
      "parameters": {
        "content": "## Google Trends daily digest\n1. Import the sheet template into Google Sheets and list your topics in Topics: keywords (1-5, comma-separated), geo, externalId.\n2. Configure: spreadsheetId, a stable monitorId, maxChargeUsd (the most one run may cost).\n3. Every HTTP Request node: an HTTP Header Auth credential, Authorization / Bearer YOUR_APIFY_TOKEN. Store the token only in credentials.\n4. Select your Google Sheets credential on every Google Sheets node and your Slack credential (or enable Send email instead).\n5. Execute once: the first run saves a baseline. Then activate: it runs daily at 06:17.\n\nEach run starts google-trends-monitor in monitor + changesOnly mode, waits for it, and writes Summary (by summary_id), Signals (by event_id), Errors and Runs. Nothing is written unless every row of the run was read. A run that did not succeed stops the workflow. Reprocessing a run updates the same rows and sends no second message.",
        "height": 560,
        "width": 700
      },
      "id": "digest-28",
      "name": "Setup",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        700
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "Prepare digest": {
      "main": [
        [
          {
            "node": "Summary rows",
            "type": "main",
            "index": 0
          },
          {
            "node": "Signals rows",
            "type": "main",
            "index": 0
          },
          {
            "node": "Errors rows",
            "type": "main",
            "index": 0
          },
          {
            "node": "Runs rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Summary rows": {
      "main": [
        [
          {
            "node": "Upsert Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Signals rows": {
      "main": [
        [
          {
            "node": "Upsert Signals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Errors rows": {
      "main": [
        [
          {
            "node": "Upsert Errors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Runs rows": {
      "main": [
        [
          {
            "node": "Upsert Runs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every day at 06:17": {
      "main": [
        [
          {
            "node": "Configure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual run": {
      "main": [
        [
          {
            "node": "Configure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure": {
      "main": [
        [
          {
            "node": "Read topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read topics": {
      "main": [
        [
          {
            "node": "Build run input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build run input": {
      "main": [
        [
          {
            "node": "Start run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start run": {
      "main": [
        [
          {
            "node": "Run state",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run state": {
      "main": [
        [
          {
            "node": "Run finished?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Finished run": {
      "main": [
        [
          {
            "node": "Read run summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read run summary": {
      "main": [
        [
          {
            "node": "Read dataset metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read dataset metadata": {
      "main": [
        [
          {
            "node": "Read all rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read all rows": {
      "main": [
        [
          {
            "node": "Read Signals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Signals": {
      "main": [
        [
          {
            "node": "Read Runs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Runs": {
      "main": [
        [
          {
            "node": "Prepare digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert Runs": {
      "main": [
        [
          {
            "node": "Digest message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for run": {
      "main": [
        [
          {
            "node": "Run state",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run finished?": {
      "main": [
        [
          {
            "node": "Finished run",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait for run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Digest message": {
      "main": [
        [
          {
            "node": "Send to Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": []
}
