| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- import "dotenv/config";
- import fs from "node:fs";
- import path from "node:path";
- import { fileURLToPath } from "node:url";
- import { Atendimento } from "../src/models/Atendimento.model.js";
- import { AtendimentoAvaliacao } from "../src/models/AtendimentoAvaliacao.model.js";
- import { gerarAvaliacaoViaLLM } from "../src/services/atendimentoAvaliacaoService.js";
- import { media, desvioPadrao } from "./utils/estatistica.js";
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
- function argValor(nome, padrao) {
- const idx = process.argv.indexOf(`--${nome}`);
- return idx > -1 ? process.argv[idx + 1] : padrao;
- }
- const AMOSTRA = Number(argValor("amostra", 20));
- const REPETICOES = Number(argValor("repeticoes", 5));
- const SETOR = argValor("setor", null);
- const IDS = argValor("ids", null)
- ?.split(",")
- .map((s) => Number(s.trim()))
- .filter(Number.isFinite) ?? null;
- const CAMPOS_CATEGORICOS = ["Resolvido", "Sentimento", "StatusCancelamento"];
- async function selecionarAmostra() {
- if (IDS?.length) return IDS.map((Id) => ({ Id }));
- let query = AtendimentoAvaliacao.query()
- .where("Avaliavel", true)
- .join("atendimentos as a", "a.Id", "atendimento_avaliacoes.AtendimentoId")
- .select("atendimento_avaliacoes.AtendimentoId as Id")
- .orderByRaw("RAND()")
- .limit(AMOSTRA);
- if (SETOR) query = query.where("a.Setor", SETOR);
- return query;
- }
- async function avaliarRepetidamente(atendimentoId) {
- const atendimento = await Atendimento.query()
- .findById(atendimentoId)
- .withGraphFetched("[cliente, mensagens]")
- .modifyGraph("mensagens", (q) => q.orderBy("Timestamp", "asc").orderBy("Id", "asc"));
- const mensagens = atendimento.mensagens ?? [];
- const execucoes = [];
- for (let i = 0; i < REPETICOES; i++) {
- const { dados } = await gerarAvaliacaoViaLLM(atendimento, mensagens);
- execucoes.push(dados);
- }
- return { atendimento, execucoes };
- }
- function metricasAtendimento(codigo, execucoes) {
- const scores = execucoes.map((e) => e.ScoreAtendente).filter((s) => s !== null);
- const scoreRange = scores.length ? Math.max(...scores) - Math.min(...scores) : null;
- const scoreDesvio = scores.length ? Number(desvioPadrao(scores).toFixed(2)) : null;
- const concordancia = {};
- for (const campo of CAMPOS_CATEGORICOS) {
- const valores = execucoes.map((e) => e[campo]);
- concordancia[campo] = new Set(valores).size === 1;
- }
- return {
- codigo,
- scores,
- scoreRange,
- scoreDesvio,
- concordancia
- };
- }
- function resumoAgregado(metricas) {
- const comScore = metricas.filter((m) => m.scoreRange !== null);
- const rangeMedio = comScore.length ? Number(media(comScore.map((m) => m.scoreRange)).toFixed(2)) : null;
- const desvioMedio = comScore.length ? Number(media(comScore.map((m) => m.scoreDesvio)).toFixed(2)) : null;
- const distribuicaoRange = { range0: 0, range1: 0, range2mais: 0 };
- for (const m of comScore) {
- if (m.scoreRange === 0) distribuicaoRange.range0 += 1;
- else if (m.scoreRange === 1) distribuicaoRange.range1 += 1;
- else distribuicaoRange.range2mais += 1;
- }
- const taxaConcordancia = {};
- for (const campo of CAMPOS_CATEGORICOS) {
- const concordantes = metricas.filter((m) => m.concordancia[campo]).length;
- taxaConcordancia[campo] = Number(((concordantes / metricas.length) * 100).toFixed(1));
- }
- return {
- totalAtendimentos: metricas.length,
- scoreRangeMedio: rangeMedio,
- scoreDesvioMedio: desvioMedio,
- distribuicaoRange,
- taxaConcordanciaPercent: taxaConcordancia
- };
- }
- async function main() {
- const alvos = await selecionarAmostra();
- const total = alvos.length;
- console.log(`[variancia-avaliacao] amostra: ${total} atendimentos | ${REPETICOES} repetições cada | ${total * REPETICOES} chamadas ao LLM`);
- const metricas = [];
- const detalhes = [];
- let processados = 0;
- let erros = 0;
- const t0 = Date.now();
- for (const { Id } of alvos) {
- try {
- const { atendimento, execucoes } = await avaliarRepetidamente(Id);
- const m = metricasAtendimento(atendimento.Codigo, execucoes);
- metricas.push(m);
- detalhes.push({ atendimentoId: Id, codigo: atendimento.Codigo, execucoes });
- } catch (err) {
- erros += 1;
- console.warn(`[variancia-avaliacao] erro no atendimento ${Id}: ${err.message}`);
- } finally {
- processados += 1;
- const s = Math.round((Date.now() - t0) / 1000);
- const ritmo = s / processados;
- const restamMin = Math.round(((total - processados) * ritmo) / 60);
- console.log(
- `[variancia-avaliacao] progresso: ${processados}/${total} | erros: ${erros} | ${Math.round(s / 60)}min decorridos | ~${restamMin}min restantes`
- );
- }
- }
- const resumo = resumoAgregado(metricas);
- console.log("\n=== Resumo ===");
- console.log(`Atendimentos processados: ${resumo.totalAtendimentos} (erros: ${erros})`);
- console.log(`Score — range médio: ${resumo.scoreRangeMedio} | desvio padrão médio: ${resumo.scoreDesvioMedio}`);
- console.log(
- `Distribuição de range: estável (0) = ${resumo.distribuicaoRange.range0} | range 1 = ${resumo.distribuicaoRange.range1} | range ≥2 = ${resumo.distribuicaoRange.range2mais}`
- );
- console.log("Concordância entre execuções (campos categóricos):");
- for (const [campo, pct] of Object.entries(resumo.taxaConcordanciaPercent)) {
- console.log(` ${campo}: ${pct}%`);
- }
- const backupDir = path.resolve(__dirname, "logs");
- fs.mkdirSync(backupDir, { recursive: true });
- const backupPath = path.join(backupDir, `variancia-avaliacao-${new Date().toISOString().replace(/[:.]/g, "-")}.json`);
- fs.writeFileSync(backupPath, JSON.stringify({ config: { amostra: AMOSTRA, repeticoes: REPETICOES, setor: SETOR, ids: IDS }, resumo, metricas, detalhes }, null, 2));
- console.log(`\n[variancia-avaliacao] resultado completo salvo em ${backupPath}`);
- process.exit(0);
- }
- main().catch((err) => {
- console.error("[variancia-avaliacao] falha fatal:", err);
- process.exit(1);
- });
|