فهرست منبع

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 { AtendimentoAvaliacao } from "../models/AtendimentoAvaliacao.model.js";
 import { AtendimentoMensagem } from "../models/AtendimentoMensagem.model.js";
 import { AtendimentoMensagem } from "../models/AtendimentoMensagem.model.js";
 import { NotFoundError, UnprocessableEntityError } from "../shared/errors/index.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;
 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",
   "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",
   "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",
   "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.",
   "Um atendimento resolvido com cliente satisfeito NUNCA recebe score abaixo de 6.",
   "Se nenhum atendente humano participou, use score_atendente = null.",
   "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):",
   "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ê.",
   "  como gatilho: sempre confira QUEM está cancelando o quê.",
   '→ motivo_cancelamento: null',
   '→ 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:",
   "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"',
   '- 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).',
   '  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.",
   "- 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).",
   "- atendentes: nomes dos atendentes humanos que participaram (como aparecem na conversa).",
   "- visita_agendada: true se ficou combinada visita técnica presencial 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" },
     justificativa_score: { type: "string" },
     score_atendente: { type: ["integer", "null"], minimum: 1, maximum: 10 },
     score_atendente: { type: ["integer", "null"], minimum: 1, maximum: 10 },
+    resumo: { type: "string" },
     resolvido: { type: "string", enum: ["sim", "nao", "parcial", "indefinido"] },
     resolvido: { type: "string", enum: ["sim", "nao", "parcial", "indefinido"] },
     sentimento_cliente_final: { type: "string", enum: ["positivo", "neutro", "negativo", "indefinido"] },
     sentimento_cliente_final: { type: "string", enum: ["positivo", "neutro", "negativo", "indefinido"] },
-    resumo: { type: "string" },
     sugestoes_melhoria: { type: "array", items: { type: "string" }, maxItems: 5 },
     sugestoes_melhoria: { type: "array", items: { type: "string" }, maxItems: 5 },
     atendentes: { type: "array", items: { type: "string" } },
     atendentes: { type: "array", items: { type: "string" } },
     visita_agendada: { type: "boolean" },
     visita_agendada: { type: "boolean" },
@@ -438,6 +558,30 @@ export async function avaliarAtendimento(atendimentoId) {
   const ultimaMensagemId = calcularUltimaMensagemId(mensagens);
   const ultimaMensagemId = calcularUltimaMensagemId(mensagens);
   const { cliente: msgsCliente, atendente: msgsAtendente } = contarInteracoes(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) {
   if (msgsCliente === 0 || msgsAtendente === 0) {
     const avaliacao = await upsertAvaliacao(atendimento.Id, {
     const avaliacao = await upsertAvaliacao(atendimento.Id, {
       Avaliavel: false,
       Avaliavel: false,
@@ -504,7 +648,8 @@ function pendentesQuery({ setor } = {}) {
       q.whereNull("av.AtendimentoId")
       q.whereNull("av.AtendimentoId")
         .orWhereNull("av.UltimaMensagemId")
         .orWhereNull("av.UltimaMensagemId")
         .orWhereRaw("av.UltimaMensagemId < m.MaxMsgId");
         .orWhereRaw("av.UltimaMensagemId < m.MaxMsgId");
-    });
+    })
+    .whereNotIn("atendimentos.Setor", SETORES_EXCLUIDOS_AVALIACAO);
 
 
   if (setor) query.where("atendimentos.Setor", setor);
   if (setor) query.where("atendimentos.Setor", setor);
   return query;
   return query;