Преглед на файлове

ajustes ao prompt para avaliaçoes de atendimentos

leonardo преди 1 месец
родител
ревизия
43ab70791e
променени са 1 файла, в които са добавени 152 реда и са изтрити 7 реда
  1. 152 7
      src/services/atendimentoAvaliacaoService.js

+ 152 - 7
src/services/atendimentoAvaliacaoService.js

@@ -7,7 +7,7 @@ import { Atendimento } from "../models/Atendimento.model.js";
 import { AtendimentoAvaliacao } from "../models/AtendimentoAvaliacao.model.js";
 import { AtendimentoMensagem } from "../models/AtendimentoMensagem.model.js";
 import { NotFoundError, UnprocessableEntityError } from "../shared/errors/index.js";
-import { nomeSetor } from "../config/setores.js";
+import { nomeSetor, setorAvaliavel, SETORES_EXCLUIDOS_AVALIACAO } from "../config/setores.js";
 
 const LIMITE_RELATORIO_PADRAO = 150;
 
@@ -75,9 +75,23 @@ const AVALIACAO_SYSTEM_PROMPT = [
   "9-10 = exemplar; 7-8 = bom, resolveu com pequenas falhas; 5-6 = mediano; 3-4 = fraco (respostas",
   "confusas, informação incorreta, cliente insistindo sem retorno adequado); 1-2 = muito ruim (atendente",
   "negligente: não respondeu, demorou de forma extrema, deu informação incorreta, transferiu o",
-  "atendimento sem acompanhar/sem solução, tratou mal, ou induziu a erro).",
+  "atendimento SEM FAZER NADA antes — sem verificar/diagnosticar o que cabia ao seu setor e sem avisar",
+  "o cliente do encaminhamento —, tratou mal, ou induziu a erro).",
+  'IMPORTANTE sobre transferências: transferir para outro setor NÃO é, por si só, negligência —',
+  "é o fluxo normal quando o problema foge do escopo do setor atual. Só penalize a transferência",
+  "(faixa 1-4) quando ela for feita às cegas: sem nenhuma verificação/diagnóstico dentro do que cabia",
+  "ao atendente resolver, e/ou sem avisar o cliente. Se o atendente fez a checagem cabível ao seu",
+  "setor (ex.: financeiro confirma que os pagamentos estão em dia antes de encaminhar um problema",
+  "técnico ao Suporte) e avisou o cliente antes de transferir, isso é atuação correta e não deve",
+  "derrubar o score para 1-4 só porque o problema em si não foi resolvido ali — avalie pela qualidade",
+  "da interação até o momento da transferência (faixa 5-8, a depender de agilidade e clareza), não",
+  "pelo desfecho técnico que ficou a cargo do outro setor.",
   "Um atendimento resolvido com cliente satisfeito NUNCA recebe score abaixo de 6.",
   "Se nenhum atendente humano participou, use score_atendente = null.",
+  "Pela mesma lógica de escrever a justificativa antes do score: escreva o resumo ANTES de decidir",
+  "resolvido e sentimento_cliente_final. Montar a narrativa completa primeiro (o que cada atendente",
+  "fez, se houve verificação antes de um encaminhamento, etc.) deixa essas categorias mais precisas —",
+  "elas devem ser consequência do que você acabou de narrar no resumo, não o contrário.",
   "",
   "Exemplos de calibração (conversas fictícias e resumidas — sirvam de referência para o score, não copie o texto):",
   "",
@@ -158,12 +172,118 @@ const AVALIACAO_SYSTEM_PROMPT = [
   "  como gatilho: sempre confira QUEM está cancelando o quê.",
   '→ motivo_cancelamento: null',
   "",
+  "Exemplo 8 — cliente entra em contato sem detalhar o problema original e diz que já está resolvido",
+  "(resolvido = \"sim\", sentimento positivo, score alto — mesmo sem diagnóstico visível na conversa):",
+  'Cliente: "Oi, bom dia"',
+  'Atendente Renata: "Bom dia! Em que posso ajudar?"',
+  'Cliente: "Ah, já está tudo certo agora, era só um susto, obrigada"',
+  'Atendente Renata: "Que bom! Qualquer coisa estou à disposição."',
+  '→ resolvido: "sim" — o próprio cliente confirma explicitamente que o problema já está resolvido;',
+  "  isso vale como resolução mesmo que a causa original nunca tenha sido detalhada na conversa nem",
+  "  o atendente tenha feito uma ação técnica visível. Não marque \"nao\"/\"indefinido\" só porque falta",
+  '  contexto sobre qual era o problema — a confirmação do cliente é o que importa aqui.',
+  '→ sentimento_cliente_final: "positivo"',
+  '→ score_atendente: 8-9 — atendimento breve mas cordial e resolvido; o piso 6 para resolvido=sim +',
+  "  sentimento=positivo se aplica normalmente.",
+  "",
   "Campos:",
-  '- resolvido: "sim" se o problema do cliente foi claramente resolvido na conversa; "parcial" se houve',
-  '  encaminhamento/paliativo; "nao" se terminou sem solução; "indefinido" se não dá para concluir.',
+  '- resolvido: "sim" se o problema do cliente foi claramente resolvido na conversa — inclui o caso em',
+  "  que o PRÓPRIO CLIENTE confirma que já está tudo certo/resolvido, mesmo sem ter detalhado qual era",
+  '  o problema original ou sem uma ação técnica visível do atendente (ver Exemplo 8); "parcial" se houve',
+  "  encaminhamento/paliativo com alguma mitigação real (ex.: solução temporária enquanto o definitivo",
+  "  não chega); \"nao\" se terminou sem solução — isso inclui um encaminhamento/transferência correto e",
+  "  diligente cujo problema em si não foi resolvido nesta conversa (a diligência do atendente já é",
+  '  reconhecida no score e no resumo, não precisa inflar o "resolvido" por causa dela); "indefinido" se',
+  "  não dá para concluir.",
   '- sentimento_cliente_final: sentimento do cliente AO FINAL da conversa ("positivo", "neutro", "negativo"',
   '  ou "indefinido" quando o cliente some/não se manifesta no final).',
-  "- resumo: 2 a 4 frases objetivas: quem procurou, qual o problema, o que foi feito, como terminou.",
+  "- resumo: 2 a 4 frases objetivas e específicas — nunca genéricas. Regras: (1) se um problema",
+  "  mencionado é causado por outro (ex.: internet bloqueada POR CAUSA de boleto em atraso), explicite",
+  "  essa relação de causa em vez de listar os dois como problemas soltos e desconectados; (2) diga o",
+  '  que o cliente pediu/solicitou concretamente (ex.: "agendar o pagamento para liberar a internet"),',
+  "  não só o problema em si; (3) diga a ação concreta que resolveu ou encaminhou o caso — evite frases",
+  '  vagas como "resolvido após várias transferências" sem dizer o que de fato foi feito (ex.: o que o',
+  "  técnico/financeiro efetivamente fez); (4) descreva como terminou.",
+  '  Exemplo ruim: "Cliente Maria procurou atendimento por problemas com internet bloqueada e boleto em',
+  '  atraso. O problema foi resolvido após várias transferências de atendimento."',
+  '  Exemplo bom: "Cliente Maria procurou atendimento devido a internet bloqueada por boleto em atraso,',
+  '  solicitando agendar o pagamento para liberar a internet. O atendente confirmou o agendamento do',
+  '  pagamento junto ao financeiro e a internet foi liberada."',
+  "  (5) quando uma transferência de setor aparecer, diga POR QUE ela aconteceu se der pra inferir da",
+  '  conversa (ex.: "transferiu para o financeiro, que é quem faz o desbloqueio") — uma transferência',
+  "  para o setor correto/responsável é um encaminhamento normal do processo, não uma falha; não a",
+  '  descreva como "sem solução encontrada" só porque o desfecho final não apareceu na conversa. Se o',
+  "  atendente fez alguma verificação/diagnóstico ANTES de transferir (ex.: conferiu que os pagamentos",
+  "  estavam em dia, descartando causa financeira) e/ou avisou o cliente antes de encaminhar, é",
+  "  OBRIGATÓRIO citar essa verificação no resumo — nunca omita esse detalhe quando ele estiver na",
+  "  conversa; ele é o que diferencia uma transferência diligente de uma transferência às cegas.",
+  '  Exemplo ruim: "Cliente Letícia procurou atendimento devido à falta de sinal de internet, mas o',
+  '  problema não foi resolvido. O atendente Bruna transferiu para o setor Suporte sem solução."',
+  '  Exemplo bom: "Cliente Letícia procurou atendimento devido à falta de sinal de internet. A atendente',
+  '  Bruna verificou que os pagamentos estavam todos em dia e encaminhou a questão para o Suporte',
+  '  verificar, avisando a cliente antes de transferir."',
+  "  (6) se",
+  "  o motivo de não ter concluído foi o CLIENTE não responder a um contato/retorno do atendente, diga",
+  "  isso explicitamente (é a causa real do desfecho em aberto) em vez de atribuir a pendência de forma",
+  "  vaga ao processo/atendimento. (7) use os nomes dos atendentes quando a conversa tiver mais de um",
+  "  e a ação de cada um for relevante para entender o desfecho (quem transferiu, quem tentou contato).",
+  '  Exemplo ruim: "Gaby procurou atendimento por problemas de internet, mas o atendimento foi',
+  '  transferido para o setor financeiro sem solução encontrada."',
+  '  Exemplo bom: "Gaby procurou atendimento devido à internet bloqueada por ter esquecido de pagar a',
+  '  fatura; ela já havia realizado o pagamento e queria a liberação. A atendente Carla transferiu para',
+  '  o setor financeiro, responsável pelo desbloqueio; a atendente Helen tentou contato em seguida, mas',
+  '  a cliente não respondeu."',
+  "  (8) não use uma frase-modelo genérica para o pedido do cliente ou para a causa da liberação só",
+  "  porque ela apareceu em outro atendimento parecido — descreva o que REALMENTE aconteceu nesta",
+  '  conversa (ex.: o cliente pode ter pedido a fatura para pagar por conta própria via PIX, em vez de',
+  '  pedir para "agendar o pagamento"). Nunca escreva uma causa circular/tautológica, onde o motivo',
+  '  citado é só outro nome para o próprio resultado (ex.: "a internet foi liberada após confirmação da',
+  '  liberação" não diz nada) — identifique o evento real que disparou a ação (ex.: "após o cliente',
+  '  enviar/confirmar o comprovante de pagamento").',
+  '  Exemplo ruim: "Cliente procurou atendimento devido a fatura em aberto e solicitou agendar o',
+  '  pagamento via PIX. O atendente forneceu a fatura e instruções para pagamento, e a internet foi',
+  '  liberada após confirmação da liberação." (a causa final é circular e "agendar o pagamento" não é',
+  "  o que o cliente pediu)",
+  '  Exemplo bom: "Cliente procurou atendimento devido a fatura em aberto e solicitou a fatura para',
+  '  realizar o pagamento via PIX. O atendente forneceu a fatura; após o cliente enviar o comprovante',
+  '  de pagamento, a internet foi liberada."',
+  '  (9) mesma lógica da regra sobre mensagens "[FORA DO EXPEDIENTE]" definida no início deste prompt:',
+  "  se o atendimento ficou sem solução porque a resposta do cliente chegou fora do horário de",
+  "  atendimento do setor (ex.: fim de semana) e por isso não houve mais retorno do atendente dentro da",
+  '  conversa, diga isso explicitamente no resumo — não descreva como "atendente não conseguiu resolver"',
+  "  nem como falha do atendente; o motivo real é o horário, não a atuação de quem atendeu.",
+  '  Exemplo ruim: "Cliente solicitou o código para colocar sua fatura em débito em conta, mas atendente',
+  '  não conseguiu resolver a solicitação. Atendimento foi encerrado sem solução."',
+  '  Exemplo bom: "Cliente solicitou o código para colocar sua fatura em débito em conta. A cliente',
+  '  respondeu fora do horário comercial do setor (fim de semana), então o atendimento ficou sem',
+  '  retorno dentro da conversa até a reabertura do expediente."',
+  "  (10) quando o atendente citar um dado concreto da conversa — um prazo, uma data-limite, um valor,",
+  '  uma condição específica — cite esse dado concreto no resumo em vez de resumir com uma expressão',
+  '  vaga como "ofereceu uma solução alternativa"/"deu instruções". Se o atendente apenas EXPLICOU um',
+  "  prazo ou regra já existente (ex.: até quando o pagamento pode ser feito), isso é uma explicação,",
+  "  não a criação de uma solução alternativa — não infira uma ação que não ocorreu.",
+  '  Exemplo ruim: "O atendente Bruna explicou que não era possível agendar com dois vencimentos em',
+  '  aberto, mas ofereceu uma solução alternativa. Após entendimento do problema e apresentação da',
+  '  solução, o cliente aceitou e a conexão foi liberada." (não cita a data e inventa uma "solução',
+  '  alternativa" que não houve)',
+  '  Exemplo bom: "A atendente Bruna explicou que não era possível agendar com dois vencimentos em',
+  '  aberto, mas informou que o prazo máximo para pagamento seria até [data citada na conversa]. O',
+  '  cliente aceitou e a conexão foi liberada."',
+  "  (11) não invente uma ação do atendente que não está na conversa — se o motivo de não haver resposta",
+  '  imediata foi simplesmente estar fora do horário de atendimento (ex.: mensagem "[FORA DO EXPEDIENTE]"',
+  '  ou ausência de resposta humana), NÃO escreva que "o atendente informou que estava fora do horário"',
+  '  nem que "o atendente aguardou resposta do cliente" a menos que isso literalmente apareça na',
+  "  conversa — descreva o fato como ele é (ficou fora do expediente até a reabertura), sem atribuir uma",
+  "  ação a quem não agiu. Se o PRÓPRIO CLIENTE relatar que o problema se resolveu sozinho (ex.: o sinal",
+  '  voltou depois de um tempo, sem intervenção do atendente), registre isso explicitamente como',
+  '  resolução espontânea — conta como resolvido="sim", já que o cliente confirma que o problema não',
+  "  existe mais, mesmo sem ação técnica do atendente.",
+  '  Exemplo ruim: "Cliente procurou atendimento por problemas com internet sem sinal, solicitando',
+  '  ajuda. O atendente informou que estava fora do horário de atendimento e aguardou resposta do',
+  '  cliente até a reabertura do expediente." (inventa uma ação do atendente que não ocorreu)',
+  '  Exemplo bom: "Cliente entrou em contato reclamando de falta de sinal de internet, mas já era fora',
+  '  do horário de atendimento do setor. O próprio cliente comentou depois que o sinal voltou a',
+  '  funcionar sozinho após algum tempo."',
   "- sugestoes_melhoria: 0 a 5 itens curtos e acionáveis para o atendente/equipe melhorar; lista vazia se exemplar.",
   "- atendentes: nomes dos atendentes humanos que participaram (como aparecem na conversa).",
   "- visita_agendada: true se ficou combinada visita técnica presencial na conversa.",
@@ -200,9 +320,9 @@ const AVALIACAO_JSON_SCHEMA = {
 
     justificativa_score: { type: "string" },
     score_atendente: { type: ["integer", "null"], minimum: 1, maximum: 10 },
+    resumo: { type: "string" },
     resolvido: { type: "string", enum: ["sim", "nao", "parcial", "indefinido"] },
     sentimento_cliente_final: { type: "string", enum: ["positivo", "neutro", "negativo", "indefinido"] },
-    resumo: { type: "string" },
     sugestoes_melhoria: { type: "array", items: { type: "string" }, maxItems: 5 },
     atendentes: { type: "array", items: { type: "string" } },
     visita_agendada: { type: "boolean" },
@@ -438,6 +558,30 @@ export async function avaliarAtendimento(atendimentoId) {
   const ultimaMensagemId = calcularUltimaMensagemId(mensagens);
   const { cliente: msgsCliente, atendente: msgsAtendente } = contarInteracoes(mensagens);
 
+  // chat interno entre técnicos, não atendimento a cliente — não faz sentido pro juiz
+  if (!setorAvaliavel(atendimento.Setor)) {
+    const avaliacao = await upsertAvaliacao(atendimento.Id, {
+      Avaliavel: false,
+      ScoreAtendente: null,
+      JustificativaScore: null,
+      Resolvido: "indefinido",
+      Sentimento: "indefinido",
+      Resumo: null,
+      Sugestoes: [],
+      Atendentes: [],
+      VisitaAgendada: null,
+      HorarioVisitaInformado: null,
+      HorarioVisita: null,
+      StatusCancelamento: "nao",
+      MotivoCancelamento: null,
+      Modelo: null,
+      ContextoIxcStatus: "nao_verificado",
+      UltimaMensagemId: ultimaMensagemId,
+      TotalMensagens: mensagens.length
+    });
+    return { avaliado: false, motivo: "setor_interno", avaliacao };
+  }
+
   if (msgsCliente === 0 || msgsAtendente === 0) {
     const avaliacao = await upsertAvaliacao(atendimento.Id, {
       Avaliavel: false,
@@ -504,7 +648,8 @@ function pendentesQuery({ setor } = {}) {
       q.whereNull("av.AtendimentoId")
         .orWhereNull("av.UltimaMensagemId")
         .orWhereRaw("av.UltimaMensagemId < m.MaxMsgId");
-    });
+    })
+    .whereNotIn("atendimentos.Setor", SETORES_EXCLUIDOS_AVALIACAO);
 
   if (setor) query.where("atendimentos.Setor", setor);
   return query;