{
  "nodes": [
    {
      "id": "ba48d480-83b8-461a-869c-832413dd226d",
      "name": "Normalize Chat Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1880,
        320
      ],
      "alwaysOutputData": true,
      "parameters": {
        "jsCode": "const toStr = (v) => String(v ?? '').trim();\nconst norm = (v) => toStr(v)\n  .normalize('NFD')\n  .replace(/[\\u0300-\\u036f]/g, '')\n  .toLowerCase()\n  .replace(/\\s+/g, ' ')\n  .trim();\n\nconst getEnv = (key) => {\n  try {\n    if ($env && $env[key]) return String($env[key]).trim();\n  } catch (_e) {}\n  try {\n    const p = (typeof globalThis !== 'undefined' && globalThis.process && globalThis.process.env)\n      ? globalThis.process.env[key]\n      : '';\n    if (p) return String(p).trim();\n  } catch (_e) {}\n  return '';\n};\n\nconst getStore = () => {\n  if (typeof $getWorkflowStaticData === 'function') return $getWorkflowStaticData('global');\n  if (this && typeof this.getWorkflowStaticData === 'function') return this.getWorkflowStaticData('global');\n  throw new Error('[Normalize Chat Input] static data indisponivel no Code node.');\n};\n\nconst raw = $json || {};\nconst message = toStr(raw.chatInput || raw.message || raw.text || raw.input || '');\nconst sessionId = toStr(raw.sessionId || raw.session_id || raw.chatSessionId || raw.metadata?.sessionId || 'default');\n\nconst store = getStore();\nif (!store.chatMemory) store.chatMemory = {};\nif (!Array.isArray(store.chatMemory[sessionId])) store.chatMemory[sessionId] = [];\nif (!store.selectedOption) store.selectedOption = {};\nif (!store.impactIntakeState) store.impactIntakeState = {};\n\nconst pushMemory = (role, content) => {\n  const text = toStr(content);\n  if (!text) return;\n  store.chatMemory[sessionId].push({ role, content: text, at: new Date().toISOString() });\n  if (store.chatMemory[sessionId].length > 40) {\n    store.chatMemory[sessionId] = store.chatMemory[sessionId].slice(-40);\n  }\n};\n\nconst chatHistory = () => store.chatMemory[sessionId]\n  .slice(-12)\n  .map((t) => ({ role: t.role, content: toStr(t.content) }));\n\nconst menuText = [\n  'Escolha uma opcao para continuar (envie apenas 1, 2 ou 3):',\n  '1) Nova analise de Impacto',\n  '2) Duvida sobre funcionalidade ja implementada',\n  '3) Correcao de PRs abertas'\n].join('\\n');\n\nconst invalidOutput = 'op\u00e7\u00e3o inv\u00e1lida! Escolha 1,2 ou 3';\n\nif (!message) {\n  return [{\n    json: {\n      option: 'invalid',\n      route: 'impact_collect_question',\n      session_id: sessionId,\n      chat_history: chatHistory(),\n      output: menuText,\n    }\n  }];\n}\n\nconst lower = norm(message);\nif (lower === '/reset') {\n  delete store.selectedOption[sessionId];\n  delete store.impactIntakeState[sessionId];\n  return [{\n    json: {\n      option: 'invalid',\n      route: 'impact_collect_question',\n      session_id: sessionId,\n      chat_history: chatHistory(),\n      output: menuText,\n    }\n  }];\n}\n\nif (lower === '/menu') {\n  return [{\n    json: {\n      option: 'invalid',\n      route: 'impact_collect_question',\n      session_id: sessionId,\n      chat_history: chatHistory(),\n      output: menuText,\n    }\n  }];\n}\n\npushMemory('user', message);\n\nconst isOption = /^(1|2|3)$/.test(lower);\nif (isOption) {\n  const option = lower;\n  store.selectedOption[sessionId] = option;\n\n  if (option === '1') {\n    const currentState = store.impactIntakeState[sessionId] || {\n      problema: '',\n      mudanca: '',\n      evidencias: '',\n      restricoes: '',\n      status: '',\n      diagnostico: '',\n      evidencias_lista: []\n    };\n    store.impactIntakeState[sessionId] = currentState;\n\n    return [{\n      json: {\n        option: '1',\n        route: 'impact_intake',\n        is_selection: true,\n        session_id: sessionId,\n        chat_history: chatHistory(),\n        user_message: message,\n        question: message,\n        impact_intake_state: currentState,\n      }\n    }];\n  }\n\n  if (option === '2') {\n    return [{\n      json: {\n        option: '2',\n        route: 'pr_qa',\n        is_selection: true,\n        session_id: sessionId,\n        chat_history: chatHistory(),\n        output: 'Opcao 2 selecionada. Agora descreva sua duvida sobre funcionalidade ja implementada.',\n      }\n    }];\n  }\n\n  return [{\n    json: {\n      option: '3',\n      route: 'correction_propose',\n      is_selection: true,\n      session_id: sessionId,\n      chat_history: chatHistory(),\n      output: 'Opcao 3 selecionada. Agora descreva a correcao desejada nas PRs abertas.',\n    }\n  }];\n}\n\nconst selected = toStr(store.selectedOption[sessionId]);\nif (!selected) {\n  return [{\n    json: {\n      option: 'invalid',\n      route: 'impact_collect_question',\n      session_id: sessionId,\n      chat_history: chatHistory(),\n      output: invalidOutput,\n    }\n  }];\n}\n\nif (selected === '1') {\n  const currentState = store.impactIntakeState[sessionId] || {\n    problema: '',\n    mudanca: '',\n    evidencias: '',\n    restricoes: '',\n    status: '',\n    diagnostico: '',\n    evidencias_lista: []\n  };\n\n  return [{\n    json: {\n      option: '1',\n      route: 'impact_intake',\n      is_selection: false,\n      session_id: sessionId,\n      chat_history: chatHistory(),\n      user_message: message,\n      question: message,\n      impact_intake_state: currentState,\n    }\n  }];\n}\n\nif (selected === '2') {\n  return [{\n    json: {\n      option: '2',\n      route: 'pr_qa',\n      is_selection: false,\n      session_id: sessionId,\n      chat_history: chatHistory(),\n      question: message,\n      owner: toStr(getEnv('GITHUB_OWNER') || 'oliversoft-tech'),\n      github_token: toStr(getEnv('GITHUB_TOKEN') || ''),\n      llm_model: toStr(getEnv('LLM_MODEL') || 'claude-3-5-sonnet-20241022')\n    }\n  }];\n}\n\nif (selected === '3') {\n  return [{\n    json: {\n      option: '3',\n      route: 'correction_propose',\n      is_selection: false,\n      session_id: sessionId,\n      chat_history: chatHistory(),\n      question: message,\n      owner: toStr(getEnv('GITHUB_OWNER') || 'oliversoft-tech'),\n      github_token: toStr(getEnv('GITHUB_TOKEN') || ''),\n      llm_model: toStr(getEnv('LLM_MODEL') || 'claude-3-5-sonnet-20241022')\n    }\n  }];\n}\n\nreturn [{\n  json: {\n    option: 'invalid',\n    route: 'impact_collect_question',\n    session_id: sessionId,\n    chat_history: chatHistory(),\n    output: invalidOutput,\n  }\n}];\n"
      }
    },
    {
      "id": "1c5326bb-0f6d-4a3b-8e4e-4d4cb53ca111",
      "name": "Switch Option",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 1,
      "position": [
        -1620,
        320
      ],
      "parameters": {
        "dataType": "string",
        "value1": "={{$json.option}}",
        "rules": [
          {
            "operation": "equal",
            "value2": "1"
          },
          {
            "operation": "equal",
            "value2": "2"
          },
          {
            "operation": "equal",
            "value2": "3"
          }
        ]
      }
    },
    {
      "id": "c78b5043-4f88-4d0f-a7e2-f4b7fdc2f2b4",
      "name": "Switch Option 2 Step",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 1,
      "position": [
        -1360,
        900
      ],
      "parameters": {
        "dataType": "string",
        "value1": "={{$json.is_selection ? 'selection' : 'message'}}",
        "rules": [
          {
            "operation": "equal",
            "value2": "selection"
          },
          {
            "operation": "equal",
            "value2": "message"
          }
        ]
      }
    },
    {
      "id": "0f52b6cf-ef20-4d4f-8a98-ec1a5ed6e9a4",
      "name": "Switch Option 3 Step",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 1,
      "position": [
        -1360,
        620
      ],
      "parameters": {
        "dataType": "string",
        "value1": "={{$json.is_selection ? 'selection' : 'message'}}",
        "rules": [
          {
            "operation": "equal",
            "value2": "selection"
          },
          {
            "operation": "equal",
            "value2": "message"
          }
        ]
      }
    },
    {
      "id": "d8358c60-3476-4bd0-944f-91558e3fd7ef",
      "name": "Basic LLM Chain (Impact Intake)",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        -1360,
        280
      ],
      "parameters": {
        "promptType": "define",
        "text": "={{[\n  'Voce e um assistente de intake para Impact Analysis.',\n  'Seu objetivo e coletar os campos abaixo com exemplos claros:',\n  '- problema',\n  '- mudanca',\n  '- evidencias',\n  '- restricoes',\n  '- status',\n  '- diagnostico',\n  '- evidencias_lista (array de strings)',\n  '',\n  'Retorne SOMENTE JSON valido sem markdown, neste formato exato:',\n  '{',\n  '  \"complete\": true|false,',\n  '  \"reply\": \"string\",',\n  '  \"updates\": {',\n  '    \"problema\": \"string\",',\n  '    \"mudanca\": \"string\",',\n  '    \"evidencias\": \"string\",',\n  '    \"restricoes\": \"string\",',\n  '    \"status\": \"string\",',\n  '    \"diagnostico\": \"string\",',\n  '    \"evidencias_lista\": [\"string\"]',\n  '  }',\n  '}',\n  '',\n  'Regras de comportamento:',\n  '1) Se faltarem campos obrigatorios (problema,mudanca,evidencias,restricoes), reply deve perguntar objetivamente o proximo campo e incluir exemplo.',\n  '2) Se o usuario ja tiver fornecido valor para algum campo, preserve e apenas complemente.',\n  '3) complete=true somente quando os 4 campos obrigatorios estiverem preenchidos.',\n  '4) Nao invente dados fora do que o usuario informou.',\n  '',\n  'Estado atual (JSON):',\n  JSON.stringify($json.impact_intake_state || {}, null, 2),\n  '',\n  'Historico recente (JSON):',\n  JSON.stringify($json.chat_history || [], null, 2),\n  '',\n  'Mensagem atual do usuario:',\n  $json.user_message || $json.question || ''\n].join('\\n')}}",
        "hasOutputParser": false,
        "messages": {
          "messageValues": [
            {
              "message": "Coletar dados para Impact Analysis de forma objetiva e incremental. Sempre responder em JSON valido conforme schema solicitado."
            }
          ]
        },
        "batching": {
          "batchSize": 1
        }
      }
    },
    {
      "id": "5ea171f9-11eb-4eb8-bcb8-3cc7e9e7a458",
      "name": "Anthropic Chat Model - Impact Intake",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.2,
      "position": [
        -1580,
        170
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-3-5-sonnet-20241022"
        },
        "options": {
          "temperature": 0,
          "maxTokens": 900
        }
      }
    },
    {
      "id": "44e2ce8e-6a3e-4ba3-a2c6-0b8e93eae382",
      "name": "Parse Impact Intake Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1100,
        280
      ],
      "alwaysOutputData": true,
      "parameters": {
        "jsCode": "const toStr = (v) => String(v ?? '').trim();\n\nconst rawText = toStr($json.text || $json.response?.text || '');\nif (!rawText) throw new Error('[Parse Impact Intake Response] LLM retornou texto vazio.');\n\nconst tryParse = (txt) => {\n  const candidates = [txt];\n  const fenced = txt.match(/```(?:json)?\\s*([\\s\\S]*?)```/i);\n  if (fenced && fenced[1]) candidates.push(toStr(fenced[1]));\n  for (const c of candidates) {\n    try {\n      return JSON.parse(c);\n    } catch (_e) {}\n  }\n  throw new Error('[Parse Impact Intake Response] JSON invalido no retorno do LLM.');\n};\n\nconst parsed = tryParse(rawText);\nconst updates = parsed && typeof parsed === 'object' ? (parsed.updates || {}) : {};\n\nconst fromNormalize = (() => {\n  try { return $items('Normalize Chat Input', 0, 0)[0]?.json || {}; } catch (_e) { return {}; }\n})();\n\nconst sessionId = toStr(fromNormalize.session_id || 'default');\nconst getStore = () => {\n  if (typeof $getWorkflowStaticData === 'function') return $getWorkflowStaticData('global');\n  if (this && typeof this.getWorkflowStaticData === 'function') return this.getWorkflowStaticData('global');\n  throw new Error('[Parse Impact Intake Response] static data indisponivel no Code node.');\n};\n\nconst store = getStore();\nif (!store.impactIntakeState) store.impactIntakeState = {};\nif (!store.selectedOption) store.selectedOption = {};\n\nconst base = store.impactIntakeState[sessionId] || {\n  problema: '',\n  mudanca: '',\n  evidencias: '',\n  restricoes: '',\n  status: '',\n  diagnostico: '',\n  evidencias_lista: []\n};\n\nconst pickStr = (a, b) => {\n  const x = toStr(a);\n  if (x) return x;\n  return toStr(b);\n};\n\nbase.problema = pickStr(updates.problema, parsed.problema || base.problema);\nbase.mudanca = pickStr(updates.mudanca, parsed.mudanca || base.mudanca);\nbase.evidencias = pickStr(updates.evidencias, parsed.evidencias || base.evidencias);\nbase.restricoes = pickStr(updates.restricoes, parsed.restricoes || base.restricoes);\nbase.status = pickStr(updates.status, parsed.status || base.status);\nbase.diagnostico = pickStr(updates.diagnostico, parsed.diagnostico || base.diagnostico);\n\nconst listA = Array.isArray(base.evidencias_lista) ? base.evidencias_lista : [];\nconst listB = Array.isArray(updates.evidencias_lista) ? updates.evidencias_lista : (Array.isArray(parsed.evidencias_lista) ? parsed.evidencias_lista : []);\nbase.evidencias_lista = [...new Set([...listA, ...listB].map((x) => toStr(x)).filter(Boolean))];\n\nstore.impactIntakeState[sessionId] = base;\n\nconst required = ['problema', 'mudanca', 'evidencias', 'restricoes'];\nconst missing = required.filter((k) => !toStr(base[k]));\nconst complete = missing.length === 0;\n\nif (complete) {\n  delete store.selectedOption[sessionId];\n  delete store.impactIntakeState[sessionId];\n\n  return [{\n    json: {\n      route: 'impact_run_manual',\n      session_id: sessionId,\n      impact_payload: {\n        problema: toStr(base.problema),\n        mudanca: toStr(base.mudanca),\n        evidencias: toStr(base.evidencias),\n        restricoes: toStr(base.restricoes),\n      },\n      status: toStr(base.status),\n      diagnostico: toStr(base.diagnostico),\n      evidencias_lista: base.evidencias_lista,\n      output: 'Dados coletados. Vou disparar o Impact Analysis agora.'\n    }\n  }];\n}\n\nconst firstMissing = missing[0] || 'problema';\nconst examples = {\n  problema: 'Exemplo de problema: Alto indice de insucesso na entrega por ausencia do morador.',\n  mudanca: 'Exemplo de mudanca: Enviar SMS D-1 com opcoes de confirmacao/reagendamento/endereco alternativo.',\n  evidencias: 'Exemplo de evidencias: SMS enviado e resposta recebida/persistida com timestamp.',\n  restricoes: 'Exemplo de restricoes: Sem aumento de custo por mensagem acima de 10%.',\n};\n\nconst llmReply = toStr(parsed.reply || '');\nconst output = llmReply || [\n  'Para continuar, informe o campo: ' + firstMissing,\n  examples[firstMissing] || ''\n].filter(Boolean).join('\\n');\n\nreturn [{\n  json: {\n    route: 'impact_collect_question',\n    session_id: sessionId,\n    output,\n    draft: base,\n  }\n}];\n"
      }
    },
    {
      "id": "4bc4ec58-079f-4ae0-9fef-c6f21e0f3e10",
      "name": "Switch Impact Intake Route",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 1,
      "position": [
        -900,
        280
      ],
      "parameters": {
        "dataType": "string",
        "value1": "={{$json.route}}",
        "rules": [
          {
            "operation": "equal",
            "value2": "impact_run_manual"
          },
          {
            "operation": "equal",
            "value2": "impact_collect_question"
          }
        ]
      }
    }
  ],
  "connections": {
    "Normalize Chat Input": {
      "main": [
        [
          {
            "node": "Switch Option",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch Option": {
      "main": [
        [
          {
            "node": "Basic LLM Chain (Impact Intake)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Switch Option 2 Step",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Switch Option 3 Step",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch Option 2 Step": {
      "main": null
    },
    "Switch Option 3 Step": {
      "main": null
    },
    "Basic LLM Chain (Impact Intake)": {
      "main": [
        [
          {
            "node": "Parse Impact Intake Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Impact Intake Response": {
      "main": [
        [
          {
            "node": "Switch Impact Intake Route",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch Impact Intake Route": {
      "main": null
    },
    "Anthropic Chat Model - Impact Intake": {
      "ai_languageModel": [
        [
          {
            "node": "Basic LLM Chain (Impact Intake)",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}