| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- import fs from "node:fs";
- import path from "node:path";
- import { fileURLToPath } from "node:url";
- import { AtendimentoAvaliacao } from "../src/models/AtendimentoAvaliacao.model.js";
- import { AtendimentoMensagem } from "../src/models/AtendimentoMensagem.model.js";
- import { avaliarPendentes, contarPendentes } from "../src/services/atendimentoAvaliacaoService.js";
- import { sincronizarTodosProtocolosAbertos } from "../src/services/atendimentosService.js";
- import { EXPEDIENTE_POR_SETOR, estaForaExpediente } from "../src/config/expediente.js";
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
- const APLICAR = process.argv.includes("--apply");
- const FORCAR_TODOS = process.argv.includes("--forcar-todos");
- const SEM_SYNC = process.argv.includes("--sem-sync");
- const loteIdx = process.argv.indexOf("--lote");
- const LOTE = loteIdx > -1 ? Number(process.argv[loteIdx + 1]) : 20;
- const setorIdx = process.argv.indexOf("--setor");
- const SETOR = setorIdx > -1 ? process.argv[setorIdx + 1] : null;
- const limiteIdx = process.argv.indexOf("--limite");
- const LIMITE = limiteIdx > -1 ? Number(process.argv[limiteIdx + 1]) : null;
- const expectIdx = process.argv.indexOf("--expect-count");
- const EXPECT_COUNT = expectIdx > -1 ? Number(process.argv[expectIdx + 1]) : null;
- const SETORES_COM_GRADE = Object.keys(EXPEDIENTE_POR_SETOR);
- async function buscarCandidatos() {
- const query = AtendimentoAvaliacao.query()
- .where("Avaliavel", true)
- .join("atendimentos as a", "a.Id", "atendimento_avaliacoes.AtendimentoId")
- .select("atendimento_avaliacoes.AtendimentoId", "atendimento_avaliacoes.ScoreAtendente", "a.Setor")
- .orderBy("atendimento_avaliacoes.AtendimentoId", "asc");
- if (SETOR) query.where("a.Setor", SETOR);
- if (!FORCAR_TODOS) query.whereIn("a.Setor", SETORES_COM_GRADE);
- return query;
- }
- async function temMensagemForaExpediente(atendimentoId, setor) {
- const mensagensCliente = await AtendimentoMensagem.query()
- .where("AtendimentoId", atendimentoId)
- .where("Resposta", 0)
- .select("Timestamp");
- return mensagensCliente.some((m) => estaForaExpediente(setor, m.Timestamp));
- }
- async function filtrarAlvos(candidatos) {
- if (FORCAR_TODOS) return { alvos: candidatos, semMensagemForaExpediente: 0 };
- const alvos = [];
- let semMensagemForaExpediente = 0;
- for (const c of candidatos) {
- if (await temMensagemForaExpediente(c.AtendimentoId, c.Setor)) {
- alvos.push(c);
- } else {
- semMensagemForaExpediente += 1;
- }
- }
- return { alvos, semMensagemForaExpediente };
- }
- async function main() {
- const totalQuery = AtendimentoAvaliacao.query()
- .where("Avaliavel", true)
- .join("atendimentos as a", "a.Id", "atendimento_avaliacoes.AtendimentoId");
- if (SETOR) totalQuery.where("a.Setor", SETOR);
- const totalAvaliavel = await totalQuery.resultSize();
- const candidatos = await buscarCandidatos();
- const descartadosSetorSemGrade = totalAvaliavel - candidatos.length;
- console.log(`[reavaliar-expediente] ${totalAvaliavel} avaliações Avaliavel=true${SETOR ? ` no setor ${SETOR}` : ""}.`);
- if (!FORCAR_TODOS) {
- console.log(`[reavaliar-expediente] setores com grade de horário: ${SETORES_COM_GRADE.join(", ")}.`);
- console.log(`[reavaliar-expediente] ${descartadosSetorSemGrade} descartados: setor sem grade de horário.`);
- }
- console.log(`[reavaliar-expediente] ${candidatos.length} candidatos após filtro de setor.`);
- const { alvos: todosAlvos, semMensagemForaExpediente } = await filtrarAlvos(candidatos);
- if (!FORCAR_TODOS) {
- console.log(`[reavaliar-expediente] ${semMensagemForaExpediente} descartados: nenhuma mensagem do cliente fora do expediente.`);
- }
- const alvos = LIMITE ? todosAlvos.slice(0, LIMITE) : todosAlvos;
- console.log(`[reavaliar-expediente] ${alvos.length} avaliações serão resetadas para pendente${LIMITE ? ` (--limite ${LIMITE})` : ""}.`);
- if (EXPECT_COUNT !== null && alvos.length !== EXPECT_COUNT) {
- console.error(`[reavaliar-expediente] abortando: esperava ${EXPECT_COUNT}, achei ${alvos.length}.`);
- process.exit(1);
- }
- if (!APLICAR) {
- console.log("[reavaliar-expediente] dry-run (nenhuma alteração feita). Use --apply para executar.");
- process.exit(0);
- }
- if (alvos.length === 0) {
- console.log("[reavaliar-expediente] nada para reavaliar.");
- process.exit(0);
- }
- const backupDir = path.resolve(__dirname, "logs");
- fs.mkdirSync(backupDir, { recursive: true });
- const backupPath = path.join(backupDir, `reavaliar-expediente-${new Date().toISOString().replace(/[:.]/g, "-")}.json`);
- fs.writeFileSync(backupPath, JSON.stringify(alvos, null, 2));
- console.log(`[reavaliar-expediente] backup dos scores antigos salvo em ${backupPath}`);
- if (!SEM_SYNC) {
- console.log("[reavaliar-expediente] passo 1/3: sincronizando protocolos abertos (sincronizarTodosProtocolosAbertos)...");
- try {
- const sync = await sincronizarTodosProtocolosAbertos();
- console.log(
- `[reavaliar-expediente] sync concluído: ${sync.importados}/${sync.processados} importados, ` +
- `${sync.mensagens} mensagens, ${sync.erros.length} erros, ${sync.paginas} página(s).`
- );
- } catch (err) {
- console.error(`[reavaliar-expediente] sync falhou (${err.message}), seguindo mesmo assim.`);
- }
- } else {
- console.log("[reavaliar-expediente] passo 1/3: pulado (--sem-sync).");
- }
- const ids = alvos.map((a) => a.AtendimentoId);
- const pendentesAntes = await contarPendentes();
- await AtendimentoAvaliacao.query().whereIn("AtendimentoId", ids).patch({ UltimaMensagemId: null });
- const pendentesDepois = await contarPendentes();
- console.log(`[reavaliar-expediente] passo 2/3: ${ids.length} avaliações resetadas — fila de pendentes ${pendentesAntes} -> ${pendentesDepois}.`);
- console.log(`[reavaliar-expediente] passo 3/3: drenando fila de pendentes (mesmo fluxo de avaliarTudo.js, lotes de ${LOTE})...`);
- let restantes = pendentesDepois;
- let totalAvaliados = 0;
- let totalSemDialogo = 0;
- const erros = [];
- const inicio = Date.now();
- while (restantes > 0) {
- const t0 = Date.now();
- const r = await avaliarPendentes({ limite: LOTE });
- restantes = await contarPendentes();
- totalAvaliados += r.avaliados;
- totalSemDialogo += r.semDialogo;
- erros.push(...r.erros);
- console.log(
- `[reavaliar-expediente] +${r.avaliados} avaliados, ${r.semDialogo} sem diálogo, ${r.erros.length} erros ` +
- `(${((Date.now() - t0) / 1000).toFixed(0)}s) — restam ${restantes}`
- );
- if (r.erros.length) r.erros.slice(0, 3).forEach((e) => console.log(` erro: ${e.atendimentoId} ${e.erro}`));
- if (r.avaliados === 0 && r.semDialogo === 0 && r.erros.length === 0) {
- console.error("[reavaliar-expediente] lote sem nenhum progresso, abortando para evitar loop infinito.");
- break;
- }
- }
- console.log(
- `[reavaliar-expediente] fila drenada em ${(((Date.now() - inicio)) / 1000 / 60).toFixed(1)}min: ` +
- `${totalAvaliados} avaliados, ${totalSemDialogo} sem diálogo, ${erros.length} erros.`
- );
- const idsSet = new Set(ids);
- const scoreAntigoPorId = new Map(alvos.map((a) => [a.AtendimentoId, a.ScoreAtendente]));
- const atuais = await AtendimentoAvaliacao.query().whereIn("AtendimentoId", ids).select("AtendimentoId", "ScoreAtendente");
- const mudouScore = atuais.filter((a) => idsSet.has(a.AtendimentoId) && a.ScoreAtendente !== scoreAntigoPorId.get(a.AtendimentoId)).length;
- console.log(`[reavaliar-expediente] dos ${ids.length} alvos do backfill, ${mudouScore} tiveram o score alterado.`);
- process.exit(erros.length ? 1 : 0);
- }
- main().catch((e) => {
- console.error("[reavaliar-expediente] falha:", e.message);
- process.exit(1);
- });
|