From ce7af94d55b226f147b36c745cddc7b84575a369 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 1 Oct 2026 14:23:46 -0300 Subject: [PATCH] docs: update coordinator project state Co-Authored-By: Claude Sonnet 5.5 --- README.md | 356 +++++++++++++-------------------------- docs/AI_PROJECT_STATE.md | 109 ++++++++++++ 2 files changed, 230 insertions(+), 235 deletions(-) create mode 100644 docs/AI_PROJECT_STATE.md diff --git a/README.md b/README.md index c12a996..5cadceb 100644 --- a/README.md +++ b/README.md @@ -1,248 +1,134 @@ -# Coordenador GLPI - Chamados Abertos +# Coordenador GLPI -Projeto n8n para atuar como coordenador diario de Suporte de TI, analisando chamados abertos no GLPI e enviando no Slack os 10 chamados que precisam de acao imediata. +Automação n8n que analisa os chamados abertos no GLPI todo dia útil, classifica +cada um com IA (contexto real do chamado, não só metadados) e envia no Slack os +10 chamados que realmente precisam de ação humana hoje. -## Objetivo +O repositório tem duas gerações do workflow: -Todos os dias uteis, o workflow consulta o banco do GLPI, calcula um score operacional para cada chamado aberto e envia uma lista priorizada no Slack. +- **V2** (`n8n/`, `logic/`) — versão atual, em produção. Todo este README + descreve o V2. +- **V1** (`Coordenador GLPI - Chamados Abertos Slack.n8n.json`, na raiz) — + versão legada, mantida apenas como rollback. Nunca foi importada nem ativada + na instância n8n de produção; existe só como referência histórica e plano B. -O foco nao e apenas listar chamados antigos. O ranking considera: +## Estado atual (verificado em 2026-08-25) -- SLA vencido ou proximo de vencer -- tempo sem interacao humana -- prioridade, urgencia e impacto -- idade do chamado -- chamado sem tecnico atribuido -- chamados em categorias e entidades reais do GLPI +``` +Workflow: [DEV] Coordenador GLPI - Chamados Abertos v2 (Shadow) (id y6JFEnp7Z2IJEUe2) +active: true +SEND_ENABLED: true +ENVIRONMENT: production +FORCE_SEND: false (execução manual nunca envia de verdade sem essa flag) +Schedule: seg-sex 08:00, timezone America/Sao_Paulo (cron "0 8 * * 1-5") +Testes: 171/171 (node --test) +``` + +O nome do workflow no n8n ainda carrega o prefixo histórico `[DEV]` / +`(Shadow)` — isso é só rótulo visual, não reflete mais o estado real (ativo, +`SEND_ENABLED=true`, `ENVIRONMENT=production`). Renomear é seguro a qualquer +momento, mas não foi feito para não gerar um diff de auditoria sem função. + +Se essa seção estiver desatualizada, o estado de verdade é sempre o que o n8n +responde (`n8n_get_workflow` / `n8n_manage_datatable`), nunca este arquivo. + +## Arquitetura + +``` +GLPI (MySQL, somente leitura) + -> candidatos (pool configurável, sem corte artificial) + -> normalização de texto (decodifica HTML/entities do GLPI) + -> cache de classificação (Data Table, chave = hash do contexto + versão do classificador) + -> IA (OpenAI) classifica só quem mudou de contexto ou nunca foi analisado + -> score determinístico + seleção Top 10 com diversidade por grupo + -> guard de envio (SEND_ENABLED, FORCE_SEND, idempotência por hash da mensagem) + -> Slack (mensagem real, sem rodapé de atribuição do n8n) + -> shadow-diff contra a query legada do V1 (auditoria, não bloqueia envio) +``` + +Cada etapa grava em uma Data Table do n8n (`coordenador_glpi_memoria`, +`coordenador_glpi_envios_digest`, `coordenador_glpi_shadow_runs`) — não há +escrita nenhuma no banco do GLPI. Toda query GLPI é somente leitura; isso é +testado (`tools/build-workflow.test.js` varre `sql/*.sql` e o SQL gerado +procurando `INSERT/UPDATE/DELETE/ALTER/DROP/CREATE/TRUNCATE`). + +### Por que a IA classifica o chamado, não só pontua + +Cada chamado recebe uma classificação semântica (`ACTIONABLE_NOW`, +`UNASSIGNED_ACTIONABLE`, `WAITING_REQUESTER`, `WAITING_VENDOR`, +`WAITING_APPROVAL`, `SCHEDULED`, `DEPENDENCY_BLOCKED`, `NEEDS_COORDINATION`, +`PROJECT_LONG_TERM`, `DUPLICATE_OR_OBSOLETE`, `INSUFFICIENT_CONTEXT`) mais um +`next_action` concreto (nunca frases genéricas tipo "revisar chamado" — isso é +validado e rejeitado, ver `logic/classification.js`). O prompt +(`n8n/nodes/prepare-ai-prompt.node.js`) trata cronologia dos apontamentos como +obrigatória e distingue "precisa contatar alguém" (acionável agora) de "já +contatou e está esperando resposta" (`WAITING_*`). + +### Determinismo do Top 10 + +`logic/diversity.js` desempata candidatos com score igual por `ticket.id` +(ascendente) — sem isso, ordens de chegada diferentes entre execuções +produziam Top10 diferentes para o mesmo dia, o que já causou um envio real +duplicado em teste (ver `docs/superpowers/notes/2026-08-25-relatorio-correcao-final.md`, +seção 5). Regressão coberta em `logic/diversity.test.js`. ## Arquivos -- `Coordenador GLPI - Chamados Abertos Slack.n8n.json`: workflow importavel no n8n -- `sql/top_10_chamados_abertos_glpi.sql`: query base usada pelo workflow -- `.env.example`: variaveis para subir n8n localmente -- `docker-compose.yml`: n8n local via Docker - -## Como configurar - -1. Suba o n8n local, se necessario: - -```powershell -docker compose up -d +``` +logic/*.js Lógica pura, testável com node:test (sem I/O, sem n8n) +n8n/nodes/*.node.js Drivers finos que rodam dentro de Code nodes do n8n + (cabeçalho "// @requires a.js b.js" declara quais + módulos de logic/ precisam ser inlinados) +n8n/workflow.template.json Fonte de verdade da estrutura do workflow + (marcadores __BUILD__:arquivo.js são substituídos + pelo conteúdo de logic/ na hora do build) +n8n/deployed/coordenador-glpi-v2.n8n.json Artefato gerado, importável no n8n +tools/build-workflow.js Inlina logic/ + n8n/nodes/ dentro do template +tools/build-workflow.test.js Regressões de build (dependência transitiva, + alwaysOutputData, columns.mappingMode, etc.) +sql/ Queries GLPI usadas como referência/validação manual +docs/superpowers/ Specs, planos e relatórios de execução (histórico, + não é para reescrever — cada nota é um registro de + um momento específico do projeto) ``` -2. Acesse: +## Build -```text -http://localhost:5678 +```bash +node tools/build-workflow.js +node --test ``` -3. Importe o workflow: - -```text -Coordenador GLPI - Chamados Abertos Slack.n8n.json -``` - -4. Configure a credencial MySQL no node: - -```text -MySQL - Top 10 chamados abertos -``` - -5. Abra o node `Config - Coordenador GLPI` e preencha: - -- `slack_webhook_url`: webhook do canal Slack -- `dias_sem_interacao_alerta`: padrao `2` -- `horas_sla_risco`: padrao `8` - -6. Execute pelo `Manual Trigger` para validar. - -7. Ative o workflow para rodar pelo `Schedule Trigger - Dias uteis 08h`. - -## Metodologia de priorizacao - -Cada chamado aberto recebe um `score_criticidade`, mas a lista final nao e mais apenas o top 10 geral. O workflow monta a lista por blocos para evitar que chamados muito antigos monopolizem todos os dias. - -Blocos do top 10: - -- 3 chamados com SLA vencido mais criticos -- 3 chamados com maior tempo sem interacao -- 2 chamados sem tecnico atribuido -- 2 chamados de prioridade alta mais recentes - -Se algum bloco nao tiver chamados suficientes, o complemento vem do ranking geral. - -O mesmo chamado nao entra duas vezes. Se ele ja apareceu em um bloco anterior, e removido dos blocos seguintes. - -### Score - -Pesos principais: - -- SLA vencido: `+100` -- SLA vencendo nas proximas horas configuradas: `+45` -- chamado sem interacao ha 7 dias ou mais: `+35` -- chamado sem interacao ha 3 a 6 dias: `+25` -- chamado sem interacao ha 1 a 2 dias: `+10` -- prioridade GLPI: `priority * 8` -- urgencia GLPI: `urgency * 5` -- impacto GLPI: `impact * 5` -- idade do chamado: ate `+30` -- sem tecnico atribuido: `+25` - -O resultado e ordenado por: - -1. `score_criticidade` desc -2. chamados com SLA vencido primeiro -3. maior tempo sem interacao -4. chamados mais antigos - -## Reincidencia - -O workflow controla reincidencia usando `staticData` global do n8n, sem gravar nada no banco do GLPI. - -Regra: - -- apareceu 1 vez: normal -- apareceu 2 dias seguidos: marcado como reincidente -- apareceu 3 dias seguidos ou mais: escalar para coordenacao - -Campos salvos no `staticData`: - -- `ultima_data_disparo` -- `ultimos_chamados_enviados` -- `reincidencia_chamados` - -Se o workflow for duplicado, recriado ou importado como novo workflow, esse historico pode ser perdido porque pertence ao workflow no n8n. - -## Tempo sem interacao - -O campo `dias_sem_interacao` considera a maior data encontrada entre: - -- followups publicos do chamado: `date`, `date_mod`, `date_creation` -- tarefas do chamado: `date`, `date_mod`, `date_creation` -- modificacao do chamado: `glpi_tickets.date_mod` -- abertura do chamado: `glpi_tickets.date` - -Isso evita marcar um chamado como parado quando alguem acabou de adicionar ou alterar uma tarefa no GLPI, mesmo que a tarefa esteja planejada para outro dia. - -Se a maior data encontrada estiver no futuro por causa de uma tarefa planejada, o contador fica em `0` dias sem interacao, evitando valores negativos no relatorio. - -## Regra de chamados abertos - -O SQL considera abertos todos os chamados com: - -```sql -is_deleted = false -AND status NOT IN (5, 6) -``` - -Este gatilho principal ignora chamados das categorias: - -- `ADMINISTRATIVO` -- `DESENVOLVIMENTO` -- `INFRA` - -O filtro usa o nome completo da categoria (`completename`) ou o nome simples (`name`) iniciando com esses termos, entao tambem remove subcategorias dessas filas. - -No GLPI, normalmente: - -- `5`: solucionado -- `6`: fechado - -## Campos usados - -A query usa tabelas comuns do GLPI: - -- `glpi_tickets` -- `glpi_itilcategories` -- `glpi_entities` -- `glpi_itilfollowups` -- `glpi_tickettasks` -- `glpi_tickets_users` -- `glpi_users` - -Se sua versao do GLPI tiver customizacoes, valide primeiro a query `sql/top_10_chamados_abertos_glpi.sql` diretamente no banco. - -## Saida no Slack - -O Slack recebe uma mensagem curta com: - -- top 10 chamados para atacar hoje -- tecnico atribuido, quando existir -- SLA -- dias sem interacao -- dias aberto -- reincidencia -- link do chamado, se `glpi_base_url` estiver configurado no workflow - -Campos removidos da mensagem para reduzir ruido: - -- motivo -- acao -- prazo - -Se o agente de IA falhar, o workflow envia a mensagem curta gerada por codigo, sem depender da IA. - -## Agente de IA do n8n com Z IA - -O workflow usa o node: - -```text -AI Agent - Revisar mensagem -``` - -Por padrao, ele esta conectado ao node: - -```text -Z IA Chat Model -``` - -Esse node usa o chat model compativel com OpenAI do n8n, mas a credencial deve ser configurada para a Z IA. - -A IA nao monta mais a mensagem do zero. O node `Code - Reincidencia e mensagem curta` monta a mensagem final de forma deterministica, e a IA apenas revisa clareza e reduz texto. - -Regras do prompt: - -- nao adicionar fatos -- nao criar motivo -- nao criar acao -- nao criar prazo -- nao alterar IDs, tecnicos, datas, SLA, dias, reincidencia ou links -- nao usar tom agressivo - -No n8n, abra o node: - -```text -Z IA Chat Model -``` - -Configure: - -- credencial `Z IA` -- endpoint/base URL da Z IA, se a credencial permitir -- modelo correto disponibilizado pela Z IA, por exemplo `glm-4.5-flash` - -No arquivo importavel deixei o modelo como `glm-4.5-flash`. Se sua conta Z IA usar outro nome de modelo, troque pelo nome exato exibido na plataforma. - -Se aparecer `The resource you are requesting could not be found`, revise principalmente: - -- Base URL da credencial: normalmente `https://api.z.ai/api/paas/v4` -- Modelo: precisa existir na sua conta, por exemplo `glm-4.5-flash` -- Chave API: precisa ser da Z IA, nao da OpenAI -- Node usado: mantenha `Z IA Chat Model` conectado ao `AI Agent - Revisar mensagem` - -Se a Z IA aparecer no seu n8n como node proprio, mantenha o `AI Agent - Revisar mensagem` e substitua somente o subnode `Z IA Chat Model` pelo modelo oficial da Z IA. - -## Recomendacao direta - -Nao trate isso como relatorio bonito. Trate como fila diaria de comando. - -Minha sugestao operacional: - -- rodar as 08h no canal da lideranca de suporte -- exigir responsavel e prazo para cada um dos 10 chamados ate 10h -- repetir apenas os chamados que continuarem parados no dia seguinte -- se um chamado aparecer 3 dias seguidos, escalar para coordenacao/gestao -- separar um segundo workflow para chamados sem tecnico atribuido, porque isso e falha de triagem, nao backlog comum -- criar outro alerta intradiario para SLA vencendo nas proximas 2 horas - -Se quiser resultado real, nao basta notificar. Tem que transformar a mensagem do Slack em ritual diario de cobranca. +O build só precisa rodar de novo quando `logic/*.js`, `n8n/nodes/*.node.js` ou +`n8n/workflow.template.json` mudarem. O artefato gerado +(`n8n/deployed/coordenador-glpi-v2.n8n.json`) **não é a fonte de verdade do +ambiente de produção** — o workflow ativo no n8n foi promovido diretamente via +API (`n8n_update_partial_workflow`) e hoje diverge do template em 3 valores de +configuração de propósito: + +| campo | template (default de deploy limpo) | live (produção) | +|---|---|---| +| `ENVIRONMENT` | `dev` | `production` | +| `SEND_ENABLED` | `false` | `true` | +| `FORCE_SEND` | `false` | `false` | + +Isso é intencional: o template continua "seguro por padrão" para qualquer +reimportação futura (nunca envia sozinho), e a promoção para produção é um ato +explícito e auditável via `n8n_update_partial_workflow`, não algo que um +rebuild acidental possa reverter. Se um dia o workflow live for recriado do +zero a partir do template, repita a promoção manualmente (ver comando na nota +`2026-08-25-relatorio-finalizacao-producao.md`). + +## Rollback + +O V1 (`Coordenador GLPI - Chamados Abertos Slack.n8n.json`) nunca foi +importado no n8n de produção — não há nada para "reverter para V1", ele +continua disponível como plano B caso seja necessário abandonar o V2. Para +desativar o V2 sem tocar em nada mais: `SEND_ENABLED=false` no node +`Config - Coordenador GLPI v2` e/ou desativar o workflow. + +## Credenciais e segredos + +Nenhum segredo vive neste repositório. Tudo (Slack bot token, OpenAI API key, +credencial MySQL do GLPI) é gerenciado pelo credential store do próprio n8n e +referenciado nos nodes só por `{id, name}`. diff --git a/docs/AI_PROJECT_STATE.md b/docs/AI_PROJECT_STATE.md new file mode 100644 index 0000000..62957ff --- /dev/null +++ b/docs/AI_PROJECT_STATE.md @@ -0,0 +1,109 @@ +# Estado do Projeto — Coordenador GLPI + +Última atualização: 2026-09-27 (revisão de documentação; último estado live +verificado por execução real: 2026-08-25). + +## O que é + +Workflow n8n que roda seg-sex 08:00 (`America/Sao_Paulo`), lê chamados abertos +do GLPI (MySQL, read-only), classifica cada um com IA e envia no Slack os 10 +que precisam de ação humana hoje. Duas gerações no repo: **V2** (ativo, +`n8n/` + `logic/`) e **V1** (legado, `Coordenador GLPI - Chamados Abertos +Slack.n8n.json` na raiz — nunca foi importado no n8n de produção, existe só +como rollback). + +## Estado de produção (n8n instance, workflow id `y6JFEnp7Z2IJEUe2`) + +Confirmado por `n8n_get_workflow` em 2026-08-25 após ativação explícita +autorizada pelo usuário ("pode ativar em produção"): + +- `active: true` +- Config node: `ENVIRONMENT=production`, `SEND_ENABLED=true`, `FORCE_SEND=false` +- Schedule Trigger conectado, cron `0 8 * * 1-5`, `settings.timezone=America/Sao_Paulo` +- 43 nodes, nenhum webhook temporário de debug presente +- Credenciais live: MySQL `GLPI` (id `dsVj1nmOIXRvix9n`), Slack `Slack - Bot + Coordenador (Chamados Suporte)` (id `lNqwBBrtLfkRqw2X`), OpenAI `OpenAI - + Coordenador GLPI` (id `WUGriAn5FgCiLza2`) +- Slack destino: canal `chamados-suporte` (id `C0BJNBTPFT5`) + +**Este arquivo não substitui uma verificação live.** Se for tomar qualquer +decisão que dependa do estado atual (reativar, mudar config, investigar um +envio), reconfirme com `n8n_get_workflow` / `n8n_executions` antes — não +assuma que nada mudou desde 25/08 só porque este arquivo diz isso. + +### Limpeza pós-ativação (2026-08-25) + +Durante a fase de correção final, execuções de teste reais enviaram mensagens +ao Slack para ~37 tickets, gravando `last_sent_at`/`times_sent`/ +`consecutive_days_sent` na Data Table `coordenador_glpi_memoria`. Isso foi +resetado manualmente (`last_sent_at`, `times_sent`, `times_sent_30d`, +`consecutive_days_sent` → `null` para essas linhas) para que o primeiro envio +real de produção não sofresse penalidade de cooldown por causa do teste. +`last_context_hash`/`ai_classification`/`next_action` foram preservados (não +há motivo para reclassificar de novo o que não mudou no GLPI). + +## Arquitetura (resumo — detalhe completo no README.md) + +``` +GLPI (MySQL read-only) -> candidatos -> normalização -> cache de classificação +(Data Table, chave = hash do contexto + AI_CLASSIFIER_VERSION) -> IA (OpenAI, +só quem mudou) -> score determinístico -> Top10 com diversidade por grupo +(desempate por ticket.id — ver "bugs reais corrigidos") -> guard de envio +(SEND_ENABLED + FORCE_SEND + idempotência por hash da mensagem) -> Slack +-> shadow-diff contra query legada do V1 (auditoria, não bloqueia) +``` + +`logic/*.js` é lógica pura testável (`node --test`, 171/171 passando em +2026-09-27). `n8n/nodes/*.node.js` são drivers finos que `tools/build-workflow.js` +inlina dentro de `n8n/workflow.template.json` para gerar +`n8n/deployed/coordenador-glpi-v2.n8n.json`. + +**O template local (`ENVIRONMENT=dev`, `SEND_ENABLED=false`) diverge de +propósito do live (`production`/`true`)** — ver README.md seção "Build" para +a tabela completa e o motivo (template = default seguro para reimportação; +promoção para produção é ato manual e auditável, não algo que rebuild reverte). + +## Bugs reais corrigidos (histórico — não reabrir sem nova evidência) + +1. `WAITING_*`/bloqueados entrando no Top10 via `contextChanged` — corrigido + em `logic/cooldown.js`. +2. Rótulo fixo "Validar e executar" sempre vencendo o `next_action` real da + IA — corrigido em `logic/message.js` (`shortActionLabel`). +3. **Crítico**: Top10 não-determinístico (empate de score desempatado pela + ordem de chegada dos itens, que o n8n não garante entre branches + paralelos) causou envio real duplicado com conteúdo diferente no mesmo + dia. Corrigido com desempate por `ticket.id` em `logic/diversity.js`. + Regressão em `logic/diversity.test.js`. +4. `alwaysOutputData` faltando em `Data Table - Buscar Envios Recentes` + quebraria o caminho feliz todo dia sem envio prévio (0 linhas = n8n não + roda os nodes seguintes). +5. `columns.mappingMode` faltando nos nodes de insert da Data Table de envio + (`Cannot convert undefined or null to object`). +6. Rodapé de atribuição automática do n8n no Slack (`includeLinkToWorkflow`) + — desligado explicitamente. +7. Falta de escopo OAuth (`channels:join`) causando `not_in_channel` mesmo + com o bot já no canal — resolvido reinstalando o app Slack com o escopo. + +Detalhe completo de cada um, com execução real que provou a causa raiz, em +`docs/superpowers/notes/2026-08-25-relatorio-correcao-final.md` e +`2026-08-25-relatorio-finalizacao-producao.md`. + +## Restrições que continuam valendo (não renegociar sem pedido explícito do usuário) + +- GLPI é estritamente somente leitura — nenhuma escrita, nunca. +- V1 nunca é apagado; é o rollback. +- Não recalibrar pesos de score/diversidade/pool sem bug real comprovado. +- Segredos só via credential store do n8n — nunca em arquivo do repo. +- Execução manual nunca envia de verdade sem `FORCE_SEND=true` (proteção + contra teste acidental em produção). + +## Onde olhar primeiro + +- Dúvida sobre comportamento atual do workflow → `n8n_get_workflow` (live), + não os arquivos deste repo (o repo é a fonte de verdade da *lógica*, não + necessariamente da config live, ver seção "Build" do README). +- Dúvida sobre por que uma decisão de design foi tomada → + `docs/superpowers/specs/2026-08-24-coordenador-glpi-v2-design.md` e + `docs/superpowers/plans/2026-08-24-coordenador-glpi-v2.md`. +- Dúvida sobre um bug específico já corrigido → notas datadas em + `docs/superpowers/notes/`, mais recente primeiro.