'use strict'; /** * Montagem das mensagens finais. * * formatFallbackMessage: PORTA quase literal da mensagem da V1 (node * "Code - Preparar indicadores" dos dois workflows *-SQL-Slack.n8n.json). * Usada em dois lugares na V2: * 1. Como fallback real quando a IA falha/timeout/JSON invalido. * 2. Como "mensagem V1" no payload de comparacao do shadow mode. * * formatShortMessage: mensagem curta da V2 (secoes 16 e 18 da missao). */ function formatDataBr(isoDate) { const [ano, mes, dia] = isoDate.split('-'); return `${dia}/${mes}`; } function formatSaldo(saldo) { return saldo > 0 ? `+${saldo}` : String(saldo); } /** Igual ao texto que a V1 ja envia hoje (nao mudar sem motivo). */ function formatFallbackMessage(row) { const ehSemanal = row.tipo_periodo === 'semanal'; const titulo = ehSemanal ? '*Resumo semanal do suporte GLPI*' : '*Resumo diario do suporte GLPI*'; const escopo = ehSemanal ? 'Periodo do relatorio' : 'Data do relatorio'; const unidade = ehSemanal ? 'no periodo' : 'no dia'; const linhas = [ titulo, '', `${escopo}: ${row.periodo_label}`, '', '*Resumo geral*', `- Chamados abertos ${unidade}: ${row.abertos}`, `- Chamados fechados ${unidade}: ${row.fechados}`, `- Pendentes atuais: ${row.pendentes}`, `- Alta prioridade ainda pendentes: ${row.alta_prioridade_pendentes}`, `- Saldo fechados - abertos: ${row.saldo}`, '', '*Categorias mais recorrentes*', ...String(row.categorias_recorrentes || 'Sem registros') .split('\n') .map((l) => `- ${l}`), '', '*Clientes com maior volume*', ...String(row.clientes_volume || 'Sem registros') .split('\n') .map((l) => `- ${l}`), '', '*Observacao*', `- Chamados fechados ${unidade} podem ter sido abertos em datas anteriores.`, ]; return linhas.join('\n'); } /** * Status operacional (semaforo interno, secao 21). So aparece na mensagem * quando agrega valor (usado hoje apenas para escolher o tom da linha de * tendencia/leitura). */ function computeOperationalStatus({ insightsAceitos, tendenciaBacklog }) { const temAlta = insightsAceitos.some((i) => i.severity === 'high'); if (temAlta) return 'CRITICAL'; const temMedia = insightsAceitos.some((i) => i.severity === 'medium'); if (temMedia || tendenciaBacklog.direcao === 'aumentando') return 'ATTENTION'; return 'NORMAL'; } function buildDestaques({ topCategoria, concentracoesCliente, altaPrioridadeDelta, insightsAceitos, unidade }, config) { const destaques = []; if (topCategoria && topCategoria.total > 0) { destaques.push(`${topCategoria.nome} liderou ${unidade} com ${topCategoria.total} chamados.`); } for (const c of concentracoesCliente || []) { destaques.push(`${c.cliente} concentrou ${c.total} chamados (${Math.round(c.share * 100)}% ${unidade}).`); } if (altaPrioridadeDelta && altaPrioridadeDelta.anterior !== null && altaPrioridadeDelta.atual !== altaPrioridadeDelta.anterior) { const verbo = altaPrioridadeDelta.atual > altaPrioridadeDelta.anterior ? 'subiram' : 'cairam'; destaques.push( `Chamados de alta prioridade ${verbo} de ${altaPrioridadeDelta.anterior} para ${altaPrioridadeDelta.atual}.` ); } for (const insight of insightsAceitos) { const ocorrencia = insight.occurrenceText; destaques.push(`${insight.title}${ocorrencia ? ` (${ocorrencia})` : ''}.`); } return destaques.slice(0, config.MAX_REPORT_INSIGHTS); } function buildTrendLine({ status, tendenciaBacklog, tipoPeriodo }) { const rotuloBacklog = tendenciaBacklog.direcao === 'aumentando' ? 'aumento do backlog no periodo' : tendenciaBacklog.direcao === 'diminuindo' ? 'reducao do backlog no periodo' : tendenciaBacklog.direcao === 'indisponivel' ? 'backlog sem historico suficiente para comparacao' : 'backlog estavel no periodo'; const abertura = status === 'CRITICAL' ? 'Operacao requer atencao' : status === 'ATTENTION' ? tipoPeriodo === 'semanal' ? 'Semana com pontos de atencao' : 'Operacao com pontos de atencao' : tipoPeriodo === 'semanal' ? 'Semana equilibrada' : 'Operacao estavel'; return `${abertura}, com ${rotuloBacklog}.`; } /** * Monta a mensagem curta final da V2. * * @param {object} dados - numeros deterministicos + insights ja sanitizados * @param {object} config */ function formatShortMessage(dados, config) { const ehSemanal = dados.tipo_periodo === 'semanal'; const unidade = ehSemanal ? 'no periodo' : 'no dia'; const tituloData = ehSemanal ? `${formatDataBr(dados.data_inicio)} a ${formatDataBr(dados.data_fim)}` : formatDataBr(dados.data_inicio); const titulo = ehSemanal ? `Resumo semanal do suporte — ${tituloData}` : `Resumo do suporte — ${tituloData}`; const status = computeOperationalStatus({ insightsAceitos: dados.insightsAceitos, tendenciaBacklog: dados.tendenciaBacklog, }); const destaques = buildDestaques( { topCategoria: dados.topCategoria, concentracoesCliente: dados.concentracoesCliente, altaPrioridadeDelta: dados.altaPrioridadeDelta, insightsAceitos: dados.insightsAceitos, unidade, }, config ); const linhaTendencia = buildTrendLine({ status, tendenciaBacklog: dados.tendenciaBacklog, tipoPeriodo: dados.tipo_periodo, }); const blocos = [ titulo, '', `${dados.abertos} abertos | ${dados.fechados} fechados | saldo ${formatSaldo(dados.saldo)}`, `${dados.pendentes} pendentes | ${dados.alta_prioridade_pendentes} alta prioridade`, '', ehSemanal ? 'Destaques' : 'Destaques', ]; if (destaques.length === 0) { blocos.push('• Sem anomalias relevantes no periodo.'); } else { for (const d of destaques) blocos.push(`• ${d}`); } blocos.push('', ehSemanal ? 'Leitura' : 'Tendencia', `→ ${linhaTendencia}`); return { texto: blocos.join('\n'), status }; } module.exports = { formatDataBr, formatSaldo, formatFallbackMessage, computeOperationalStatus, buildDestaques, buildTrendLine, formatShortMessage, };