{
  "name": "GSC Bi-Weekly SEO-Report mit Claude",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 7 1,15 * *"
            }
          ]
        }
      },
      "name": "Cron – 1. und 15.",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        -1168,
        144
      ],
      "id": "6fabf25e-4b7b-42c0-854c-2b4b74deddb1"
    },
    {
      "parameters": {
        "jsCode": "// PASSE AN: deine GSC-Property (Domain-Property: 'sc-domain:...', URL-Property: 'https://www.deine-domain.de/')\nconst PROPERTY = 'sc-domain:deine-domain.de';\n\nfunction fmt(date) {\n  return date.toISOString().split('T')[0];\n}\nconst today = new Date();\nconst delay = 3;\nconst endCurrent   = new Date(today); endCurrent.setDate(today.getDate() - delay);\nconst startCurrent = new Date(endCurrent); startCurrent.setDate(endCurrent.getDate() - 29);\nconst endPrev      = new Date(startCurrent); endPrev.setDate(startCurrent.getDate() - 1);\nconst startPrev    = new Date(endPrev); startPrev.setDate(endPrev.getDate() - 29);\nconst startYoy     = new Date(startCurrent); startYoy.setDate(startCurrent.getDate() - 365);\nconst endYoy       = new Date(endCurrent); endYoy.setDate(endCurrent.getDate() - 365);\nreturn [{ json: {\n  property: PROPERTY,\n  domain: PROPERTY.replace('sc-domain:', ''),\n  current:    { start: fmt(startCurrent), end: fmt(endCurrent) },\n  prev_month: { start: fmt(startPrev),    end: fmt(endPrev) },\n  prev_year:  { start: fmt(startYoy),     end: fmt(endYoy) }\n}}];"
      },
      "name": "Datumsbereiche berechnen",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        -944,
        144
      ],
      "id": "1c821911-be3d-4a7c-8dd5-63c3ffc1b981"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://searchconsole.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Datumsbereiche berechnen').item.json.property) }}/searchAnalytics/query",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSearchConsoleOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"startDate\": \"{{ $('Datumsbereiche berechnen').item.json.current.start }}\",\n  \"endDate\": \"{{ $('Datumsbereiche berechnen').item.json.current.end }}\",\n  \"dimensions\": [\"query\"],\n  \"rowLimit\": 2000\n}",
        "options": {}
      },
      "name": "GSC – Queries aktuell",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        -704,
        -64
      ],
      "id": "2fa40d77-9c2a-498f-9db8-eafa868ed350",
      "credentials": {
        "googleSearchConsoleOAuth2Api": {
          "name": "Google Search Console account"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://searchconsole.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Datumsbereiche berechnen').item.json.property) }}/searchAnalytics/query",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSearchConsoleOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"startDate\": \"{{ $('Datumsbereiche berechnen').item.json.current.start }}\",\n  \"endDate\": \"{{ $('Datumsbereiche berechnen').item.json.current.end }}\",\n  \"dimensions\": [\"page\"],\n  \"rowLimit\": 2000\n}",
        "options": {}
      },
      "name": "GSC – Pages aktuell",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        -704,
        80
      ],
      "id": "106a4b75-9139-4e04-8513-28f1520626ea",
      "credentials": {
        "googleSearchConsoleOAuth2Api": {
          "name": "Google Search Console account"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://searchconsole.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Datumsbereiche berechnen').item.json.property) }}/searchAnalytics/query",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSearchConsoleOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"startDate\": \"{{ $('Datumsbereiche berechnen').item.json.prev_month.start }}\",\n  \"endDate\": \"{{ $('Datumsbereiche berechnen').item.json.prev_month.end }}\",\n  \"dimensions\": [\"page\"],\n  \"rowLimit\": 2000\n}",
        "options": {}
      },
      "name": "GSC – Pages Vormonat",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        -704,
        224
      ],
      "id": "f0abfc33-4664-4bc8-9a77-a7c128e7cb1d",
      "credentials": {
        "googleSearchConsoleOAuth2Api": {
          "name": "Google Search Console account"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://searchconsole.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Datumsbereiche berechnen').item.json.property) }}/searchAnalytics/query",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSearchConsoleOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"startDate\": \"{{ $('Datumsbereiche berechnen').item.json.prev_year.start }}\",\n  \"endDate\": \"{{ $('Datumsbereiche berechnen').item.json.prev_year.end }}\",\n  \"dimensions\": [\"page\"],\n  \"rowLimit\": 2000\n}",
        "options": {}
      },
      "name": "GSC – Pages Vorjahr",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        -704,
        368
      ],
      "id": "472b894a-3b8e-49a3-96da-02d4a9c1c95a",
      "credentials": {
        "googleSearchConsoleOAuth2Api": {
          "name": "Google Search Console account"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://searchconsole.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Datumsbereiche berechnen').item.json.property) }}/searchAnalytics/query",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSearchConsoleOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"startDate\": \"{{ $('Datumsbereiche berechnen').item.json.current.start }}\",\n  \"endDate\": \"{{ $('Datumsbereiche berechnen').item.json.current.end }}\",\n  \"dimensions\": [\"query\", \"page\"],\n  \"rowLimit\": 3000\n}",
        "options": {}
      },
      "name": "GSC – Query+Page aktuell",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        -704,
        544
      ],
      "id": "e1ec0991-6a32-46cd-99ca-3c44205882c6",
      "credentials": {
        "googleSearchConsoleOAuth2Api": {
          "name": "Google Search Console account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// PASSE AN: URL-Muster deines Shopsystems (hier: OXID-Stil, PDPs enden auf .html)\nconst PAGE_TYPE_PATTERNS = {\n  'Produktdetailseiten':     ['.html'],\n  'Upper Funnel / Ratgeber': ['/ratgeber/'],\n  'Marken / Brand-Seiten':   ['/marken/'],\n  'Statische Seiten':        ['/kontakt', '/ueber-uns', '/about', '/faq', '/service/', '/impressum', '/datenschutz', '/agb'],\n};\n\nfunction classifyUrl(url) {\n  const u = url.toLowerCase();\n  for (const [type, patterns] of Object.entries(PAGE_TYPE_PATTERNS)) {\n    if (patterns.some(p => u.includes(p))) return type;\n  }\n  return 'Kategorieseiten';\n}\n\n// Letzten sinnvollen Slug aus URL extrahieren\n// z.B. https://www.deine-domain.de/kategorie/art/produkt-slug.html → \"produkt-slug.html\"\n// https://www.deine-domain.de/kategorie/ → \"/kategorie/\"\nfunction extractSlug(url) {\n  try {\n    const path = new URL(url).pathname;\n    const parts = path.split('/').filter(Boolean);\n    if (parts.length === 0) return '/';\n    // Bei PDPs: letztes Segment (produktname.html)\n    if (path.endsWith('.html')) return parts[parts.length - 1];\n    // Bei Kategorien: letzten zwei Segmente für Kontext\n    if (parts.length >= 2) return '/' + parts[parts.length - 2] + '/' + parts[parts.length - 1] + '/';\n    return '/' + parts[0] + '/';\n  } catch(e) {\n    return url;\n  }\n}\n\nconst queriesRaw  = $('GSC – Queries aktuell').first().json.rows || [];\nconst pagesRaw    = $('GSC – Pages aktuell').first().json.rows || [];\nconst pagesPrev   = $('GSC – Pages Vormonat').first().json.rows || [];\nconst pagesYoy    = $('GSC – Pages Vorjahr').first().json.rows || [];\nconst qpRaw       = $('GSC – Query+Page aktuell').first().json.rows || [];\nconst dates       = $('Datumsbereiche berechnen').first().json;\n\nconst sumClicks = arr => arr.reduce((s, r) => s + (r.clicks || 0), 0);\nconst sumImpr   = arr => arr.reduce((s, r) => s + (r.impressions || 0), 0);\nconst pct = (curr, prev) => prev > 0 ? Math.round(((curr - prev) / prev) * 1000) / 10 : null;\n\nconst trafficSummary = {\n  current:    { clicks: sumClicks(pagesRaw),  impressions: sumImpr(pagesRaw) },\n  prev_month: { clicks: sumClicks(pagesPrev), impressions: sumImpr(pagesPrev) },\n  prev_year:  { clicks: sumClicks(pagesYoy),  impressions: sumImpr(pagesYoy) },\n  pct_vs_prev_month: {\n    clicks:      pct(sumClicks(pagesRaw), sumClicks(pagesPrev)),\n    impressions: pct(sumImpr(pagesRaw),  sumImpr(pagesPrev))\n  },\n  pct_vs_prev_year: {\n    clicks:      pct(sumClicks(pagesRaw), sumClicks(pagesYoy)),\n    impressions: pct(sumImpr(pagesRaw),  sumImpr(pagesYoy))\n  },\n  period: dates.current\n};\n\n// Seitentyp-Aggregate\nconst byType = {};\nfor (const row of pagesRaw) {\n  const type = classifyUrl(row.keys[0]);\n  if (!byType[type]) byType[type] = { clicks: 0, impressions: 0, pages: [] };\n  byType[type].clicks      += row.clicks || 0;\n  byType[type].impressions += row.impressions || 0;\n  byType[type].pages.push({\n    url:      row.keys[0],\n    slug:     extractSlug(row.keys[0]),\n    clicks:   row.clicks,\n    position: Math.round((row.position||0)*10)/10\n  });\n}\n\nconst byTypePrev = {};\nfor (const row of pagesPrev) {\n  const type = classifyUrl(row.keys[0]);\n  if (!byTypePrev[type]) byTypePrev[type] = { clicks: 0 };\n  byTypePrev[type].clicks += row.clicks || 0;\n}\n\nconst pageTypeComparison = {};\nfor (const type of new Set([...Object.keys(byType), ...Object.keys(byTypePrev)])) {\n  const curr = byType[type]?.clicks || 0;\n  const prev = byTypePrev[type]?.clicks || 0;\n  pageTypeComparison[type] = {\n    clicks_current:   curr,\n    clicks_prev:      prev,\n    pct_change:       pct(curr, prev),\n    impressions:      byType[type]?.impressions || 0,\n    top_pages:        (byType[type]?.pages || []).sort((a,b) => b.clicks - a.clicks).slice(0, 5)\n  };\n}\n\n// Gewinner / Verlierer mit Prozent\nconst currentMap = {};\nfor (const r of pagesRaw) currentMap[r.keys[0]] = r;\nconst prevMap = {};\nfor (const r of pagesPrev) prevMap[r.keys[0]] = r;\n\nconst deltas = [];\nfor (const [url, curr] of Object.entries(currentMap)) {\n  const prev = prevMap[url];\n  if (prev) {\n    const deltaClicks = (curr.clicks || 0) - (prev.clicks || 0);\n    deltas.push({\n      url,\n      slug:           extractSlug(url),\n      page_type:      classifyUrl(url),\n      clicks_current: curr.clicks || 0,\n      clicks_prev:    prev.clicks || 0,\n      delta_clicks:   deltaClicks,\n      pct_change:     pct(curr.clicks || 0, prev.clicks || 0),\n      pos_current:    Math.round((curr.position||0)*10)/10,\n      pos_prev:       Math.round((prev.position||0)*10)/10,\n    });\n  }\n}\ndeltas.sort((a, b) => b.delta_clicks - a.delta_clicks);\n\n// Top Queries\nconst topQueries = [...queriesRaw]\n  .sort((a, b) => (b.clicks||0) - (a.clicks||0))\n  .slice(0, 100)  // Top 100 für Clustering\n  .map(r => ({\n    query:       r.keys[0],\n    clicks:      r.clicks||0,\n    impressions: r.impressions||0,\n    ctr:         Math.round((r.ctr||0)*10000)/100,\n    position:    Math.round((r.position||0)*10)/10\n  }));\n\n// Potenzial-Queries: Position 5-20, hohe Impressionen, wenig Klicks\nconst potentialQueries = queriesRaw\n  .filter(r => (r.impressions||0) > 50 && (r.clicks||0) < 10 && (r.position||0) > 4 && (r.position||0) < 20)\n  .map(r => ({ query: r.keys[0], impressions: r.impressions, clicks: r.clicks, ctr: Math.round((r.ctr||0)*10000)/100, position: Math.round((r.position||0)*10)/10 }))\n  .sort((a, b) => b.impressions - a.impressions)\n  .slice(0, 40);\n\n// NEU: Keywords mit hohen Impressionen aber kaum Ranking (Position > 20)\n// → noch nicht wirklich sichtbar, aber Nachfrage vorhanden\nconst hiddenKeywords = queriesRaw\n  .filter(r => (r.impressions||0) > 200 && (r.clicks||0) < 5 && (r.position||0) > 20)\n  .map(r => ({ query: r.keys[0], impressions: r.impressions, clicks: r.clicks || 0, position: Math.round((r.position||0)*10)/10 }))\n  .sort((a, b) => b.impressions - a.impressions)\n  .slice(0, 30);\n\n// Kategorie-Lücken: PDPs die für generische Begriffe ranken\n// Mit konkreter PDP-URL (Slug) damit klar ist WELCHES Produkt\nconst pdpGeneric = qpRaw\n  .filter(r =>\n    classifyUrl(r.keys[1]) === 'Produktdetailseiten' &&\n    (r.impressions||0) > 30 &&\n    (r.position||0) < 15 &&\n    r.keys[0].split(' ').length <= 3\n  )\n  .map(r => ({\n    query:       r.keys[0],\n    page:        r.keys[1],\n    slug:        extractSlug(r.keys[1]),  // Produktname.html\n    impressions: r.impressions,\n    clicks:      r.clicks || 0,\n    position:    Math.round((r.position||0)*10)/10\n  }))\n  .sort((a, b) => b.impressions - a.impressions)\n  .slice(0, 30);\n\nreturn [{ json: {\n  property:             dates.property,\n  report_period:        dates.current,\n  traffic_summary:      trafficSummary,\n  page_type_comparison: pageTypeComparison,\n  winners_losers: {\n    winners: deltas.slice(0, 10),\n    losers:  deltas.slice(-10).reverse()\n  },\n  top_100_queries:      topQueries,\n  potential_queries:    potentialQueries,\n  hidden_keywords:      hiddenKeywords,\n  pdp_ranking_generic:  pdpGeneric\n}}];"
      },
      "name": "Daten aufbereiten",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        -208,
        224
      ],
      "id": "5fdb5482-b4b2-4e7c-aef5-24844c8be581"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json) }}",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "name": "Claude API aufrufen",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        192,
        224
      ],
      "id": "feb6fe4b-271c-4fb4-9c68-bb7ee3907deb",
      "credentials": {
        "httpHeaderAuth": {
          "name": "Anthropic API (Header-Name: x-api-key)"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const analysisText = $json.content[0].text;\nconst dates    = $('Datumsbereiche berechnen').first().json;\nconst period   = `${dates.current.start} – ${dates.current.end}`;\nconst domain = dates.domain;\n\nfunction mdToHtml(text) {\n  const lines = text.split('\\n');\n  let html = '';\n  let inList = false;\n  for (const line of lines) {\n    if (line.startsWith('## ')) {\n      if (inList) { html += '</ul>'; inList = false; }\n      html += `<h2 style=\"color:#1a1a2e;margin-top:24px;border-bottom:2px solid #e8e8e8;padding-bottom:6px\">${line.slice(3)}</h2>`;\n    } else if (line.startsWith('- ')) {\n      if (!inList) { html += '<ul style=\"margin:8px 0;padding-left:20px\">'; inList = true; }\n      const item = line.slice(2).replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n      html += `<li style=\"margin:4px 0\">${item}</li>`;\n    } else if (line.startsWith('**') && line.endsWith('**')) {\n      if (inList) { html += '</ul>'; inList = false; }\n      html += `<p style=\"margin:10px 0 4px\"><strong>${line.slice(2,-2)}</strong></p>`;\n    } else if (line.trim() === '---') {\n      if (inList) { html += '</ul>'; inList = false; }\n      html += '<hr style=\"border:none;border-top:1px solid #e8e8e8;margin:16px 0\">';\n    } else if (line.trim() === '') {\n      if (inList) { html += '</ul>'; inList = false; }\n      html += '<br>';\n    } else {\n      if (inList) { html += '</ul>'; inList = false; }\n      const f = line.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n      html += `<p style=\"margin:6px 0\">${f}</p>`;\n    }\n  }\n  if (inList) html += '</ul>';\n  return html;\n}\n\nconst htmlBody = `\n<div style=\"font-family:Arial,sans-serif;max-width:800px;margin:0 auto;color:#333\">\n  <div style=\"background:#1a1a2e;color:white;padding:20px 24px;border-radius:8px 8px 0 0\">\n    <h1 style=\"margin:0;font-size:20px\">📊 GSC SEO-Report · ${domain}</h1>\n    <p style=\"margin:4px 0 0;opacity:0.8;font-size:14px\">${period}</p>\n  </div>\n  <div style=\"background:white;padding:24px;border:1px solid #e8e8e8;border-top:none;border-radius:0 0 8px 8px\">\n    ${mdToHtml(analysisText)}\n  </div>\n  <p style=\"text-align:center;color:#999;font-size:12px;margin-top:16px\">Automatisch generiert · Powered by Claude</p>\n</div>`;\n\nreturn [{ json: {\n  subject: `GSC SEO-Report | ${domain} | ${period}`,\n  htmlBody,\n  textBody: analysisText\n}}];"
      },
      "name": "Report als HTML aufbereiten",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        416,
        224
      ],
      "id": "56f95507-722e-4f6e-b506-beb961dabe6f"
    },
    {
      "parameters": {
        "sendTo": "=du@deine-domain.de",
        "subject": "={{ $json.subject }}",
        "message": "={{ $json.htmlBody }}",
        "options": {}
      },
      "name": "Gmail – Report senden",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        640,
        224
      ],
      "id": "465f3da8-c108-4227-b1cb-595d04940858",
      "credentials": {
        "gmailOAuth2": {
          "name": "Gmail account"
        }
      }
    },
    {
      "parameters": {
        "numberInputs": 5
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        -368,
        176
      ],
      "id": "fee64509-c251-47d1-9775-11fb3c1e89c4",
      "name": "Merge"
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json;\n\nconst prompt = `Du bist ein erfahrener E-Commerce SEO-Stratege. Analysiere diese GSC-Daten für ${$('Datumsbereiche berechnen').first().json.domain} (PASSE AN: dein Sortiment, deine Besonderheiten, dein Shopsystem) und erstelle einen strukturierten bi-weekly Report auf Deutsch.\n\nWICHTIGE HINWEISE FÜR DEINE ANALYSE:\n- Bei PDPs immer den Produktnamen (Slug) nennen, nicht nur die volle URL\n- Bei Gewinnern/Verlierern immer absolute Klickzahl UND Prozentveränderung angeben\n- Bei Tickets: konkrete URL/Slug nennen, konkrete Maßnahme (nicht generisch)\n- Saisonaler Kontext: PASSE AN, z. B. Frühjahr = Peak für Gartenprodukte, Q4 = Geschenkgeschäft\n- YoY-Rückgänge IMMER mit Kontext erklären: AI-Overviews, veränderte SERP-Layouts und Google Shopping Grids reduzieren organische Klicks strukturell — das ist ein Markttrend, kein Shop-Problem\n- Niemals Formulierungen wie \"liegt X% unter Vorjahr\" ohne Kontext-Einordnung\n\n## 1. Executive Summary\n2-3 prägnante Sätze: Was ist die wichtigste Entwicklung?\nDann: YoY-Gesamtentwicklung aufdröseln nach Seitentypen:\n- PDPs: X Klicks aktuell vs. Vorjahr (Differenz + %)\n- Kategorieseiten: X Klicks aktuell vs. Vorjahr (Differenz + %)\n- Ratgeber: X Klicks aktuell vs. Vorjahr (Differenz + %)\n- Marken/Brand: X Klicks aktuell vs. Vorjahr (Differenz + %)\nFalls YoY negativ: Hinweis dass strukturelle SERP-Veränderungen (AI Overviews, Shopping Grids, veränderte CTR-Kurven) besonders Upper-Funnel-Traffic reduzieren — das ist Marktstandard, nicht Shop-spezifisch.\n\n## 2. Traffic-Entwicklung\nNutze die vorberechneten Prozentwerte aus traffic_summary.\nFormat: \n\"Klicks: X (Vormonat: +Y% | YoY: +Z%) | Impressionen: X (Vormonat: +Y% | YoY: +Z%)\"\nEinordnung: Was bedeutet das für den Shop in der aktuellen Saison?\nFalls YoY negativ: strukturellen Kontext ergänzen (AI Search, SERP-Veränderungen).\n\n## 3. Seitentyp-Performance\nWICHTIG: Kennzeichne klar ob die Zahlen Vormonat-Vergleich oder YoY-Vergleich sind.\nFür jeden Seitentyp aus page_type_comparison:\nFormat: \"[Seitentyp]: [Klicks aktuell] | Vormonat: [Klicks prev] ([+/- %]) | YoY: [Tendenz falls erkennbar]\"\nMax. 2 Sätze Bewertung + Auffälligkeit pro Typ.\n\n## 4. Auffällige Query-Cluster\nGruppiere die top_100_queries nach Themen/Produktkategorien.\nPro Cluster: Cluster-Name | Anzahl Keywords | Gesamt-Klicks | Gesamt-Impressionen | Trend (+/- % Klicks)\n- Welche Cluster gewinnen gerade?\n- Welche verlieren?\n- Saisonale Auffälligkeiten?\nNenne pro Cluster 2-3 Beispiel-Queries mit Klicks und Impressionen.\n\n## 5. Top-Gewinner und -Verlierer\nJe 5 aus winners_losers. Format pro Eintrag:\n\"[Slug] | [Seitentyp] | [Klicks aktuell] vs. [Klicks vorher] ([+/- Delta] / [+/- %]) | Pos: [aktuell] vs. [vorher] | Hypothese: ...\"\nNenne bei PDPs den Produktnamen aus dem Slug.\n\n## 6. Keyword-Potenziale\n\n### 6a. Quick Wins (potential_queries)\nQueries auf Position 5-15 mit hohen Impressionen.\nFormat: \"[Query]\" | Pos. X | Impressionen Y | CTR Z% | Empfehlung: [konkrete Maßnahme]\n\n### 6b. Kategorie-Lücken (pdp_ranking_generic)\nPDPs die für generische Begriffe ranken → Hinweis auf fehlende oder schwache Kategorienseiten.\nFormat: \"[Slug/Produktname]\" rankt für \"[Query]\" (Pos. X, Y Impressionen) → Kategorie \"[Vorschlag]\" anlegen/stärken\nErkläre kurz warum eine Kategorie besser ranken würde.\nZUSÄTZLICH: Gibt es generische Keywords mit hohen Impressionen für die aktuell GAR KEINE Kategorie existiert? Diese separat als \"Fehlende Kategorien\" auflisten.\n\n### 6c. Noch nicht sichtbare Keywords (hidden_keywords)\nKeywords mit hohen Impressionen aber Position > 20.\nFormat: \"[Query]\" | Pos. X | Y Impressionen → [konkreter Handlungsvorschlag]\n\n### 6d. FAQ-Potenziale — FEHLENDE FRAGEN\nAnalysiere die Queries auf Fragen die mit \"wie\", \"welches\", \"was\", \"warum\", \"wann\", \"wie lange\", \"wie viel\" beginnen oder eindeutig informationell sind.\nTrenne in zwei Gruppen:\n1. Fragen für die die Domain bereits rankt aber noch keine optimale Antwortseite hat → FAQ ausbauen\n2. Fragen mit relevanten Impressionen für die die Domain noch NICHT rankt (Position > 30 oder gar nicht) → diese komplett auflisten als \"Fehlende FAQ-Inhalte\"\nFormat fehlende FAQs: \"[Frage]\" | Impressionen Y | Empfehlung: FAQ-Seite / Ratgeber-Artikel / PDP-FAQ\n\n## 7. Keyword-Clustering (top_100_queries)\nClustere die Top-100-Keywords nach Suchintention:\n- **Transactional** (kaufbereit, z.B. \"kaufen\", \"bestellen\", konkrete Produktnamen): Anzahl + Top-3-Beispiele + optimale Seite\n- **Commercial** (vergleichend, z.B. \"test\", \"vergleich\", \"beste\", \"empfehlung\"): Anzahl + Top-3-Beispiele + optimale Seite\n- **Informational** (Ratgeber, z.B. \"wie\", \"was\", \"aufbauen\", \"befüllen\"): Anzahl + Top-3-Beispiele + optimale Seite\n- **Navigational** (Brand/Shop-Navigation): Anzahl + Top-3-Beispiele\n\nPro Cluster: Welcher Seitentyp sollte optimiert/ausgebaut werden?\n\n## 8. Konkrete Handlungsempfehlungen (Tickets)\n4-6 Tickets, priorisiert nach Impact. Format:\n\n**[TITEL] | Priorität: [P0/Hoch/Mittel] | Typ: [SEO/Content/CRO/Technik]**\n- Status quo: [konkret — welcher Slug, welche Kennzahl, welches Problem]\n- Ziel: [messbar]\n- Maßnahme: [konkret und umsetzbar — nicht \"Content optimieren\" sondern WAS genau: H1 anpassen, FAQ-Modul mit X Fragen ergänzen, interne Verlinkung von Y auf Z aufbauen etc.]\n- SEO-Wirkung: [realistisch]\n\nGSC-DATEN:\n${JSON.stringify(data)}`;\n\nreturn [{\n  json: {\n    model: \"claude-sonnet-5\",\n    max_tokens: 8000,\n    messages: [\n      {\n        role: \"user\",\n        content: prompt\n      }\n    ]\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        0,
        224
      ],
      "id": "20fea8e1-6ad1-4346-81d7-335f09db457a",
      "name": "Claude-Prompt bauen"
    }
  ],
  "connections": {
    "Cron – 1. und 15.": {
      "main": [
        [
          {
            "node": "Datumsbereiche berechnen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Datumsbereiche berechnen": {
      "main": [
        [
          {
            "node": "GSC – Queries aktuell",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC – Pages aktuell",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC – Pages Vormonat",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC – Pages Vorjahr",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC – Query+Page aktuell",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC – Queries aktuell": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC – Pages aktuell": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "GSC – Pages Vormonat": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "GSC – Pages Vorjahr": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "GSC – Query+Page aktuell": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "Daten aufbereiten": {
      "main": [
        [
          {
            "node": "Claude-Prompt bauen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Report als HTML aufbereiten": {
      "main": [
        [
          {
            "node": "Gmail – Report senden",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Daten aufbereiten",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude-Prompt bauen": {
      "main": [
        [
          {
            "node": "Claude API aufrufen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude API aufrufen": {
      "main": [
        [
          {
            "node": "Report als HTML aufbereiten",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  }
}
