|
@@ -731,19 +731,19 @@ function formatarContextoIxc(contexto, contextoCancelamento) {
|
|
|
return linhas.join("\n");
|
|
return linhas.join("\n");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function truncateConversa(text, maxChars = config.avaliacao.maxChars) {
|
|
|
|
|
|
|
+function truncarConversa(text, maxChars = config.avaliacao.maxChars) {
|
|
|
if (text.length <= maxChars) return text;
|
|
if (text.length <= maxChars) return text;
|
|
|
const head = Math.floor(maxChars * 0.45);
|
|
const head = Math.floor(maxChars * 0.45);
|
|
|
const tail = maxChars - head;
|
|
const tail = maxChars - head;
|
|
|
return text.slice(0, head) + TRUNCATION_MARKER + text.slice(-tail);
|
|
return text.slice(0, head) + TRUNCATION_MARKER + text.slice(-tail);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function normalizeEnum(value, allowed, fallback = "indefinido") {
|
|
|
|
|
|
|
+function normalizarEnum(value, allowed, fallback = "indefinido") {
|
|
|
const v = String(value ?? "").trim().toLowerCase();
|
|
const v = String(value ?? "").trim().toLowerCase();
|
|
|
return allowed.includes(v) ? v : fallback;
|
|
return allowed.includes(v) ? v : fallback;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function normalizeScore(value) {
|
|
|
|
|
|
|
+function normalizarScore(value) {
|
|
|
const n = Number(value);
|
|
const n = Number(value);
|
|
|
if (!Number.isFinite(n)) return null;
|
|
if (!Number.isFinite(n)) return null;
|
|
|
return Math.min(10, Math.max(1, Math.round(n)));
|
|
return Math.min(10, Math.max(1, Math.round(n)));
|
|
@@ -764,7 +764,7 @@ export function aplicarPisoScore(dados) {
|
|
|
return dados;
|
|
return dados;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function normalizeStringArray(value, { maxItems = 10, maxLength = 300 } = {}) {
|
|
|
|
|
|
|
+function normalizarListaTexto(value, { maxItems = 10, maxLength = 300 } = {}) {
|
|
|
if (!Array.isArray(value)) return [];
|
|
if (!Array.isArray(value)) return [];
|
|
|
return value
|
|
return value
|
|
|
.map((s) => String(s ?? "").trim())
|
|
.map((s) => String(s ?? "").trim())
|
|
@@ -782,26 +782,26 @@ function parseAvaliacao(content) {
|
|
|
.trim();
|
|
.trim();
|
|
|
const parsed = JSON.parse(cleaned);
|
|
const parsed = JSON.parse(cleaned);
|
|
|
|
|
|
|
|
- const horarioInformado = normalizeEnum(parsed.horario_visita_informado, ["sim", "nao", "nao_se_aplica"], "nao_se_aplica");
|
|
|
|
|
|
|
+ const horarioInformado = normalizarEnum(parsed.horario_visita_informado, ["sim", "nao", "nao_se_aplica"], "nao_se_aplica");
|
|
|
const horarioVisita = typeof parsed.horario_visita === "string" && parsed.horario_visita.trim()
|
|
const horarioVisita = typeof parsed.horario_visita === "string" && parsed.horario_visita.trim()
|
|
|
? parsed.horario_visita.trim().slice(0, 150)
|
|
? parsed.horario_visita.trim().slice(0, 150)
|
|
|
: null;
|
|
: null;
|
|
|
|
|
|
|
|
- const statusCancelamento = normalizeEnum(parsed.status_cancelamento, ["nao", "ameacou", "cancelou", "indefinido"], "nao");
|
|
|
|
|
|
|
+ const statusCancelamento = normalizarEnum(parsed.status_cancelamento, ["nao", "ameacou", "cancelou", "indefinido"], "nao");
|
|
|
const motivoCancelamento = statusCancelamento !== "nao" && typeof parsed.motivo_cancelamento === "string" && parsed.motivo_cancelamento.trim()
|
|
const motivoCancelamento = statusCancelamento !== "nao" && typeof parsed.motivo_cancelamento === "string" && parsed.motivo_cancelamento.trim()
|
|
|
? parsed.motivo_cancelamento.trim().slice(0, 500)
|
|
? parsed.motivo_cancelamento.trim().slice(0, 500)
|
|
|
: null;
|
|
: null;
|
|
|
|
|
|
|
|
return aplicarPisoScore({
|
|
return aplicarPisoScore({
|
|
|
- ScoreAtendente: normalizeScore(parsed.score_atendente),
|
|
|
|
|
|
|
+ ScoreAtendente: normalizarScore(parsed.score_atendente),
|
|
|
JustificativaScore: typeof parsed.justificativa_score === "string" && parsed.justificativa_score.trim()
|
|
JustificativaScore: typeof parsed.justificativa_score === "string" && parsed.justificativa_score.trim()
|
|
|
? parsed.justificativa_score.trim()
|
|
? parsed.justificativa_score.trim()
|
|
|
: null,
|
|
: null,
|
|
|
- Resolvido: normalizeEnum(parsed.resolvido, ["sim", "nao", "parcial", "indefinido"]),
|
|
|
|
|
- Sentimento: normalizeEnum(parsed.sentimento_cliente_final, ["positivo", "neutro", "negativo", "indefinido"]),
|
|
|
|
|
|
|
+ Resolvido: normalizarEnum(parsed.resolvido, ["sim", "nao", "parcial", "indefinido"]),
|
|
|
|
|
+ Sentimento: normalizarEnum(parsed.sentimento_cliente_final, ["positivo", "neutro", "negativo", "indefinido"]),
|
|
|
Resumo: typeof parsed.resumo === "string" && parsed.resumo.trim() ? parsed.resumo.trim() : null,
|
|
Resumo: typeof parsed.resumo === "string" && parsed.resumo.trim() ? parsed.resumo.trim() : null,
|
|
|
- Sugestoes: normalizeStringArray(parsed.sugestoes_melhoria, { maxItems: 5 }),
|
|
|
|
|
- Atendentes: normalizeStringArray(parsed.atendentes, { maxItems: 10, maxLength: 100 }),
|
|
|
|
|
|
|
+ Sugestoes: normalizarListaTexto(parsed.sugestoes_melhoria, { maxItems: 5 }),
|
|
|
|
|
+ Atendentes: normalizarListaTexto(parsed.atendentes, { maxItems: 10, maxLength: 100 }),
|
|
|
VisitaAgendada: Boolean(parsed.visita_agendada),
|
|
VisitaAgendada: Boolean(parsed.visita_agendada),
|
|
|
HorarioVisitaInformado: horarioInformado,
|
|
HorarioVisitaInformado: horarioInformado,
|
|
|
HorarioVisita: horarioInformado === "sim" ? horarioVisita : null,
|
|
HorarioVisita: horarioInformado === "sim" ? horarioVisita : null,
|
|
@@ -842,8 +842,8 @@ export async function gerarAvaliacaoViaLLM(atendimento, mensagens) {
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
const conteudoUsuario = contextoIxc || contextoCancelamentoIxc
|
|
const conteudoUsuario = contextoIxc || contextoCancelamentoIxc
|
|
|
- ? `${formatarContextoIxc(contextoIxc, contextoCancelamentoIxc)}\n\n${truncateConversa(conversaTexto)}`
|
|
|
|
|
- : truncateConversa(conversaTexto);
|
|
|
|
|
|
|
+ ? `${formatarContextoIxc(contextoIxc, contextoCancelamentoIxc)}\n\n${truncarConversa(conversaTexto)}`
|
|
|
|
|
+ : truncarConversa(conversaTexto);
|
|
|
|
|
|
|
|
const model = config.avaliacao.model || config.ollama.chatModel;
|
|
const model = config.avaliacao.model || config.ollama.chatModel;
|
|
|
const { content } = await chatCompletion({
|
|
const { content } = await chatCompletion({
|
|
@@ -868,6 +868,33 @@ export async function gerarAvaliacaoViaLLM(atendimento, mensagens) {
|
|
|
return { dados, contextoIxcStatus, model };
|
|
return { dados, contextoIxcStatus, model };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const AVALIACAO_NAO_AVALIAVEL_BASE = {
|
|
|
|
|
+ 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"
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+async function registrarNaoAvaliavel(atendimentoId, { ultimaMensagemId, totalMensagens, motivo }) {
|
|
|
|
|
+ const avaliacao = await upsertAvaliacao(atendimentoId, {
|
|
|
|
|
+ ...AVALIACAO_NAO_AVALIAVEL_BASE,
|
|
|
|
|
+ UltimaMensagemId: ultimaMensagemId,
|
|
|
|
|
+ TotalMensagens: totalMensagens
|
|
|
|
|
+ });
|
|
|
|
|
+ return { avaliado: false, motivo, avaliacao };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export async function avaliarAtendimento(atendimentoId) {
|
|
export async function avaliarAtendimento(atendimentoId) {
|
|
|
const atendimento = await Atendimento.query()
|
|
const atendimento = await Atendimento.query()
|
|
|
.findById(atendimentoId)
|
|
.findById(atendimentoId)
|
|
@@ -882,49 +909,19 @@ export async function avaliarAtendimento(atendimentoId) {
|
|
|
|
|
|
|
|
// chat interno entre técnicos, não atendimento a cliente — não faz sentido pro juiz
|
|
// chat interno entre técnicos, não atendimento a cliente — não faz sentido pro juiz
|
|
|
if (!setorAvaliavel(atendimento.Setor)) {
|
|
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 registrarNaoAvaliavel(atendimento.Id, {
|
|
|
|
|
+ ultimaMensagemId,
|
|
|
|
|
+ totalMensagens: mensagens.length,
|
|
|
|
|
+ motivo: "setor_interno"
|
|
|
});
|
|
});
|
|
|
- return { avaliado: false, motivo: "setor_interno", avaliacao };
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (msgsCliente === 0 || msgsAtendente === 0) {
|
|
if (msgsCliente === 0 || msgsAtendente === 0) {
|
|
|
- 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 registrarNaoAvaliavel(atendimento.Id, {
|
|
|
|
|
+ ultimaMensagemId,
|
|
|
|
|
+ totalMensagens: mensagens.length,
|
|
|
|
|
+ motivo: "sem_dialogo"
|
|
|
});
|
|
});
|
|
|
- return { avaliado: false, motivo: "sem_dialogo", avaliacao };
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
await transcreverAudiosPendentes(mensagens);
|
|
await transcreverAudiosPendentes(mensagens);
|