16 KiB
Relatório Final — Coordenador GLPI V2
Data: 2026-08-24. Branch: feature/coordenador-glpi-v2 (19 commits, working tree limpo).
RECOVERY
- Projeto encontrado:
D:\PROJETOS\Coordenador GLPI\Coordenador GLPI— projeto n8n puro (sem backend tradicional), com workflow importável (Coordenador GLPI - Chamados Abertos Slack.n8n.json), query SQL (sql/top_10_chamados_abertos_glpi.sql),docker-compose.ymlpara n8n local e README. - Git: repositório sem nenhum commit (
git statusmostrava tudo untracked). Não havia branch, não havia HEAD. - Alterações preexistentes: nenhuma (nada estava em progresso).
.envreal preservado (gitignored, nunca commitado). - Preservação: commitei o V1 exatamente como encontrado no disco (
1eb1f80, "baseline import") antes de qualquer mudança, como ponto de reversão. Toda a evolução V2 foi feita em branch separada (feature/coordenador-glpi-v2), nunca emmaster.
COMO FUNCIONAVA (V1, comprovado no código)
- Cron
0 8 * * 1-5(+ Manual Trigger) → nodeConfig(Set) comslack_webhook_url,glpi_base_url,dias_sem_interacao_alerta,horas_sla_risco→ monta SQL em um Code node → executa direto no MySQL do GLPI. - SQL calcula
score_criticidade(SLA vencido +100, SLA em risco +45, dias sem interação +10/+25/+35, idade até +30, prioridade×8, urgência×5, impacto×5, sem técnico +25) e seleciona por blocos fixos: 3 SLA vencido, 3 sem interação, 2 sem técnico, 2 prioridade alta recente, complementando com ranking geral até 10. - Exclui categorias
ADMINISTRATIVO%,DESENVOLVIMENTO%,INFRA%(porcompletename/name) e chamados comstatus IN (5,6)(solucionado/fechado) ouis_deleted. - Reincidência: contada via
staticDatado próprio n8n (não é banco) — conta dias seguidos que o chamado aparece, mas não penaliza o score, só informa. - Mensagem final montada por código; um AI Agent (Z IA/GLM) só revisa o texto (clareza), nunca decide nada — há validação determinística (todos os
#ide links precisam estar presentes, semmotivo/ação/prazo) com fallback para o texto sem IA se a revisão falhar.
CAUSA TÉCNICA DO LOOP
O score é dominado por sinais que só crescem com o tempo (SLA vencido, dias sem interação, idade), e a reincidência é apenas informativa — não interrompe nem penaliza. Um chamado bloqueado por terceiro/solicitante, sem nenhuma ação possível pelo suporte, continua pontuando alto todos os dias e reocupando os blocos "SLA vencido"/"sem interação", empurrando chamados com ação real (mas sinais mais baixos) para fora do Top 10.
ARQUITETURA IMPLEMENTADA
Pipeline em 7 estágios (seleção → filtros → memória/cache → contexto → IA → regras determinísticas → score/ranking → mensagem), implementado como um workflow n8n novo e separado, deployado no DEV, inativo:
[Manual Trigger / Schedule Trigger (desligado)] → Config
→ Code: Montar SQL Candidatos → MySQL: Buscar Candidatos (até CANDIDATE_POOL_SIZE=60, sem corte em blocos)
→ Code: Normalizar → Code: Montar SQL Contexto → MySQL: Buscar Contexto (followups+tarefas, 1 query UNION ALL)
→ Code: Mesclar Contexto e Calcular Hash
→ [Merge: Aguardar Memoria] ← Data Table: Buscar Memoria (ramo paralelo, dispara 1x a partir do Config)
→ Code: Decidir Necessidade de IA (cache/hash, corta em AI_MAX_CANDIDATES=30)
→ IF Precisa IA? → IF IA Ligada? → Code: Preparar Prompt → HTTP OpenAI (structured output) → Code: Validar
(senão) → Code: Fallback IA Desligada
(senão) → Code: Reusar Classificação em Cache
→ Merge: Consolidar Classificações → Code: Calcular Score
→ Code: Preparar Registro Memória (efeito colateral) → Data Table: Upsert Memória
→ Code: Selecionar Top 10 → Code: Montar Mensagem Curta → Code: Montar Digest Coordenação
→ Code: Preparar Registro de Envio (efeito colateral) → Data Table: Registrar Envio
→ [Merge: Unir Shadow] ← MySQL: Legado Top 10 (query V1 verbatim, ramo paralelo)
→ Code: Comparar Legacy x V2 → Data Table: Registrar Shadow Run
Debug - Ver Painel (webhook) → Data Table: Buscar Shadow Runs Recentes → Code: Montar Painel → Responder
Duas descobertas de arquitetura corrigidas durante a implementação (documentadas nos commits):
- Nodes MySQL "irmãos" sem sincronização garantida — n8n não garante ordem relativa entre ramos que só compartilham um ancestral comum distante. Resolvido com nodes
Mergereais como portão de sincronização (contexto, memória, shadow) em vez de depender de$('Node').all()sem uma aresta real garantindo execução prévia. Data Tablede leitura em massa ("pegar toda a tabela") não pode ficar inline na cadeia de 41 candidatos — ela roda por item de entrada e seu próprio output substitui os itens recebidos. Corrigido movendo a busca de memória para um ramo paralelo.
ARQUIVOS ALTERADOS
logic/*.js(10 módulos) +*.test.js— lógica determinística pura, testada.sql/candidatos_v2_glpi.sql— base do V1 sem corte em blocos,LIMIT {CANDIDATE_POOL_SIZE}.sql/contexto_glpi.sql— followups + tarefas numa query só (UNION ALL, marcada portipo).sql/top_10_chamados_abertos_glpi.sql— intocado (usado verbatim no ramo shadow).n8n/nodes/*.node.js(17 arquivos) — drivers dos Code nodes, fonte de verdade.n8n/workflow.template.json+tools/build-workflow.js— template + script que gera o JSON final inlinandologic/*.jsnos Code nodes.n8n/deployed/coordenador-glpi-v2.n8n.json— snapshot do que está no DEV.docs/superpowers/{specs,plans,notes}/*— spec, plano, notas de introspecção e este relatório.- V1 original: nenhum arquivo alterado.
BANCO / PERSISTÊNCIA
Duas n8n Data Tables novas no DEV (recurso nativo do n8n, não infraestrutura nova):
coordenador_glpi_memoria(idwQZSlILIApb6aErW) — 17 colunas:ticket_id, last_analyzed_at, last_sent_at, last_context_hash, last_meaningful_change_at, ai_classification, ai_confidence, blocker_type, waiting_for, next_action, next_review_at, times_sent, times_sent_30d, consecutive_days_sent, last_score, last_ai_reason, updated_at.coordenador_glpi_shadow_runs(idV74oP8TMq6BwJelw) —run_at, legacy_ids, v2_ids, added, removed, summary.
Substitui o staticData do V1 (que o próprio README do V1 já apontava como frágil: some se o workflow for reimportado). Nenhuma tabela do GLPI foi alterada — todo acesso ao GLPI é SELECT.
IA
- Provedor: OpenAI, modelo
gpt-4o-mini(configurável no node Config). - Credencial
OpenAI - Coordenador GLPI(criada por mim, chave colada pelo usuário) — não reaproveitei nenhuma credencial de outro workflow. - Formato:
response_format: json_schemaestrito (11 classes:ACTIONABLE_NOW, UNASSIGNED_ACTIONABLE, WAITING_REQUESTER, WAITING_VENDOR, WAITING_APPROVAL, SCHEDULED, DEPENDENCY_BLOCKED, NEEDS_COORDINATION, PROJECT_LONG_TERM, DUPLICATE_OR_OBSOLETE, INSUFFICIENT_CONTEXT), validado em código (logic/classification.js). - Cache: hash de contexto (
logic/hash.js) ignora campos que só mudam com o tempo; só entra em IA quem tem hash novo, sem memória, ou passou donext_review_at. - Fallback:
ai_disabled,ai_error,invalid_json,ai_max_candidates_cap— todos tratados comoACTIONABLE_NOWcomconfidence: 0, marcadosis_fallback: true, sem bônus de acionabilidade no score (comportamento conservador, tipo V1). - Custo controlado: nesta execução real, 41 candidatos → só 30 foram para a IA (
AI_MAX_CANDIDATES), 11 ficaram em fallback controlado.
REGRAS
- Acionabilidade: score final = baseline (sinais baratos, capado) + bônus/penalidade por classe da IA (ACTIONABLE_NOW +200, UNASSIGNED_ACTIONABLE +180, WAITING_* −150, SCHEDULED/NEEDS_COORDINATION −1000) − penalidade de cooldown. Confiança abaixo de
AI_CONFIDENCE_THRESHOLD(0.6) corta o bônus pela metade. - Bloqueios:
WAITING_REQUESTER/WAITING_VENDOR/SCHEDULED/DEPENDENCY_BLOCKEDsem mudança de contexto ficam bloqueados para reenvio;NEEDS_COORDINATIONsempre sai do Top 10 diário. - Cooldown: penalidade crescente (até -90) para quem foi enviado recentemente sem mudança de contexto; chamado crítico (SLA vencido + prioridade ≥4) quebra o cooldown.
- Mudança significativa: hash ignora
dias_aberto/dias_sem_interacao; só muda com evento real (status, técnico, categoria, novo followup/tarefa). - Top 10: fixo, com diversidade leve entre grupos (cap de 50% por grupo na primeira passada, preenchido pelos próximos por score — não bloqueia emergência real).
MENSAGEM (exemplo real, gerado no DEV em 24/08/2026 19:25 UTC)
@time 10 chamados para atenção hoje — 24/08/2026
1. #38611 • Colocar etiqueta no espelho na sala 03 (Ultrassom)
SEM TÉCNICO • parado 3d
→ Revisar chamado
https://glpi.suaempresa.com.br/front/ticket.form.php?id=38611
5. #37389 • Manutenção preventiva
Gabriel Lessa • SLA vencido • parado 9d
→ Completar informação
https://glpi.suaempresa.com.br/front/ticket.form.php?id=37389
10. #38642 • Recolher nobreak do servidor da ortocentro
Wagner Silva • parado 0d
→ Revisar chamado
https://glpi.suaempresa.com.br/front/ticket.form.php?id=38642
(lista completa nos logs; aqui vão só 3 de 10 pra ilustrar). Achei e corrigi, logo depois desta captura, um bug de espaçamento (linha em branco quádrupla entre o cabeçalho e o primeiro chamado) — já corrigido e testado, mas não recapturei a execução só por causa disso.
Comparado à mensagem atual do V1 (que já é curta), esta é do mesmo tamanho por chamado mas sem os campos "motivo/ação/prazo" verbosos que o V1 tinha decidido remover; a real redução está em não repetir os mesmos 4 chamados bloqueados todos os dias (ver Shadow Mode abaixo), não no tamanho da mensagem em si — o V1 já era enxuto.
TESTES
npm test: 54/54 passando, 10 arquivos de módulo + 1 de sanidade + 1 de build + 1 de guarda de SQL.- Cobre os 17 cenários pedidos: acionável entra, aguardando cliente não monopoliza, aguardando fornecedor não repete diariamente, agendado não entra antes da data, sem técnico + acionável prioriza, cooldown penaliza reenvio sem mudança, mudança significativa libera reavaliação, alteração só de "dias aberto" não muda hash, administrativo continua excluído (testado direto no SQL gerado), IA indisponível aciona fallback, JSON inválido não quebra, baixa confiança recebe tratamento, contexto inalterado usa cache,
NEEDS_COORDINATIONsai do Top10 diário, Top final nunca passa de 10, mensagem final é curta, regra crítica quebra cooldown.
SHADOW MODE (execução real, id 13599, 24/08/2026 19:25:39–19:25:50 UTC, 11.1s)
LEGACY (query V1, verbatim):
#37389 #34419 #36530 #33492 #36488 #36897 #38611 #38583 #38319 #38642
V2:
#38611 #38583 #38648 #38645 #37389 #34419 #36530 #38216 #35059 #38642
Saíram na V2 (estavam no Legacy):
#33492 — motivo: INSUFFICIENT_CONTEXT (IA não achou base para next_action)
#36488 — motivo: WAITING_REQUESTER (aguardando "Patricia" sobre troca de carcaça)
#36897 — motivo: INSUFFICIENT_CONTEXT
#38319 — motivo: WAITING_VENDOR (aguardando fornecimento de baterias)
Entraram na V2 (não estavam no Legacy):
#38648, #38645 — ACTIONABLE_NOW (fallback por limite de IA nesta rodada, precisam triagem humana)
#38216 — INSUFFICIENT_CONTEXT ("Não há informações suficientes sobre a próxima ação ou bloqueios")
#35059 — INSUFFICIENT_CONTEXT ("Informações insuficientes para determinar ações concretas")
Isso é o mecanismo funcionando: 2 dos 4 removidos saíram porque a IA identificou, com evidência real do GLPI (apontamentos/tarefas), que dependiam de terceiro ou do solicitante — exatamente o padrão que a missão queria parar de repetir todo dia.
OBSERVABILIDADE (números reais desta execução)
candidatos iniciais: 41 (de até 60 configurados; backlog real após exclusão administrativa)
memoria previa: 0 linhas (primeira execução, tabela nova)
enviados para IA: 30 (limite AI_MAX_CANDIDATES)
fallback por limite de IA (ai_max_candidates_cap): 11
--- classificacoes reais da IA (30) ---
INSUFFICIENT_CONTEXT: 20
WAITING_VENDOR: 6
WAITING_REQUESTER: 4
ACTIONABLE_NOW / UNASSIGNED_ACTIONABLE (real, nao-fallback): 0
top final: 10 (nunca ultrapassou)
Achado honesto: nesta execução, a IA nunca classificou nenhum chamado como genuinamente ACTIONABLE_NOW/UNASSIGNED_ACTIONABLE com confiança real — ou o chamado tinha uma dependência clara (fornecedor/solicitante) ou faltava informação. Isso bate com a própria hipótese da missão (chamados antigos sem ação real) mas também expõe uma melhoria pendente: o campo content do GLPI vem com entidades HTML escapadas duas vezes (<p>), o que pode estar deixando o texto mais difícil de interpretar. Registrado como pendência, não escondido.
DEPLOY DEV
- Workflow
[DEV] Coordenador GLPI - Chamados Abertos v2 (Shadow), idy6JFEnp7Z2IJEUe2, 37 nodes,active: false. n8n_validate_workflow(profilestrict): 0 erros, 1 aviso menor (estilo de tratamento de erro, não bloqueante).- Healthcheck: execução real
13599terminou comstatus: success— prova de que o serviço está de pé e o workflow roda de ponta a ponta contra dados reais. - Método de teste: ativei o workflow só pelo tempo de um disparo (webhook temporário adicionado e removido a cada tentativa), sempre desativando imediatamente depois — a
Schedule Triggernunca chegou a disparar de verdade (não estava nem perto de 8h no horário testado).
PRODUÇÃO
Produção alterada: NÃO
Envio para grupo de produção realizado: NÃO
Escrita em chamados reais: NÃO
Todo acesso ao GLPI foi SELECT. Nenhum outro workflow do DEV ([DEV] GTI - GLPI x WhatsApp v7, Enforcer - GLPI, Klingo/ClinicAll/SysLife/ZPRO/Asaas) foi tocado. Nenhuma mensagem foi enviada a nenhum canal real — a mensagem fica só nos logs de execução e na Data Table.
PENDÊNCIAS REAIS
- HTML duplamente escapado no
contentdo GLPI (<p>) pode estar prejudicando a leitura da IA — vale decodificar/limpar emlogic/context.jsantes de mandar pra IA. blocker_typeàs vezes vem como string"null"em vez denullde verdade (comportamento do modelo, não do meu código) — inofensivo hoje, mas vale normalizar.- Digest de coordenação (
Code - Montar Digest Coordenacao) ainda não foi validado com um caso real deNEEDS_COORDINATION(nenhum chamado caiu nessa classe nesta execução) — a lógica está coberta por teste unitário, mas não por execução real. - Cron desligado por design — a ativação para rodar automaticamente às 8h é uma decisão do usuário, não foi feita (e não deveria ser feita sem aprovação explícita).
- Duas Data Tables de terceiros (
dev_glpi_v2_insights_memory,dev_glpi_v2_run_history) apareceram no DEV no meio desta sessão, de origem que não identifiquei — vale confirmar com quem estiver por trás se é trabalho duplicado.
VEREDITO
A V2 está pronta para continuar em shadow mode — validada estruturalmente (n8n_validate_workflow), testada unitariamente (54/54) e comprovada com uma execução real de ponta a ponta contra o banco de produção do GLPI (somente leitura) e a API real da OpenAI, produzindo um diff Legacy×V2 defensável. Antes de virar candidata a substituir o envio real, sugiro: (a) rodar mais algumas vezes em dias diferentes para ver o comportamento do cache/cooldown ao longo do tempo (hoje só temos a primeira execução, sem histórico), (b) resolver a pendência 1 (HTML escapado) e comparar se isso muda a proporção de INSUFFICIENT_CONTEXT, (c) revisar manualmente as 10 mensagens junto com o time de suporte antes de decidir mudar o canal real.