Ver código fonte

adicao de filtro por setores

leonardo 2 meses atrás
pai
commit
6ab2d84bfc

+ 1 - 10
scripts/exportarDatasetTreinamento.js

@@ -14,6 +14,7 @@ import { fileURLToPath } from "node:url";
 import { AtendimentoAvaliacao } from "../src/models/AtendimentoAvaliacao.model.js";
 import { Atendimento } from "../src/models/Atendimento.model.js";
 import { stripHtml, parseAtendenteBody } from "../src/utils/atendimentoFormat.js";
+import { SETOR_NOMES } from "../src/config/setores.js";
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
 
@@ -36,16 +37,6 @@ function parseArgs(argv) {
   return args;
 }
 
-const SETOR_NOMES = {
-  SUP: "suporte técnico",
-  FIN: "financeiro",
-  ATE: "atendimento geral",
-  VEN: "vendas",
-  POS: "pós-venda",
-  PLA: "planos",
-  SUI: "suporte interno"
-};
-
 function systemPrompt(setor) {
   const area = SETOR_NOMES[setor] ?? (setor ? setor.toLowerCase() : "atendimento ao cliente");
   return [

+ 21 - 0
src/config/setores.js

@@ -0,0 +1,21 @@
+// nomes amigáveis por sigla de setor (prefixo de 3 letras do protocolo, ver setorFromCodigo
+// em atendimentosService.js) — compartilhado entre o script de dataset de treinamento e a API
+export const SETOR_NOMES = {
+  SUP: "suporte técnico",
+  FIN: "financeiro",
+  ATE: "atendimento geral",
+  VEN: "vendas",
+  POS: "pós-venda",
+  PLA: "planos",
+  SUI: "suporte interno"
+};
+
+function capitalizar(texto) {
+  return texto.charAt(0).toUpperCase() + texto.slice(1);
+}
+
+// nome pronto para exibição (ex.: dropdown de filtro); siglas sem mapeamento caem na própria sigla
+export function nomeSetor(sigla) {
+  const nome = SETOR_NOMES[sigla];
+  return nome ? capitalizar(nome) : sigla;
+}

+ 11 - 1
src/controllers/Atendimentos.Controller.js

@@ -11,7 +11,8 @@ import {
   avaliarAtendimento,
   avaliarPendentes,
   listarAvaliacoes,
-  estatisticasAvaliacoes
+  estatisticasAvaliacoes,
+  listarSetores
 } from "../services/atendimentoAvaliacaoService.js";
 import { estruturarMensagem, montarUrlMidia } from "../utils/atendimentoFormat.js";
 import { config } from "../config/index.js";
@@ -158,6 +159,15 @@ export const AtendimentosController = {
     } catch (err) {
       next(err);
     }
+  },
+
+  ListarSetoresAvaliacoes: async function (req, res, next) {
+    try {
+      const results = await listarSetores();
+      res.json({ results });
+    } catch (err) {
+      next(err);
+    }
   }
 };
 

+ 1 - 0
src/routes/Atendimentos.Rotas.js

@@ -15,6 +15,7 @@ atendimentosRouter.post("/sync-all", requireUser, requireAdmin, validate({ body:
 atendimentosRouter.post("/avaliar", requireUser, requireAdmin, validate({ body: atendimentoAvaliarSchema }), AtendimentosController.AvaliarPendentes);
 atendimentosRouter.get("/avaliacoes", requireUser, requireAdmin, AtendimentosController.ListarAvaliacoes);
 atendimentosRouter.get("/avaliacoes/estatisticas", requireUser, requireAdmin, AtendimentosController.EstatisticasAvaliacoes);
+atendimentosRouter.get("/avaliacoes/setores", requireUser, requireAdmin, AtendimentosController.ListarSetoresAvaliacoes);
 atendimentosRouter.get("/", requireUser, AtendimentosController.Listar);
 atendimentosRouter.get("/:id", requireUser, parseId, AtendimentosController.Obter);
 atendimentosRouter.post("/:id/avaliar", requireUser, requireAdmin, parseId, AtendimentosController.AvaliarUm);

+ 15 - 0
src/services/atendimentoAvaliacaoService.js

@@ -5,6 +5,7 @@ import { Atendimento } from "../models/Atendimento.model.js";
 import { AtendimentoAvaliacao } from "../models/AtendimentoAvaliacao.model.js";
 import { AtendimentoMensagem } from "../models/AtendimentoMensagem.model.js";
 import { NotFoundError } from "../shared/errors/index.js";
+import { nomeSetor } from "../config/setores.js";
 
 const AVALIACAO_SYSTEM_PROMPT = [
   "Você é um auditor de qualidade de atendimento ao cliente de um provedor de internet (Star).",
@@ -414,6 +415,20 @@ export async function listarAvaliacoes({ page = 1, pageSize = 20, setor, resolvi
   return { results: items, total, page, pageSize };
 }
 
+// setores com pelo menos uma avaliação, para popular o filtro da tela de avaliações
+export async function listarSetores() {
+  const rows = await AtendimentoAvaliacao.query()
+    .where("Avaliavel", true)
+    .join("atendimentos as a", "a.Id", "atendimento_avaliacoes.AtendimentoId")
+    .whereNotNull("a.Setor")
+    .select("a.Setor")
+    .count("* as total")
+    .groupBy("a.Setor")
+    .orderBy("a.Setor");
+
+  return rows.map((r) => ({ sigla: r.Setor, nome: nomeSetor(r.Setor), total: Number(r.total) }));
+}
+
 export async function estatisticasAvaliacoes({ setor } = {}) {
   const base = () => {
     const q = AtendimentoAvaliacao.query().where("Avaliavel", true);

+ 4 - 5
src/services/atendimentosService.js

@@ -85,7 +85,7 @@ function toDate(value) {
   return Number.isNaN(d.getTime()) ? null : d;
 }
 
-export async function salvarAtendimento({ payload, setor, sourceUrl, abertura, telefone }) {
+export async function salvarAtendimento({ payload, sourceUrl, abertura, telefone }) {
   const now = new Date();
   const trx = await Model.startTransaction();
 
@@ -111,8 +111,8 @@ export async function salvarAtendimento({ payload, setor, sourceUrl, abertura, t
       .merge(clienteMerge);
 
     const p = payload.Protocolo;
-    const atendimentoMerge = ["Codigo", "Status", "ClienteId", "IngestedAt", "UpdatedAt"];
-    if (setor) atendimentoMerge.push("Setor");
+    const setor = setorFromCodigo(p.Codigo);
+    const atendimentoMerge = ["Codigo", "Status", "ClienteId", "Setor", "IngestedAt", "UpdatedAt"];
     if (sourceUrl) atendimentoMerge.push("SourceUrl");
     if (abertura) atendimentoMerge.push("Abertura");
 
@@ -122,7 +122,7 @@ export async function salvarAtendimento({ payload, setor, sourceUrl, abertura, t
         Codigo: p.Codigo,
         Status: p.Status,
         ClienteId: c.Id,
-        Setor: setor ?? null,
+        Setor: setor,
         SourceUrl: sourceUrl ?? null,
         Abertura: toDate(abertura),
         IngestedAt: now,
@@ -187,7 +187,6 @@ async function importarProtocolo(item, baseUrl) {
 
   return salvarAtendimento({
     payload,
-    setor: setorFromCodigo(item.Codigo),
     sourceUrl: mensagensUrl,
     abertura: item.Abertura ?? null,
     telefone: item.Cliente?.Telefone ?? null