|
|
@@ -3,6 +3,7 @@ import { chatCompletion } from "./ollamaClient.js";
|
|
|
import { buildConversaTexto, calcularUltimaMensagemId, SCORE_RUIM_MAX } from "../utils/atendimentoFormat.js";
|
|
|
import { buscarContextoIxc, buscarContextoCancelamentoIxc, conversaMencionaCancelamento } from "./atendimentoIxcContextoService.js";
|
|
|
import { ingestarAtendimento } from "./atendimentoRagService.js";
|
|
|
+import { transcreverAudiosPendentes } from "./whisperTranscricaoService.js";
|
|
|
import { Atendimento } from "../models/Atendimento.model.js";
|
|
|
import { AtendimentoAvaliacao } from "../models/AtendimentoAvaliacao.model.js";
|
|
|
import { AtendimentoMensagem } from "../models/AtendimentoMensagem.model.js";
|
|
|
@@ -80,6 +81,25 @@ const AVALIACAO_SYSTEM_PROMPT = [
|
|
|
"pergunta do atendente veio em tempo razoável e de forma cordial, o score fica na faixa 7-8 (não",
|
|
|
"1-4), sentimento_cliente_final=\"indefinido\" (o cliente não chegou a se manifestar, então NÃO é",
|
|
|
'"negativo") e resolvido="nao"/"indefinido" sem que isso pese contra o atendente.',
|
|
|
+ "Segundo caso muito comum dessa mesma exceção, com o mesmo desfecho errado ainda mais frequente: o",
|
|
|
+ "atendente JÁ ESTAVA conduzindo um diagnóstico técnico ativo — fez uma ou mais perguntas específicas",
|
|
|
+ "sobre o problema, testou algo, ajustou o equipamento remotamente, pediu para o cliente testar de novo",
|
|
|
+ "— e o CLIENTE para de responder no meio dessa investigação, antes de uma causa/solução ser confirmada.",
|
|
|
+ "O motivo do desfecho incompleto é o silêncio do cliente durante o diagnóstico — não uma limitação",
|
|
|
+ "do atendente. Na justificativa_score, credite a conduta ativa do atendente (as perguntas/testes que",
|
|
|
+ "ele de fato fez) e nomeie o silêncio do cliente como a causa de a investigação ter ficado incompleta.",
|
|
|
+ "Mesma regra da exceção acima: se a conduta do atendente até aí foi boa, score 7-8 e",
|
|
|
+ 'sentimento_cliente_final="indefinido", mesmo com resolvido="nao"/"indefinido".',
|
|
|
+ 'Exemplo bom: "O atendente diagnosticou o problema, fez o ajuste remoto cabível e pediu para o cliente',
|
|
|
+ 'testar novamente e confirmar; o cliente não retornou depois disso, então não foi possível confirmar se',
|
|
|
+ 'o problema foi resolvido."',
|
|
|
+ "CUIDADO com um erro relacionado: mensagem de ÁUDIO SEM TRANSCRIÇÃO disponível É uma resposta do",
|
|
|
+ "cliente — o conteúdo é desconhecido, mas ele respondeu. Nunca trate uma sequência de áudios sem",
|
|
|
+ 'transcrição como se o cliente tivesse "parado de responder"/"sumido" — as duas exceções acima (cliente',
|
|
|
+ "sumiu) só valem quando não há NENHUMA mensagem nova do cliente depois da pergunta do atendente, não",
|
|
|
+ "quando há áudio(s) sem transcrição alegando resposta. Julgue a conduta do atendente pelo conteúdo que",
|
|
|
+ "de fato está disponível na conversa, e não use uma nota mínima (1-2) como padrão automático só porque",
|
|
|
+ "o conteúdo do áudio é desconhecido para você.",
|
|
|
"",
|
|
|
"Primeiro escreva a justificativa_score (1 a 2 frases analisando a atuação do atendente) e só então",
|
|
|
"dê o score_atendente (1 a 10), que deve ser coerente com a justificativa e com o desfecho:",
|
|
|
@@ -189,6 +209,19 @@ const AVALIACAO_SYSTEM_PROMPT = [
|
|
|
" como gatilho: sempre confira QUEM está cancelando o quê.",
|
|
|
'→ motivo_cancelamento: null',
|
|
|
"",
|
|
|
+ "Exemplo 7b — é o CLIENTE quem cancela, mas o que ele cancela é só a VISITA/AGENDAMENTO/INSTALAÇÃO",
|
|
|
+ ' pontual, porque já resolveu por conta própria ou não precisa mais dela — não pede para cancelar',
|
|
|
+ ' o serviço/contrato em si (status_cancelamento = "nao"):',
|
|
|
+ 'Cliente: "Tem um agendamento para dia 17 na parte da manhã. Daí gostaria de cancelar por motivo',
|
|
|
+ ' que consegui instalar a câmera no meu celular."',
|
|
|
+ 'Atendente: "Ah certo, obrigado. Atendimento cancelado."',
|
|
|
+ '→ status_cancelamento: "nao" — a cliente cancelou apenas a visita/agendamento de instalação porque',
|
|
|
+ " já resolveu sozinha; em nenhum momento ela pede para cancelar o serviço/contrato. O mesmo vale",
|
|
|
+ " sempre que o cliente cancela uma visita/instalação pontual por conveniência própria ou por já ter",
|
|
|
+ ' resolvido sozinho — sem manifestar intenção de deixar de ser cliente. Não confunda com o Exemplo 5',
|
|
|
+ ' (ali o cliente pede para cancelar o PLANO/CONTRATO, não uma visita).',
|
|
|
+ '→ 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"',
|
|
|
@@ -357,6 +390,19 @@ const AVALIACAO_SYSTEM_PROMPT = [
|
|
|
' descrever o que precisava. A atendente Bruna cumprimentou e perguntou como poderia ajudar, mas a',
|
|
|
' cliente não respondeu mais, e o atendimento foi encerrado no dia seguinte sem que o motivo do',
|
|
|
' contato chegasse a ser informado."',
|
|
|
+ " Isso também vale para quando o atendente já estava em pleno diagnóstico técnico (fez pergunta(s)",
|
|
|
+ " específica(s), testou algo) e o cliente parou de responder no meio disso — o resumo deve dizer",
|
|
|
+ ' explicitamente que o cliente não respondeu mais após aquele ponto, em vez de só narrar os fatos e',
|
|
|
+ ' terminar com uma frase genérica como "o atendimento foi encerrado sem solução" (que soa como falha',
|
|
|
+ " do atendimento, sem dizer a causa real).",
|
|
|
+ ' Exemplo ruim: "O cliente Marcos entrou em contato devido a problemas com a conexão da TV. O',
|
|
|
+ ' atendente Nicolas perguntou sobre a conexão da TV e se estava via cabo ou wifi. O cliente respondeu',
|
|
|
+ ' que estava via cabo. O atendimento foi encerrado sem solução." (não diz que foi o cliente quem',
|
|
|
+ " parou de responder à pergunta seguinte do atendente)",
|
|
|
+ ' Exemplo bom: "O cliente Marcos relatou que a conexão da TV piorou após a troca do roteador. O',
|
|
|
+ ' atendente Nicolas perguntou se a TV estava conectada via cabo ou wifi e, na sequência, se o cliente',
|
|
|
+ ' já havia reiniciado a televisão; o cliente respondeu apenas a primeira pergunta e não respondeu mais',
|
|
|
+ ' depois disso, então o diagnóstico ficou incompleto."',
|
|
|
" (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',
|
|
|
@@ -497,7 +543,9 @@ const AVALIACAO_SYSTEM_PROMPT = [
|
|
|
' QUEM está falando em cancelar e O QUE está sendo cancelado: se for o atendente/empresa cancelando',
|
|
|
" ou reagendando uma VISITA, INSTALAÇÃO ou AGENDAMENTO pontual (por mau tempo, falta de peça,",
|
|
|
' técnico indisponível, atraso etc.), isso é operacional e NÃO conta — use "nao", mesmo que a',
|
|
|
- ' palavra "cancelar"/"cancelamento" apareça várias vezes (ver Exemplo 7). Só considere "ameacou"/',
|
|
|
+ ' palavra "cancelar"/"cancelamento" apareça várias vezes (ver Exemplo 7). O mesmo vale quando é o',
|
|
|
+ " próprio CLIENTE quem cancela uma visita/agendamento/instalação pontual por já ter resolvido sozinho",
|
|
|
+ ' ou por conveniência própria — use "nao" também nesse caso (ver Exemplo 7b). Só considere "ameacou"/',
|
|
|
' "cancelou" quando é o próprio CLIENTE que pede, menciona ou ameaça cancelar o SERVIÇO/CONTRATO em',
|
|
|
' si (não uma visita/agendamento pontual). Valores: "nao" (padrão) quando não há nenhuma menção do',
|
|
|
' cliente a cancelar o serviço/contrato; "ameacou" sempre que o cliente pede, menciona ou ameaça',
|
|
|
@@ -807,6 +855,8 @@ export async function avaliarAtendimento(atendimentoId) {
|
|
|
return { avaliado: false, motivo: "sem_dialogo", avaliacao };
|
|
|
}
|
|
|
|
|
|
+ await transcreverAudiosPendentes(mensagens);
|
|
|
+
|
|
|
const { dados, contextoIxcStatus, model } = await gerarAvaliacaoViaLLM(atendimento, mensagens);
|
|
|
|
|
|
const avaliacao = await upsertAvaliacao(atendimento.Id, {
|