atendenteSugestaoGuardrails.js 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. // Detecta, numa sugestão de resposta ao cliente, afirmações que o modelo não tem como saber
  2. // (ação concluída, valor, cobertura, multa/taxa) e pedidos/aceites de dado sensível.
  3. // Compartilhado com training/eval/juiz.mjs para o eval e a produção não divergirem.
  4. const PADROES = [
  5. {
  6. tipo: "acao_concluida",
  7. re: /\bj[áa] (verifiquei|liberei|resolvi|agendei|enviei|encaminhei|cancelei|estornei|conferi|localizei)\b|\b(conex[ãa]o|internet|acesso|servi[çc]o)\s+(foi\s+|est[áa]\s+|ficou\s+|j[áa]\s+)?(liberad[ao]|normalizad[ao]|restabelecid[ao])\b|\bpagamento\s+(foi\s+)?(agendad[ao]|confirmad[ao]|compensad[ao])\b|\b(visita|instala[çc][ãa]o|t[ée]cnico)\s+(est[áa]\s+|foi\s+)?(agendad[ao]|confirmad[ao])\b|\bconfirmando\s+(a\s+|sua\s+)?(instala[çc][ãa]o|visita)\b/i
  8. },
  9. {
  10. tipo: "cobertura_afirmada",
  11. re: /\b(sim,?\s+)?(a\s+star(\s+internet)?\s+)?atend(e|emos)\s+(sim|em|na|no|nessa|nesse)\b(?<!se\s+atend(e|emos)\s+(sim|em|na|no|nessa|nesse)\b)/i
  12. },
  13. {
  14. tipo: "multa_taxa_afirmada",
  15. re: /\b(sem|isent[oa]\s+de)\s+(multa|taxa)\b|\bn[ãa]o\s+(h[áa]|tem|existe|cobra(mos)?)\s+(multa|taxa)\b|\binstala[çc][ãa]o\s+(é|e)\s+(gratuita|gr[áa]tis)\b/i
  16. },
  17. {
  18. tipo: "dado_sensivel",
  19. re: /(?<!(?:n[ãa]o|nunca)\s+(?:me\s+)?)(?<!necess[áa]rio\s+)(?<!precis[ao]\s+)\b(passe|passa|passar|envie|enviar|informe|informar|mande|mandar|encaminhe|digite|digitar)\b[^.!?\n]{0,40}\b(c[óo]digo|senha|cvv|cart[ãa]o)\b|\bsim,?\s+(pode\s+)?(passar|enviar|mandar)\b[^.!?\n]{0,30}c[óo]digo|(?<!n[ãa]o\s+)(?:é|e)\s+nosso\s+c[óo]digo\b/i
  20. }
  21. ];
  22. const VALOR_RE = /R\$\s?\d[\d.,]*/gi;
  23. function normalizarValor(v) {
  24. return v.replace(/\s/g, "").toLowerCase();
  25. }
  26. /**
  27. * @param {string} texto sugestão gerada
  28. * @param {string} conversaTexto histórico (cliente + atendente) usado como contexto — valores
  29. * monetários que já aparecem aqui não são considerados inventados
  30. * @returns {{tipo: string, trecho: string}[]}
  31. */
  32. export function detectarAfirmacoesArriscadas(texto, conversaTexto = "") {
  33. if (!texto) return [];
  34. const hits = [];
  35. for (const { tipo, re } of PADROES) {
  36. const m = texto.match(re);
  37. if (m) hits.push({ tipo, trecho: m[0] });
  38. }
  39. const conhecidos = new Set((conversaTexto.match(VALOR_RE) ?? []).map(normalizarValor));
  40. for (const v of texto.match(VALOR_RE) ?? []) {
  41. if (!conhecidos.has(normalizarValor(v))) hits.push({ tipo: "valor_inventado", trecho: v });
  42. }
  43. return hits;
  44. }