Files
SLACK_COORD_RELATORIO/test/baseline.test.js
T
Automacao GLPIandClaude Sonnet 5 bdfa675d89 Adiciona V2 do relatorio GLPI: camada deterministica + IA com fallback
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>
2026-08-25 08:49:45 -03:00

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]);
});