{
  "name": "Busca Produtos - Query Expansion + Rerank IA",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "busca-produtos",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Webhook - Receber Carrinho",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "mode": "jsonata",
        "jsonataExpression": "carrinho"
      },
      "name": "Loop Items - Cada Produto",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "model": "gpt-3.5-turbo",
        "messages": {
          "values": [
            {
              "content": "=Voc\u00ea receber\u00e1 um termo de busca e sua tarefa \u00e9 gerar 15 varia\u00e7\u00f5es que se assemelhem \u00e0 linguagem de cat\u00e1logo.\n\nProduto: \"{{ $json.produto }}\"\nMarca: \"{{ $json.marca || 'n\u00e3o fornecida' }}\"\n\nRegras:\n- Gere 15 varia\u00e7\u00f5es por produto\n- Priorize sin\u00f4nimos, abrevia\u00e7\u00f5es e jarg\u00f5es\n  Exemplo: \"cotonete\" -> \"HASTES FLEXIVEIS\"\n  Exemplo: \"macaxeira\" -> \"AIPIM\", \"MANDIOCA\"\n- Varia\u00e7\u00f5es em MAI\u00daSCULAS\n- N\u00c3O adicione tipos espec\u00edficos\n  \u274c Errado: laranja -> LARANJA PERA\n  \u2705 Certo: laranja -> FRUTA LARANJA, CITRUS\n- Se marca fornecida, inclua nas varia\u00e7\u00f5es\n\nRetorne APENAS array JSON:\n[\"VARIA\u00c7\u00c3O 1\", \"VARIA\u00c7\u00c3O 2\", ...]"
            }
          ]
        },
        "options": {
          "temperature": 0.7,
          "maxTokens": 150
        }
      },
      "name": "OpenAI - Expandir Query",
      "type": "n8n-nodes-base.openAi",
      "typeVersion": 1,
      "position": [
        680,
        300
      ],
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "functionCode": "// Parsear resposta OpenAI\nconst resposta = $input.item.json.message?.content || '[]';\nlet queries = [];\n\ntry {\n  // Tentar extrair array JSON\n  const match = resposta.match(/\\[.*\\]/s);\n  if (match) {\n    queries = JSON.parse(match[0]);\n  }\n} catch (e) {\n  // Fallback: usar query original\n  queries = [$input.item.json.produto.toUpperCase()];\n}\n\n// Garantir que temos queries\nif (queries.length === 0) {\n  queries = [$input.item.json.produto.toUpperCase()];\n}\n\n// Adicionar query original se n\u00e3o estiver na lista\nconst original = $input.item.json.produto.toUpperCase();\nif (!queries.includes(original)) {\n  queries.unshift(original);\n}\n\nreturn {\n  json: {\n    produto_original: $input.item.json.produto,\n    marca_original: $input.item.json.marca,\n    queries_expandidas: queries\n  }\n};"
      },
      "name": "Code - Parse Expans\u00e3o",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:3003/batch-search",
        "jsonParameters": true,
        "options": {
          "timeout": 30000
        },
        "bodyParametersJson": "={\n  \"queries\": {{ $json.queries_expandidas.map(q => ({ query: q, produto_original: $json.produto_original })) }},\n  \"loja\": {{ $node[\"Webhook - Receber Carrinho\"].json.loja }},\n  \"limit\": 20,\n  \"incluir_sem_estoque\": false\n}"
      },
      "name": "HTTP - Batch Search Qdrant",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        1120,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "// Agregar todos os produtos das queries expandidas\nconst resultados = $input.item.json.resultados || [];\nconst produtosUnicos = new Map();\n\n// Coletar todos os produtos\nfor (const resultado of resultados) {\n  for (const produto of resultado.produtos || []) {\n    // Deduplic por ID, mantendo o de maior score\n    const existing = produtosUnicos.get(produto.id);\n    if (!existing || produto.score > existing.score) {\n      produtosUnicos.set(produto.id, produto);\n    }\n  }\n}\n\n// Converter para array e ordenar por score\nconst produtosArray = Array.from(produtosUnicos.values());\nprodutosArray.sort((a, b) => b.score - a.score);\n\nreturn {\n  json: {\n    produto_original: $input.item.json.produto_original,\n    marca_original: $input.item.json.marca_original,\n    total_candidatos: produtosArray.length,\n    candidatos: produtosArray\n  }\n};"
      },
      "name": "Code - Agregar Resultados",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1340,
        300
      ]
    },
    {
      "parameters": {
        "model": "gpt-3.5-turbo",
        "messages": {
          "values": [
            {
              "content": "=Voc\u00ea receber\u00e1 um produto solicitado e uma lista de op\u00e7\u00f5es. Identifique os 5 produtos mais relevantes.\n\n<pedido do usu\u00e1rio>\nProduto: {{ $json.produto_original }}\n{{ $json.marca_original ? `Marca: ${$json.marca_original}` : '' }}\n</pedido do usu\u00e1rio>\n\n<op\u00e7\u00f5es encontradas>\n{{ $json.candidatos.map((p, i) => `${i+1}. ${p.nome} - ${p.marca || 'Sem marca'} (R$ ${p.preco_final?.toFixed(2) || '?'}, estoque: ${p.estoque_total || 0})`).join('\\n') }}\n</op\u00e7\u00f5es encontradas>\n\nIMPORTANTE:\n- Considere sin\u00f4nimos e varia\u00e7\u00f5es\n- Priorize nome mais pr\u00f3ximo\n- Considere marca se mencionada  \n- Priorize mais estoque e menor pre\u00e7o quando relev\u00e2ncia igual\n\nRetorne APENAS array JSON com n\u00fameros dos 5 melhores:\n[1, 5, 8, 12, 15]"
            }
          ]
        },
        "options": {
          "temperature": 0.3,
          "maxTokens": 100
        }
      },
      "name": "OpenAI - Rerank com LLM",
      "type": "n8n-nodes-base.openAi",
      "typeVersion": 1,
      "position": [
        1560,
        300
      ],
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "functionCode": "// Extrair top 5 usando \u00edndices do LLM\nconst candidatos = $input.item.json.candidatos || [];\nconst respostaLLM = $input.item.json.message?.content || '[]';\n\nlet indices = [];\ntry {\n  const match = respostaLLM.match(/\\[(\\d+(?:,\\s*\\d+)*)\\]/);\n  if (match) {\n    indices = match[1].split(',').map(n => parseInt(n.trim()));\n  }\n} catch (e) {\n  console.log('Erro parse rerank:', e.message);\n}\n\n// Fallback: se n\u00e3o conseguiu parsear, pegar os 5 primeiros\nif (indices.length === 0) {\n  indices = [1, 2, 3, 4, 5];\n}\n\n// Converter \u00edndices (1-based) para produtos\nconst topProdutos = indices\n  .slice(0, 5) // Garantir no m\u00e1ximo 5\n  .map(idx => candidatos[idx - 1])\n  .filter(p => p !== undefined);\n\nreturn {\n  json: {\n    produto_original: $input.item.json.produto_original,\n    marca_original: $input.item.json.marca_original,\n    total_encontrados: candidatos.length,\n    top_5: topProdutos\n  }\n};"
      },
      "name": "Code - Extrair Top 5",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1780,
        300
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ \n  {\n    success: true,\n    loja: $node['Webhook - Receber Carrinho'].json.loja,\n    total_itens_carrinho: $node['Webhook - Receber Carrinho'].json.carrinho.length,\n    resultados: $items().map(item => item.json)\n  }\n}}"
      },
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        2000,
        300
      ]
    }
  ],
  "connections": {
    "Webhook - Receber Carrinho": {
      "main": [
        [
          {
            "node": "Loop Items - Cada Produto",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Items - Cada Produto": {
      "main": [
        [
          {
            "node": "OpenAI - Expandir Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI - Expandir Query": {
      "main": [
        [
          {
            "node": "Code - Parse Expans\u00e3o",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - Parse Expans\u00e3o": {
      "main": [
        [
          {
            "node": "HTTP - Batch Search Qdrant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP - Batch Search Qdrant": {
      "main": [
        [
          {
            "node": "Code - Agregar Resultados",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - Agregar Resultados": {
      "main": [
        [
          {
            "node": "OpenAI - Rerank com LLM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI - Rerank com LLM": {
      "main": [
        [
          {
            "node": "Code - Extrair Top 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - Extrair Top 5": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {},
  "staticData": null,
  "tags": []
}