Evolui a automacao (V1 preservada, intocada) para uma V2 com: - Camada deterministica (src/v2): calendario operacional (porta fiel da logica da V1), queries SQL somente-leitura (indicadores, ranking com IDs, contexto de chamados, baseline historico), deteccao de anomalia (spike de categoria/cliente, concentracao, tendencia de backlog), memoria/fingerprint de insights com transicao de estado (new -> persisting -> normalizing -> resolved), mensagens curtas (fallback V1 e V2). 97 testes automatizados (node --test). - Camada de IA (structured output via OpenAI, schema JSON validado em codigo, nunca confia em numeros vindos da IA): prompt de "Analista Operacional", fallback automatico e seguro quando a IA falha/expira/ retorna JSON invalido. - Workflow n8n "[DEV] GLPI - Relatorio Operacional IA v2" (id 3x5kb58TeBCvirm6, INATIVO) criado e validado ao vivo no DEV (devn8n.gtigrupo.com.br) via webhook temporario (removido apos os testes) com SHADOW_MODE/DEV_MODE ligados — nenhuma mensagem real enviada, nenhuma escrita no GLPI. - tools/build_v2_workflow.js + build_v2_workflow_assemble.js: geram o workflow n8n a partir de src/v2/*.js (Code nodes remotos nao importam arquivos locais), mantendo uma unica fonte de verdade testada. Bugs reais encontrados e corrigidos durante a validacao ao vivo: - glpi_tickets nao tem coluna "solution" nesta versao do schema. - Nodes MySQL/DataTable sem "Execute Once" executavam 1x por item recebido do node anterior; a cadeia amplificava ate ~76 mil execucoes na query de baseline por cliente, causando o timeout real observado. Corrigido com executeOnce=true nos 8 nodes afetados e reescrita da query de baseline para nao depender de JOIN com tabela de dimensao. - IF "Pode Enviar" usava o operador booleano invertido (checava "e true" em vez de "e false"), o que quase permitia envio real ao Slack mesmo com SHADOW_MODE/DEV_MODE ligados. - gpt-5.5 exige max_completion_tokens (nao max_tokens) e gasta o orcamento em tokens de raciocinio; ajustado para 3000 tokens + reasoning_effort=low. Validado ao vivo contra dados reais do GLPI DEV (20/08/2026): IA gerou insights reais com evidencia auditavel, memoria persistiu entre execucoes (occurrences incrementando, normalizacao detectada), resultado 200 confirmando envio suprimido (shadow mode). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
48 lines
1.6 KiB
JavaScript
48 lines
1.6 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const {
|
|
listCalendarDays,
|
|
pivotDailyRows,
|
|
bucketIntoChunks,
|
|
bucketMapIntoChunks,
|
|
} = require('../src/v2/baseline');
|
|
|
|
test('listCalendarDays inclui inicio e fim, sem pular nenhum dia', () => {
|
|
const dias = listCalendarDays('2026-08-01', '2026-08-05');
|
|
assert.deepEqual(dias, ['2026-08-01', '2026-08-02', '2026-08-03', '2026-08-04', '2026-08-05']);
|
|
});
|
|
|
|
test('pivotDailyRows preenche com zero os dias sem chamados (nao enviesa a media)', () => {
|
|
const allDayKeys = ['2026-08-01', '2026-08-02', '2026-08-03'];
|
|
const rows = [
|
|
{ dia: '2026-08-01', nome: 'Hardware', total: 2 },
|
|
{ dia: '2026-08-03', nome: 'Hardware', total: 4 },
|
|
];
|
|
const pivot = pivotDailyRows(rows, allDayKeys);
|
|
assert.deepEqual(pivot.get('Hardware'), [2, 0, 4]);
|
|
});
|
|
|
|
test('pivotDailyRows aceita objetos Date do driver MySQL, nao so string', () => {
|
|
const allDayKeys = ['2026-08-01', '2026-08-02'];
|
|
const rows = [{ dia: new Date(2026, 7, 1), nome: 'Software', total: 5 }];
|
|
const pivot = pivotDailyRows(rows, allDayKeys);
|
|
assert.deepEqual(pivot.get('Software'), [5, 0]);
|
|
});
|
|
|
|
test('bucketIntoChunks agrupa dias em blocos de 7 (semana)', () => {
|
|
const serie = [1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2];
|
|
assert.deepEqual(bucketIntoChunks(serie, 7), [7, 14]);
|
|
});
|
|
|
|
test('bucketMapIntoChunks aplica o bucket em todas as chaves do mapa', () => {
|
|
const mapa = new Map([
|
|
['A', [1, 1, 1, 1, 1, 1, 1]],
|
|
['B', [2, 0, 0, 0, 0, 0, 0]],
|
|
]);
|
|
const bucketed = bucketMapIntoChunks(mapa, 7);
|
|
assert.deepEqual(bucketed.get('A'), [7]);
|
|
assert.deepEqual(bucketed.get('B'), [2]);
|
|
});
|