reavaliarAudiosSemTranscricao.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import "dotenv/config";
  2. import { Atendimento } from "../src/models/Atendimento.model.js";
  3. import { AtendimentoMensagem } from "../src/models/AtendimentoMensagem.model.js";
  4. import { avaliarAtendimento } from "../src/services/atendimentoAvaliacaoService.js";
  5. import { AUDIO_MEDIA_TYPES } from "../src/utils/atendimentoFormat.js";
  6. // backfill pós-correção do viés de julgamento em áudios de atendente sem transcrição
  7. // (ver plano "corrigir viés de julgamento em áudios de atendente sem transcrição",
  8. // 2026-08-10): reavalia atendimentos que já foram avaliados ANTES da mudança de prompt/
  9. // placeholder e que têm ao menos uma mensagem de áudio sem transcrição real, pra que a
  10. // nova regra do AVALIACAO_SYSTEM_PROMPT seja aplicada ao histórico.
  11. const limiteIdx = process.argv.indexOf("--limite");
  12. const LIMITE = limiteIdx > -1 ? Number(process.argv[limiteIdx + 1]) : null;
  13. // --janela 19-8: só processa dentro do intervalo de horas (cruzando meia-noite, se preciso);
  14. // fora dele, pausa e fica checando a cada 5min até reabrir — pensado pra rodar madrugadas
  15. // seguidas sem precisar reiniciar o processo manualmente.
  16. const janelaIdx = process.argv.indexOf("--janela");
  17. const JANELA = janelaIdx > -1 ? process.argv[janelaIdx + 1] : null;
  18. const [JANELA_INICIO, JANELA_FIM] = JANELA ? JANELA.split("-").map(Number) : [null, null];
  19. const SENTINELS = ["__processando__", "__erro__"];
  20. function dentroDaJanela(inicioHora, fimHora) {
  21. const horaSp = Number(
  22. new Intl.DateTimeFormat("en-US", { timeZone: "America/Sao_Paulo", hour: "2-digit", hourCycle: "h23" }).format(new Date())
  23. );
  24. if (inicioHora <= fimHora) return horaSp >= inicioHora && horaSp < fimHora;
  25. return horaSp >= inicioHora || horaSp < fimHora; // janela cruza a meia-noite
  26. }
  27. async function aguardarJanela(inicioHora, fimHora) {
  28. let avisado = false;
  29. while (!dentroDaJanela(inicioHora, fimHora)) {
  30. if (!avisado) {
  31. console.log(`[reavaliar-audios] fora da janela noturna (${inicioHora}h-${fimHora}h) — pausado até reabrir`);
  32. avisado = true;
  33. }
  34. await new Promise((r) => setTimeout(r, 5 * 60 * 1000));
  35. }
  36. if (avisado) console.log("[reavaliar-audios] janela reaberta, retomando");
  37. }
  38. async function main() {
  39. // subquery (não JOIN) pra evitar duplicar uma linha de atendimento por cada mensagem de
  40. // áudio que ele tiver — JOIN + DISTINCT ... ORDER BY Abertura quebra no MySQL porque
  41. // Abertura não entra no SELECT do DISTINCT.
  42. const atendimentosComAudioPendente = AtendimentoMensagem.query()
  43. .select("AtendimentoId")
  44. .whereIn("Tipodemidia", [...AUDIO_MEDIA_TYPES])
  45. .where((qb) => {
  46. qb.whereNull("Transcricao").orWhere("Transcricao", "").orWhereIn("Transcricao", SENTINELS);
  47. });
  48. let query = Atendimento.query()
  49. .join("atendimento_avaliacoes as av", "av.AtendimentoId", "atendimentos.Id")
  50. .where("av.Avaliavel", true)
  51. .whereIn("atendimentos.Id", atendimentosComAudioPendente)
  52. .select("atendimentos.Id", "atendimentos.Codigo")
  53. .orderBy("atendimentos.Abertura", "desc");
  54. if (LIMITE) query = query.limit(LIMITE);
  55. const alvos = await query;
  56. const total = alvos.length;
  57. console.log(`[reavaliar-audios] iniciando: ${total} atendimentos`);
  58. let processados = 0;
  59. let erros = 0;
  60. const t0 = Date.now();
  61. for (const a of alvos) {
  62. if (JANELA) await aguardarJanela(JANELA_INICIO, JANELA_FIM);
  63. try {
  64. await avaliarAtendimento(a.Id);
  65. } catch (err) {
  66. erros += 1;
  67. console.warn(`[reavaliar-audios] erro em ${a.Codigo}: ${err.message}`);
  68. } finally {
  69. processados += 1;
  70. if (processados % 25 === 0 || processados === total) {
  71. const s = Math.round((Date.now() - t0) / 1000);
  72. const ritmo = s / processados;
  73. const restamMin = Math.round(((total - processados) * ritmo) / 60);
  74. console.log(
  75. `[reavaliar-audios] progresso: ${processados}/${total} | erros: ${erros} | ${Math.round(s / 60)}min decorridos | ~${restamMin}min restantes`
  76. );
  77. }
  78. }
  79. }
  80. console.log(`[reavaliar-audios] concluído: ${processados}/${total} | erros: ${erros} | ${Math.round((Date.now() - t0) / 60000)}min total`);
  81. process.exit(0);
  82. }
  83. main().catch((err) => {
  84. console.error("[reavaliar-audios] falha fatal:", err);
  85. process.exit(1);
  86. });