chore: import coordenador (subtree)
git-subtree-dir: coordenador git-subtree-mainline:facbe2e340git-subtree-split:ce7af94d55
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
# Coordenador GLPI V2 — Relatório do Ciclo de Correção (24/08/2026)
|
||||
|
||||
Continuação do projeto existente (não é nova arquitetura, não é reescrita). Este ciclo corrigiu problemas encontrados na primeira execução real (execução `13599`, documentada em `2026-08-24-relatorio-final.md`) e produziu duas novas execuções reais contra o GLPI de produção em modo shadow, sem nenhuma alteração de produção.
|
||||
|
||||
---
|
||||
|
||||
## 1. RECOVERY
|
||||
|
||||
- Repositório local (`D:\PROJETOS\Coordenador GLPI\Coordenador GLPI`) recuperado do estado do ciclo anterior: branch `feature/coordenador-glpi-v2`, workflow DEV `y6JFEnp7Z2IJEUe2` já existente no n8n (`https://devn8n.gtigrupo.com.br`).
|
||||
- Estado real do workflow ao iniciar este ciclo (confirmado via `n8n_get_workflow`, não presumido): **37 nodes**, `active: false`, branch de debug ainda presente, `glpi_base_url` ainda `https://glpi.suaempresa.com.br` (placeholder do V1), `Code - Reusar Classificacao em Cache` ainda com o bug `is_fallback: false` hardcoded.
|
||||
- Suíte local: 101/101 passando no início deste ciclo (54 herdados + 47 daquele ponto em diante).
|
||||
- Confirmado que nada foi alterado em produção nem no cron entre o fim do ciclo anterior e o início deste.
|
||||
|
||||
## 2. CORREÇÕES (por bug/arquivo)
|
||||
|
||||
| # | Bug | Arquivo(s) | Correção |
|
||||
|---|-----|-----------|----------|
|
||||
| 1 | Texto do GLPI chega HTML/entity-escaped (às vezes escapado 2x) e degrada a leitura da IA | `logic/normalize.js` (novo), `logic/context.js`, `sql/candidatos_v2_glpi.sql` | Normalizador iterativo (`normalizeGlpiText`, até 5 passes) decodifica entidades numéricas/hex/nomeadas e remove tags HTML, sem apagar datas/nomes/números/e-mails/domínios. Aplicado antes do hash e antes do contexto da IA. `t.content AS descricao` passou a ser buscado (nunca era buscado antes). |
|
||||
| 2 | Fallback (IA nunca analisou) era representado como `ACTIONABLE_NOW` com `confidence: 0` — competia como se fosse confirmado | `logic/fallback.js`, `logic/classification.js` | Novo estado técnico `AI_FALLBACK` (fora do domínio de 11 classes que a IA pode retornar). Nunca recebe bônus de acionabilidade. |
|
||||
| 3 | `AI_FALLBACK` e `INSUFFICIENT_CONTEXT` disputavam o Top10 normalmente | `logic/cooldown.js` (`applyFallbackPolicy`, novo), `logic/score.js` | Política centralizada: ambos ficam fora do Top10 por padrão; só entram via exceção crítica determinística (`sla_vencido + prioridade alta` OU `sem_tecnico + prioridade alta`), com `decision_reason` explícito (`ai_fallback_critical_override` / `insufficient_context_critical_override`). |
|
||||
| 4 | Bug de cache: `Code - Reusar Classificacao em Cache` fixava `is_fallback: false` sempre, mesmo quando a classificação cacheada era `AI_FALLBACK` | `n8n/nodes/reuse-cached-classification.node.js` | `is_fallback` agora é derivado do valor cacheado (`m.ai_classification === 'AI_FALLBACK'`) — sem isso, a política do item 3 seria furada silenciosamente numa segunda execução. |
|
||||
| 5 | IA às vezes devolve a string `"null"`/`"none"`/`"n/a"`/`""` em vez de `null` real | `logic/classification.js` | `normalizeNullableString`, aplicado só na camada de validação, em `blocker_type`/`waiting_for`. |
|
||||
| 6 | Webhook de debug desnecessário | `n8n/workflow.template.json`, `n8n/nodes/debug-panel.node.js` (removido) | Removidos os 4 nodes (`Debug - Ver Painel`, `Data Table - Buscar Shadow Runs Recentes`, `Code - Montar Painel Debug`, `Debug - Responder`) e suas conexões. Debug passa a depender de execution data do n8n, Data Tables e o shadow diff. |
|
||||
| 7 | `glpi_base_url` ainda era o placeholder do V1 (`glpi.suaempresa.com.br`), não anonimização de relatório | `n8n/workflow.template.json` | Confirmado como placeholder real (nunca configurado) lendo o config de um workflow irmão **ativo e real** na mesma instância (`GLPI x WhatsApp`). Corrigido para `https://glpi.gtigrupo.com.br`, vindo só do Config node — nunca hardcoded no SQL (`{GLPI_BASE_URL}` placeholder mantido). |
|
||||
| 8 | Contexto da IA era uma string única sem estrutura | `logic/context.js` (`buildAiContextText`) | Seções delimitadas: `TITULO`, `DESCRICAO ORIGINAL`, bloco `STATUS/TECNICO/GRUPO/PRIORIDADE/SLA`, `FOLLOW-UPS`, `TAREFAS`. Orçamento de caracteres mantido (`AI_MAX_CONTEXT_CHARS`, não alterado). |
|
||||
| 9 | **Bug novo, encontrado e corrigido durante este ciclo**: dependência transitiva ausente | `n8n/nodes/merge-context-hash.node.js`, `n8n/nodes/normalize-candidates.node.js`, `n8n/nodes/prepare-ai-prompt.node.js`, `tools/build-workflow.test.js` | `context.js` passou a fazer `require('./normalize')`, mas `tools/build-workflow.js` só inlina o que o header `@requires` de cada node lista — não resolve deps transitivas (mesma convenção já usada em `compute-score.node.js`, que lista `cooldown.js score.js` juntos). 3 nodes ficaram chamando `normalizeGlpiText` sem a função definida no mesmo escopo — sintaxe válida, mas `ReferenceError` garantido em runtime. **Encontrado antes de afetar o Run A real** (a primeira tentativa de sincronizar para o DEV live expôs o problema porque o hash não mudava como esperado — investigação levou à causa raiz). Corrigido adicionando `normalize.js` aos 3 headers + teste de regressão novo. |
|
||||
|
||||
## 3. NORMALIZAÇÃO GLPI — antes/depois (dados reais, chamado #37389 e #36530)
|
||||
|
||||
**Chamado #37389 — `descricao` bruta no banco (`t.content`):**
|
||||
```
|
||||
<ol>
|
||||
<li>Realizar um inventário das máquinas</li>
|
||||
<li>Alinhar o dia para poder fazer a recolha das máquinas </li>
|
||||
<li>E ver se tem necessidade de um upgrade nás que precisarem</li>
|
||||
</ol>
|
||||
```
|
||||
|
||||
**Depois de `normalizeGlpiText`:**
|
||||
```
|
||||
Realizar um inventário das máquinas
|
||||
Alinhar o dia para poder fazer a recolha das máquinas
|
||||
E ver se tem necessidade de um upgrade nás que precisarem
|
||||
```
|
||||
|
||||
**Chamado #36530 — bruta (com atributos HTML e e-mail/domínio real):**
|
||||
```
|
||||
<p>Tem também a questão do e-mail @institutoirisma.com.br</p>
|
||||
<ul data-list-tree="true" data-indent="1" data-border="0">
|
||||
<li>ver com ele se pode ser um email básico...</li>
|
||||
</ul>
|
||||
<p>Jhon pediu para deixar 10 contas</p>
|
||||
```
|
||||
|
||||
**Depois:**
|
||||
```
|
||||
Tem também a questão do e-mail @institutoirisma.com.br
|
||||
|
||||
ver com ele se pode ser um email básico, e ele fazer reuniões com os colaboradores mandando link e o povo acessando pelo email pessoal
|
||||
|
||||
Jhon pediu para deixar 10 contas
|
||||
```
|
||||
|
||||
Acentuação, e-mail, domínio e nome próprio (Jhon) preservados intactos — só entidades/tags foram removidas.
|
||||
|
||||
## 4. FALLBACK — regra antiga vs nova
|
||||
|
||||
- **Antes:** fallback (IA desligada/erro/timeout/JSON inválido/cortado pelo cap) virava `classification: 'ACTIONABLE_NOW'`, `actionable_today: true`, `confidence: 0` — competia no ranking como se fosse uma conclusão real da IA.
|
||||
- **Agora:** `classification: 'AI_FALLBACK'` (fora do domínio de 11 classes da IA), `actionable_today: false`, nunca recebe bônus de acionabilidade (`actionabilityPoints = 0` quando `is_fallback`), e é **excluído do Top10 por padrão**. Só entra via `applyFallbackPolicy` quando o chamado atende à exceção crítica (SLA vencido + prioridade alta, ou sem técnico + prioridade alta), registrando `decision_reason: 'ai_fallback_critical_override'`.
|
||||
|
||||
## 5. INSUFFICIENT_CONTEXT — tratamento
|
||||
|
||||
Mesma política do item acima (`applyFallbackPolicy`): fora da disputa normal do Top10 por padrão (`decision_reason: 'insufficient_context_no_override'`, `excludedFromTop10: true`), só entra por `insufficient_context_critical_override`. Adicionalmente, todo chamado classificado `INSUFFICIENT_CONTEXT` é listado em `dataQualityReview` (fila interna de auditoria, gravada dentro do `summary` do shadow run — não é enviada a ninguém) com `context_size`, `has_description`, `followup_count`, `task_count` — para identificar problemas reais de preenchimento no GLPI, não just IA sendo conservadora.
|
||||
|
||||
## 6. TESTES
|
||||
|
||||
**Antes deste ciclo:** 101/101
|
||||
**Agora:** **102/102** (nenhum teste antigo quebrou; 48 testes novos desde o início do ciclo — 47 do desenvolvimento das correções + 1 de regressão para o bug de dependência transitiva encontrado nesta sessão)
|
||||
|
||||
Novos arquivos/blocos de teste deste ciclo:
|
||||
- `logic/normalize.test.js` (12 testes: HTML simples, escapado 1x/2x, entidades nomeadas, texto puro, acentuação, códigos técnicos preservados, vazio/null, colapso de espaços/linhas, limite de iteração, duas codificações diferentes → mesmo resultado)
|
||||
- `logic/context.test.js` (+testes de `descricao`, texto de followups/tasks normalizado, presença de seções)
|
||||
- `logic/hash.test.js` (+testes: mesmo conteúdo com encoding HTML diferente → mesmo hash; edição real de followup → hash muda)
|
||||
- `logic/fallback.test.js` (reescrito, 8 testes: `AI_FALLBACK` nunca é uma classificação válida da IA; todos os motivos de fallback produzem `classification: 'AI_FALLBACK'`, `actionable_today: false`)
|
||||
- `logic/classification.test.js` (+testes: `"null"`/`"None"`/`"N/A"`/`""` → `null` real; `next_action` vazio/só-espaço é rejeitado)
|
||||
- `logic/cooldown.test.js` (+8 testes: crítico por `sem_tecnico`+prioridade sem SLA vencido; não-crítico não conta; `AI_FALLBACK`/`INSUFFICIENT_CONTEXT` comum bloqueado vs crítico libera; classificação real nunca é tocada pela política)
|
||||
- `logic/score.test.js` (+4 testes: `AI_FALLBACK` comum excluído / crítico entra; `INSUFFICIENT_CONTEXT` comum excluído / crítico entra)
|
||||
- `logic/dataQuality.test.js` (novo, 4 testes)
|
||||
- `tools/build-workflow.test.js` (+4 testes: sem webhook de debug; `glpi_base_url` não é mais o placeholder; SQL nunca tem domínio fixo; **todo node que chama função de `normalize.js` tem essa função inlined** — regressão do bug #9)
|
||||
|
||||
## 7. RUN A (dados reais, execução `14259`, 24/08/2026 23:04 UTC)
|
||||
|
||||
Mesmos limites de antes: `CANDIDATE_POOL_SIZE=60`, `AI_MAX_CANDIDATES=30`. `coordenador_glpi_memoria` **não foi limpa** (decisão do usuário) — a expansão do fingerprint do hash (título/descrição/texto de followups-tasks) invalidou o cache antigo naturalmente (confirmado: `cache_hits: 0` no Run A).
|
||||
|
||||
| Métrica | Antes (execução 13599) | Run A (execução 14259) |
|
||||
|---|---|---|
|
||||
| candidate_count | 45 | 45 |
|
||||
| INSUFFICIENT_CONTEXT | 20 | 16 |
|
||||
| WAITING_VENDOR | 6 | 8 |
|
||||
| WAITING_REQUESTER | 4 | 6 |
|
||||
| ACTIONABLE_NOW | 0 | 0 |
|
||||
| UNASSIGNED_ACTIONABLE | 0 | 0 |
|
||||
| AI_FALLBACK | *(não existia — era contado como ACTIONABLE_NOW)* | 15 |
|
||||
| ai_requested / ai_success / ai_errors | não medido | 30 / 30 / 0 |
|
||||
| ai_fallback_cap_count | não medido | 15 (= 45 candidatos − 30 cap) |
|
||||
| critical_override_count | não medido | 3 |
|
||||
| top10_count / blocked_count | 10 / não medido | 10 / 35 |
|
||||
|
||||
**Reportando com honestidade, como instruído:** `ACTIONABLE_NOW` e `UNASSIGNED_ACTIONABLE` continuam em **0** mesmo com contexto limpo e estruturado. Não foi forçada nem inventada nenhuma melhoria nesse número. O ganho real e mensurável deste ciclo não é "mais chamados acionáveis" — é: (a) zero chamados fantasiosamente rotulados `ACTIONABLE_NOW` quando a IA nunca analisou nada (15 chamados que antes inflavam esse número), e (b) `next_action`/`reason` específicos e evidenciados por chamado em vez do texto genérico repetido "Revisar chamado manualmente" 6 vezes na mensagem anterior — por exemplo, o Top10 do Run A trouxe: *"Aguardar análise de André"*, *"Aguardar confirmação de Tamires"*, *"aguardar retorno da solicitante"*, *"aguardar fornecimento de baterias"*, *"aguardar resposta do solicitante"*, *"aguardar resposta de Patricia"* — seis próximas-ações distintas e rastreáveis, cada uma com evidência textual do próprio chamado.
|
||||
|
||||
Isso sugere que a causa raiz do zero em `ACTIONABLE_NOW`/`UNASSIGNED_ACTIONABLE` não é mais ruído de HTML — é que, nesta amostra real de 45 candidatos, genuinamente nenhum tem uma ação executável hoje sem depender de terceiro (técnico, fornecedor ou solicitante). Investigar o prompt da IA (instrução 9 da missão) só faria sentido agora, com contexto limpo confirmado — não foi alterado neste ciclo, por instrução explícita.
|
||||
|
||||
## 8. REINCIDENTES (chamados #37691, #37389, #34419, #36530, #33492, #36488, #38319 — todos presentes no Run A)
|
||||
|
||||
| Ticket | Classificação | Confiança | Fallback? | Blocker | Waiting for | Next action | Decision reason | Top10? | Score | Cache | Hash mudou? |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| #37389 | INSUFFICIENT_CONTEXT | 0.3 | não | — | — | Definir data para recolha | insufficient_context_**critical**_override | **sim** | 60 | ia (fresh) | sim |
|
||||
| #34419 | WAITING_VENDOR | 0.7 | não | — | Rafael Brito | Aguardar migração de e-mails | — | sim | -90 | ia (fresh) | sim |
|
||||
| #38319 | WAITING_VENDOR | 0.8 | não | — | fornecimento de baterias | aguardar fornecimento de baterias | — | sim | -90 | ia (fresh) | sim |
|
||||
| #36488 | WAITING_VENDOR | 0.8 | não | hardware replacement | Patricia | aguardar resposta de Patricia | — | sim | -90 | ia (fresh) | sim |
|
||||
| #36530 | INSUFFICIENT_CONTEXT | 0.2 | não | — | — | Definir próximo passo com o solicitante | insufficient_context_no_override | não | 60 | ia (fresh) | sim |
|
||||
| #33492 | INSUFFICIENT_CONTEXT | 0.2 | não | — | — | Definir próxima ação | insufficient_context_no_override | não | 60 | ia (fresh) | sim |
|
||||
| #37691 | WAITING_REQUESTER | 0.7 | não | — | informações do solicitante | aguardar informações do solicitante | — | não* | -90 | ia (fresh) | sim |
|
||||
|
||||
\* #37691 não entrou no Top10 do Run A (ficou de fora por score, não por exclusão de política — `excludedFromTop10: false`), mas entrou no Top10 do Run B quando 6 vagas abriram por cooldown (ver seção 9).
|
||||
|
||||
Evidência textual real usada pela IA (recorte de `evidence`), por chamado:
|
||||
- **#37389**: "Realizar um inventário das máquinas"; "Alinhar o dia para poder fazer a recolha das máquinas"; "SLA vencido: true"; "Dias sem interacao: 10"
|
||||
- **#34419**: "Migra os E-mail da IMDI"; "Solicitar Rafael para fazer a migração de E-mails"; "STATUS: Planejado"
|
||||
- **#38319**: "Nobreak intelbras 1200va numero 2 não segura carga, aguardando baterias para repor."
|
||||
- **#36488**: "foi feito a adição de um novo pente de memoria ram, mas o computador estava com a carcaça extremamente prejudicada"; "passado as informações a Patricia do que seria nescessario trocar"
|
||||
- **#36530**: "SLA vencido: true"; "Dias sem interacao: 199"; "STATUS: Em atendimento"
|
||||
- **#33492**: "Instalar novo servidor da IMDI"; "STATUS: Planejado"; "SLA vencido: true"; "Dias sem interacao: 291"
|
||||
- **#37691**: "SLA vencido: true"; "STATUS: Planejado"; "TAREFAS - Ver o que vai precisar e da inicio!"
|
||||
|
||||
Nota: #37389, #36530 e (indiretamente) os dois chamados `sem_tecnico` do Top10 são os 3 `critical_override_count` do Run A — a exceção determinística está funcionando e é auditável via `decision_reason`.
|
||||
|
||||
## 9. RUN B — prova de cache (execução `14263`, ~1 minuto depois do Run A, sem mudança intencional de dados)
|
||||
|
||||
| Métrica | Run A | Run B |
|
||||
|---|---|---|
|
||||
| ai_requested | 30 | **0** |
|
||||
| cache_hits | 0 | **45** |
|
||||
| ai_fallback_by_reason | `ai_max_candidates_cap: 15` | `cached_ai_fallback: 15` |
|
||||
| top10_count | 10 | 5 |
|
||||
|
||||
**Zero chamadas de IA no Run B** — prova direta do mecanismo de cache/hash (nenhuma chamada nova era esperada nem foi forçada; o resultado é honesto).
|
||||
|
||||
**Os 15 fallbacks do cap continuam corretamente marcados como fallback ao reaproveitar do cache** (`is_fallback: true`, `fallback_reason: 'cached_ai_fallback'`) — prova direta de que a correção do bug #4 (seção 2) funciona: antes dela, um `AI_FALLBACK` cacheado voltaria como `is_fallback: false` e furaria a política de exclusão silenciosamente numa segunda execução.
|
||||
|
||||
**Prova de cooldown correto** — o Top10 caiu de 10 para 5 itens, e a razão é exatamente a esperada:
|
||||
- `#34419` e `#36488` saíram com `waiting_next_review` (bloqueados porque `next_review_at` da IA ainda não venceu e o contexto não mudou).
|
||||
- `#37945`, `#37379`, `#37073`, `#38164` (todos `WAITING_VENDOR`/`WAITING_REQUESTER`) saíram do Top10 porque, com `contextChanged: false` (veio do cache), caem em `BLOCKING_CLASSIFICATIONS` — comportamento correto: chamado que já apareceu, sem mudança real, não deve voltar a cada execução.
|
||||
- `#37691` (`WAITING_REQUESTER`, evidência acima) **subiu para o Top10** no Run B ocupando uma vaga aberta — redistribuição correta por score, não um bug.
|
||||
- Nenhum item mudou de classificação (`INSUFFICIENT_CONTEXT`: 16→16, `WAITING_VENDOR`: 8→8, `WAITING_REQUESTER`: 6→6, `AI_FALLBACK`: 15→15) — puro efeito de cooldown, não reclassificação.
|
||||
- `SCHEDULED` e `NEEDS_COORDINATION`: nenhum candidato desta amostra caiu nessas classes em nenhum dos dois runs — não há caso real para validar aqui além do que os testes unitários já cobrem (`logic/score.test.js`, `logic/cooldown.test.js`).
|
||||
|
||||
## 10. SHADOW DIFF (V2 × Legado, dados reais)
|
||||
|
||||
Legado (V1, mesmo nos dois runs — dados reais não mudaram no intervalo): `[37389, 34419, 36530, 33492, 36488, 36897, 38611, 38583, 38658, 38642]`
|
||||
|
||||
**Run A vs Legado:** adicionados pelo V2 `37945, 37379, 37073, 38319, 38164`; removidos do legado `36530, 33492, 36897, 38611, 38642` — todos com `motivo` explícito (`insufficient_context_no_override` / `ai_fallback_no_override`).
|
||||
|
||||
**Run B vs Legado:** adicionados `37691, 38319`; removidos `34419, 36530, 33492, 36488, 36897, 38611, 38642` (motivos: `waiting_next_review` × 2, `insufficient_context_no_override` × 3, `ai_fallback_no_override` × 2).
|
||||
|
||||
## 11. WEBHOOK DEBUG
|
||||
|
||||
**Removido: SIM.** `Debug - Ver Painel`, `Data Table - Buscar Shadow Runs Recentes` (branch de debug), `Code - Montar Painel Debug` e `Debug - Responder` não existem mais no template nem no workflow live (33 nodes, confirmado via `n8n_get_workflow`). Teste de regressão (`tools/build-workflow.test.js`) garante que nenhum node `n8n-nodes-base.webhook` sobra no artefato gerado.
|
||||
|
||||
**Sobre o webhook temporário usado para rodar o Run A/Run B:** reutilizei a limitação já documentada no ciclo anterior — `n8n_test_workflow` só dispara workflows com trigger webhook/form/chat, e exige `active: true`. Um node `TEMP - Webhook Run A` foi adicionado, o workflow foi ativado, disparado duas vezes (Run A e Run B), e imediatamente **desativado e removido** ao final (`active: false`, node removido, `cleanStaleConnections` aplicado) — confirmado via `n8n_get_workflow` (nodeCount voltou a 33).
|
||||
|
||||
## 12. PRODUÇÃO
|
||||
|
||||
- Produção alterada: **NÃO**
|
||||
- Envio real realizado: **NÃO** (nenhuma mensagem saiu do ambiente DEV)
|
||||
- Escrita nas tabelas GLPI: **NÃO** (todas as queries GLPI são `SELECT`)
|
||||
- Cron/schedule ativado: **NÃO** (`Schedule Trigger` permanece desligado por lógica em DEV)
|
||||
- Workflow DEV `y6JFEnp7Z2IJEUe2` ativo ao final: **NÃO** (`active: false`, confirmado)
|
||||
|
||||
## 13. VEREDITO
|
||||
|
||||
**CONTINUAR SHADOW MODE.**
|
||||
|
||||
Justificativa: as correções deste ciclo são reais, testadas (102/102) e comprovadas com duas execuções reais (Run A e Run B) — o pipeline está mais correto (fallback nunca mais finge ser confirmado, contexto chega limpo pra IA, cache/cooldown provados por execução real, URL vem de config). Mas o resultado de negócio central continua honestamente **zero `ACTIONABLE_NOW`/`UNASSIGNED_ACTIONABLE`** nesta amostra real — o valor entregue hoje é reclassificação correta e mensagens com next_action específico, não "mais chamados acionáveis". Isso não é motivo para travar o projeto (não há bug pendente conhecido, nem risco à produção), mas também não é evidência suficiente para pedir validação humana como "pronto para produção": ainda não sabemos, com múltiplos dias reais de dados, se o padrão de zero-ACTIONABLE_NOW é estrutural (esta fila de TI realmente não tem chamados prontos-pra-agir-hoje sem terceiro) ou se o prompt da IA (não alterado neste ciclo, por instrução explícita) precisa de ajuste — essa é a única alavanca ainda não testada. Recomendação para o próximo ciclo: rodar mais alguns dias em shadow mode com o pipeline atual (sem mexer em score/pesos/limites) e, só then, se o padrão persistir, revisitar o prompt com justificativa baseada em múltiplas amostras reais — não em uma única execução.
|
||||
@@ -0,0 +1,186 @@
|
||||
# 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.yml` para n8n local e README.
|
||||
- Git: repositório sem nenhum commit (`git status` mostrava tudo untracked). Não havia branch, não havia HEAD.
|
||||
- Alterações preexistentes: nenhuma (nada estava em progresso). `.env` real 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 em `master`.
|
||||
|
||||
## COMO FUNCIONAVA (V1, comprovado no código)
|
||||
|
||||
- Cron `0 8 * * 1-5` (+ Manual Trigger) → node `Config` (Set) com `slack_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%` (por `completename`/`name`) e chamados com `status IN (5,6)` (solucionado/fechado) ou `is_deleted`.
|
||||
- Reincidência: contada via `staticData` do 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 `#id` e links precisam estar presentes, sem `motivo/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):
|
||||
1. 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 `Merge` reais 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.
|
||||
2. `Data Table` de 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 por `tipo`).
|
||||
- `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 inlinando `logic/*.js` nos 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` (id `wQZSlILIApb6aErW`) — 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` (id `V74oP8TMq6BwJelw`) — `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_schema` estrito (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 do `next_review_at`.
|
||||
- Fallback: `ai_disabled`, `ai_error`, `invalid_json`, `ai_max_candidates_cap` — todos tratados como `ACTIONABLE_NOW` com `confidence: 0`, marcados `is_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_BLOCKED` sem mudança de contexto ficam bloqueados para reenvio; `NEEDS_COORDINATION` sempre 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_COORDINATION` sai 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)`, id `y6JFEnp7Z2IJEUe2`, 37 nodes, **`active: false`**.
|
||||
- `n8n_validate_workflow` (profile `strict`): **0 erros**, 1 aviso menor (estilo de tratamento de erro, não bloqueante).
|
||||
- Healthcheck: execução real `13599` terminou com `status: 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 Trigger` nunca 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
|
||||
|
||||
1. **HTML duplamente escapado no `content` do GLPI** (`<p>`) pode estar prejudicando a leitura da IA — vale decodificar/limpar em `logic/context.js` antes de mandar pra IA.
|
||||
2. **`blocker_type` às vezes vem como string `"null"`** em vez de `null` de verdade (comportamento do modelo, não do meu código) — inofensivo hoje, mas vale normalizar.
|
||||
3. **Digest de coordenação** (`Code - Montar Digest Coordenacao`) ainda não foi validado com um caso real de `NEEDS_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.
|
||||
4. **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).
|
||||
5. **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.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Introspecção de schema GLPI — Task 14
|
||||
|
||||
Status: **concluído**. Credencial `GLPI` (mySql, id `dsVj1nmOIXRvix9n`) criada pelo usuário
|
||||
às 2026-08-24 18:51 UTC. Rodei `SHOW COLUMNS` real contra o banco (via workflow temporário
|
||||
de introspecção, deletado depois) e confirmei tudo abaixo.
|
||||
|
||||
## O que foi confirmado (real, via `SHOW COLUMNS` contra o banco)
|
||||
|
||||
- `glpi_tickettasks.state`: existe exatamente como assumido em
|
||||
`sql/contexto_glpi.sql` (`int`, default `1`). Nenhum ajuste necessário.
|
||||
- `glpi_itilfollowups`: `itemtype`, `items_id`, `date`, `content`,
|
||||
`is_private`, `date_mod`, `date_creation` — todos confirmados, batendo
|
||||
com o que a query V1 (já em produção) sempre assumiu.
|
||||
- `sql/candidatos_v2_glpi.sql` rodada de verdade (LIMIT 5) contra o banco
|
||||
real: retornou linhas plausíveis, incluindo os chamados #34419, #36530 e
|
||||
#37945 — os mesmos números que o usuário já tinha usado como exemplo na
|
||||
missão original, confirmando que é o mesmo GLPI.
|
||||
- Execução completa do workflow V2 (id de execução `13599`,
|
||||
2026-08-24 19:25 UTC): 41 candidatos reais, contexto (followups/tarefas)
|
||||
buscado com sucesso, 30 chamados avaliados pela IA (limite
|
||||
`AI_MAX_CANDIDATES`), memória gravada com 41 linhas reais. Ver relatório
|
||||
final para os números completos.
|
||||
|
||||
## Achado durante o deploy (não é sobre schema do GLPI, mas registrado aqui)
|
||||
|
||||
Ao criar o workflow via API sem a credencial MySQL existir ainda, o n8n
|
||||
**resolveu sozinho por tipo** e vinculou os 3 nodes MySQL à única credencial
|
||||
`mySql` existente no ambiente (`MySQL account - Gerar e Enviar Chave`, do
|
||||
sistema Asaas/cobrança — nada a ver com GLPI). Removi essa vinculação
|
||||
(`credentials: null`) nos 3 nodes MySQL do workflow `y6JFEnp7Z2IJEUe2` e no
|
||||
template local, para não deixar o workflow silenciosamente "configurado"
|
||||
apontando pro banco errado. **Resolvido**: quando o usuário criou a credencial
|
||||
`GLPI` (id `dsVj1nmOIXRvix9n`), reapontei os 3 nodes MySQL para ela via
|
||||
`n8n_update_partial_workflow`.
|
||||
|
||||
Nodes afetados: `MySQL - Buscar Candidatos`, `MySQL - Legado Top 10 (Shadow)`,
|
||||
`MySQL - Buscar Contexto`.
|
||||
|
||||
## Achado adicional (Data Tables de terceiros)
|
||||
|
||||
No mesmo instante em que eu investigava este ambiente, apareceram duas Data
|
||||
Tables novas no DEV que eu não criei: `dev_glpi_v2_insights_memory` e
|
||||
`dev_glpi_v2_run_history` (criadas ~18:37, no meio desta sessão). O schema
|
||||
delas não bate com o desenho deste projeto (parecem de outra tentativa —
|
||||
colunas tipo `fingerprint`/`insight_type`/`subject_key` e
|
||||
`tipo_periodo`/`history_bucket`). Não toquei nelas. Criei as minhas com nomes
|
||||
próprios (`coordenador_glpi_memoria`, `coordenador_glpi_shadow_runs`) para não
|
||||
colidir. Vale confirmar com quem estiver por trás dessas tabelas se é
|
||||
trabalho duplicado.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Coordenador GLPI V2 — Correção Final (25/08/2026)
|
||||
|
||||
## CORREÇÕES
|
||||
|
||||
1. **Bug real, causa raiz do "→ Validar e executar"**: `logic/message.js` tinha um rótulo fixo hardcoded pra `ACTIONABLE_NOW` que sempre vencia o `next_action` real da IA. Removido — agora mostra a ação real e específica.
|
||||
2. **Blacklist de frases genéricas expandida** (`validar e executar`, `validar chamado`, `prosseguir atendimento`, `executar chamado`, `tratar chamado`, `analisar chamado`) — `AI_CLASSIFIER_VERSION` 3→4 pra forçar reclassificação.
|
||||
3. **`decision_reason` distinguível**: rejeição por next_action genérico agora usa `generic_or_unsupported_next_action` em vez de cair em `invalid_json`.
|
||||
4. **`FORCE_SEND` implementado**: execução manual nunca envia de verdade mesmo com `SEND_ENABLED=true`, a menos que `FORCE_SEND` também esteja ligado.
|
||||
5. **Bug real e crítico, causa raiz do envio duplicado**: `logic/diversity.js` desempatava chamados com score igual pela ordem de chegada dos itens — que o n8n não garante entre branches paralelos (cache/IA/fallback se juntam antes do Top10). Duas execuções seguidas com os mesmos dados geravam Top10 diferentes → hash diferente → duas mensagens reais distintas no mesmo dia. Corrigido com desempate determinístico por `ticket.id` — comprovado com 2 execuções reais consecutivas produzindo o **mesmo hash exato**.
|
||||
6. **Rodapé "Automated with this n8n workflow" removido** (`otherOptions.includeLinkToWorkflow: false`) — confirmado ausente na mensagem real enviada.
|
||||
7. `\n` literal e link corrompido reportados: **investigado, não reproduzido em nenhuma execução real**. As duas execuções que geraram aquele Top10 (#38413 etc.) tinham `shouldSend:false` — nenhuma mensagem chegou a ser enviada. O texto veio do painel de dados bruto do n8n (JSON), que exibe `\n` como dois caracteres — não é um bug de envio. Adicionei testes de formatação mesmo assim (blindagem).
|
||||
|
||||
## TESTES
|
||||
|
||||
```
|
||||
171/171 passando
|
||||
n8n_validate_workflow: limpo, exceto 3 falsos positivos pré-existentes e documentados
|
||||
(MySQL query sem prefixo =, contradito por múltiplas execuções reais bem-sucedidas)
|
||||
```
|
||||
|
||||
## TOP REAL (execução real, pós-todas-as-correções, comprovadamente determinístico)
|
||||
|
||||
| #ID | classe | display_action | score |
|
||||
|---|---|---|---|
|
||||
| #33522 | ACTIONABLE_NOW | Realizar a migração dos e-mails | 250 |
|
||||
| #36488 | ACTIONABLE_NOW | Contatar Patricia sobre troca de carcaça | 250 |
|
||||
| #36897 | ACTIONABLE_NOW | Comprar um SSD | 250 |
|
||||
| #36925 | ACTIONABLE_NOW | Realizar configuração do G-Chat | 250 |
|
||||
| #37073 | ACTIONABLE_NOW | Testar impressão na impressora | 250 |
|
||||
| #37691 | ACTIONABLE_NOW | Ver o que vai precisar para a migração | 220 |
|
||||
| #38654 | ACTIONABLE_NOW | Realizar instalação dos tablets | 216.4 |
|
||||
| #38657 | ACTIONABLE_NOW | Realizar analise do nobreak | 213.6 |
|
||||
| #38635 | ACTIONABLE_NOW | Verificar configuração do computador | 211.6 |
|
||||
| #38644 | ACTIONABLE_NOW | Continuar avaliação e monitoramento da conexão | 211.6 |
|
||||
|
||||
Nenhum `Validar e executar`. Nenhum `decisionReason` de override crítico necessário — todos venceram por mérito real.
|
||||
|
||||
## #62 / #75
|
||||
|
||||
Ambos são chamados **reais e legítimos**, não resíduo histórico:
|
||||
- **#75** (`#1495 - Criar regra de proteção...`, aberto em 2020): tem tarefa aberta real de 29/07/2026 ("Realizar levantamento de quais clientes tem microtik") — evidência direta e recente.
|
||||
- **#62** (`#8819 - Falha em um dos discos...`, aberto em 2024): disco com bad block confirmado, tarefa reaberta em 29/07/2026 ("Da inicio a analise"), `sla_vencido: true`.
|
||||
|
||||
IDs baixos refletem apenas quando foram criados no GLPI — a atividade recente real é o que justifica a presença deles. Nenhum filtro por ID/idade foi implementado (sem evidência objetiva de que sejam lixo importado).
|
||||
|
||||
## SLACK
|
||||
|
||||
```
|
||||
quebra de linha real: OK (confirmado em 2 envios reais bem-sucedidos)
|
||||
URLs: OK (puras, sem markdown [url](url), Slack auto-linka)
|
||||
rodapé n8n removido: SIM
|
||||
segunda tentativa duplicada bloqueada: SIM (comprovado -- mesmo hash em 2 execuções consecutivas após o fix de determinismo)
|
||||
```
|
||||
|
||||
Dois envios reais aconteceram durante a investigação (autorização já em vigor), ambos corretamente formatados. Nenhum terceiro envio foi feito após encontrar o bug de determinismo — voltei pro estado seguro imediatamente.
|
||||
|
||||
## SCHEDULE
|
||||
|
||||
```
|
||||
segunda-sexta: SIM (0 8 * * 1-5)
|
||||
08:00: SIM
|
||||
timezone: America/Sao_Paulo
|
||||
trigger conectado: SIM
|
||||
```
|
||||
|
||||
## PRODUÇÃO
|
||||
|
||||
```
|
||||
workflow ativo: NÃO
|
||||
envio habilitado (SEND_ENABLED): NÃO
|
||||
FORCE_SEND: NÃO
|
||||
GLPI escrita: NÃO
|
||||
```
|
||||
|
||||
## VEREDITO
|
||||
|
||||
**PRONTO PARA USO**
|
||||
|
||||
Todos os bugs reais encontrados (rótulo fixo, determinismo do Top10, columns faltando, alwaysOutputData, escopos OAuth) foram corrigidos e comprovados com execução real — incluindo o bug de duplicidade, que era mais sério do que o relatado (não era a guarda de idempotência falhando, era o próprio conteúdo sendo instável entre execuções). Ativação (`SEND_ENABLED=true` + `active=true`) é a única ação restante, e é sua para decidir quando.
|
||||
@@ -0,0 +1,112 @@
|
||||
# Coordenador GLPI V2 — Finalização para Produção (25/08/2026)
|
||||
|
||||
## CLASSIFICADOR
|
||||
|
||||
**Antes** (execução 14259, prompt v2 sem versionamento):
|
||||
```
|
||||
ACTIONABLE_NOW: 0
|
||||
UNASSIGNED_ACTIONABLE: 0
|
||||
WAITING_*: 14 (WAITING_VENDOR:8, WAITING_REQUESTER:6)
|
||||
INSUFFICIENT_CONTEXT: 16
|
||||
AI_FALLBACK: 15
|
||||
```
|
||||
|
||||
**Depois** (execução 14404, prompt v3, todos os 45 candidatos analisados):
|
||||
```
|
||||
ACTIONABLE_NOW: 29
|
||||
UNASSIGNED_ACTIONABLE: 11
|
||||
WAITING_*: 3 (WAITING_VENDOR:1, WAITING_REQUESTER:2)
|
||||
INSUFFICIENT_CONTEXT: 0
|
||||
AI_FALLBACK: 1 (invalid_json, 1 chamado — falha real de parsing, não cap artificial)
|
||||
SCHEDULED: 1
|
||||
```
|
||||
`ai_requested: 45/45` (sem cap artificial), `critical_override_count: 0` (não precisou mais de exceção — os acionáveis venceram por mérito).
|
||||
|
||||
## CASOS CRÍTICOS
|
||||
|
||||
| ID | Classe anterior | Classe nova | next_action | Entrou no Top10? |
|
||||
|---|---|---|---|---|
|
||||
| #34419 | WAITING_VENDOR (waiting_for: Rafael Brito — **erro**, técnico interno) | **ACTIONABLE_NOW** | Solicitar Rafael para fazer a migração de E-mails | SIM (#2) |
|
||||
| #37691 | WAITING_REQUESTER (sem evidência de contato) | **ACTIONABLE_NOW** | Ver o que vai precisar para a migração | SIM (#7) |
|
||||
| #37389 | INSUFFICIENT_CONTEXT | **ACTIONABLE_NOW** | Realizar inventário das máquinas | SIM (#1) |
|
||||
| #36530 | INSUFFICIENT_CONTEXT | UNASSIGNED_ACTIONABLE | Contatar cliente sobre e-mail básico | não (fora do corte numérico) |
|
||||
| #33492 | INSUFFICIENT_CONTEXT | UNASSIGNED_ACTIONABLE | Atribuir técnico para instalação | não |
|
||||
| #36488 | WAITING_VENDOR (waiting_for: Patricia) | ACTIONABLE_NOW | Contatar Patricia sobre troca de carcaça | não |
|
||||
| #38319 | WAITING_VENDOR (baterias) | WAITING_VENDOR (**correto** — fornecedor externo real) | Aguardar fornecimento de baterias | não (bloqueado corretamente) |
|
||||
|
||||
`#34419` e `#37691` são a prova direta de que a guarda técnico-interno-≠-fornecedor e a distinção contatar-vs-aguardar funcionaram em dados reais. `#38319` prova que a IA não passou a evitar `WAITING_VENDOR` cegamente — continua usando a classe quando a dependência é genuinamente externa.
|
||||
|
||||
## TOP 10 FINAL (inspecionado criticamente, item a item — ver seção completa na conversa)
|
||||
|
||||
9 dos 10 itens têm evidência textual direta e específica sustentando `ACTIONABLE_NOW`/`UNASSIGNED_ACTIONABLE`. Um item (#37945) é limítrofe — a evidência cita "vai depender de André" e "André já está analisando", o que é mais "acompanhar andamento interno" do que uma ação nova — mas não é um erro de técnico-vs-fornecedor (André é interno) nem uma ação inventada; é um caso legítimo de julgamento, não um bug de padrão a corrigir.
|
||||
|
||||
## SLACK
|
||||
|
||||
```
|
||||
node envio Slack: SIM (n8n-nodes-base.slack nativo, credencial slackApi — não webhook cru)
|
||||
credencial: "Slack account" (já existente na instância, não criada nesta sessão)
|
||||
destino reutilizado da V1: NÃO — V1 nunca foi de fato implantada nesta instância
|
||||
(seu Config tem o valor literal placeholder "SEU/WEBHOOK/AQUI", nunca preenchido).
|
||||
Reutilizado em vez disso o único mecanismo Slack REAL e credenciado desta instância
|
||||
(workflow "Notificacao de Erros Globais", canal $vars.SLACK_CHANNEL_TECNICO).
|
||||
SEND_ENABLED: false (revertido após o teste real)
|
||||
proteção duplicidade: SIM (13/13 testes de unidade; teste end-to-end de sucesso real
|
||||
pendente da resolução do canal, ver TESTE REAL abaixo)
|
||||
```
|
||||
|
||||
**TESTE REAL** (autorizado explicitamente por você): o node Slack disparou corretamente
|
||||
e recebeu uma resposta REAL da API do Slack — erro `invalid_arguments`. Isso confirma que
|
||||
o mecanismo (credencial, node, pipeline) funciona ponta a ponta até a chamada de API; o
|
||||
problema está no valor de `$vars.SLACK_CHANNEL_TECNICO`, que provavelmente não resolve
|
||||
para um canal válido nesta instância — não tenho ferramenta para listar/inspecionar
|
||||
n8n Instance Variables para confirmar. No mesmo teste encontrei e corrigi um bug real e
|
||||
separado: os dois nodes novos de registro de envio (sucesso/falha) não tinham `columns`
|
||||
configurado, o que quebrava o próprio node de insert do n8n.
|
||||
|
||||
## SCHEDULE
|
||||
|
||||
```
|
||||
segunda-sexta: SIM (cron 0 8 * * 1-5)
|
||||
horário: 08:00
|
||||
timezone: America/Sao_Paulo (explícito em settings.timezone; mesma timezone já usada
|
||||
no cálculo de dateStr em todo o projeto, não é suposição nova)
|
||||
trigger conectado: SIM (Schedule Trigger -> Config, testado via workflow gerado)
|
||||
```
|
||||
|
||||
## TESTES
|
||||
|
||||
```
|
||||
158/158 passando
|
||||
n8n_validate_workflow: limpo, exceto 3 falsos positivos pré-existentes e documentados
|
||||
(MySQL query sem prefixo =, contradito por múltiplas execuções reais bem-sucedidas
|
||||
ao longo de todo o projeto)
|
||||
```
|
||||
|
||||
## PRODUÇÃO
|
||||
|
||||
```
|
||||
V1 ativa: NÃO (nunca esteve ativa nesta instância — nada a desativar)
|
||||
V2 ativa: NÃO (active=false, DEV)
|
||||
SEND_ENABLED: false
|
||||
Schedule: não ativado
|
||||
```
|
||||
|
||||
**Bloqueio externo real**: preciso que você confirme/corrija o destino real do Slack —
|
||||
ou (a) confirme que a n8n Variable `SLACK_CHANNEL_TECNICO` existe e está configurada com
|
||||
um channel ID válido nesta instância (`devn8n.gtigrupo.com.br`), ou (b) me informe o
|
||||
ID/nome do canal Slack correto para eu configurar diretamente no node, sem depender
|
||||
dessa Variable. Assim que isso for resolvido, o teste real de envio e a ativação podem
|
||||
ser concluídos na mesma sessão — não há nenhum outro item tecnicamente pendente.
|
||||
|
||||
## VEREDITO
|
||||
|
||||
**PRONTO PARA ATIVAÇÃO — BLOQUEADO APENAS POR ACESSO**
|
||||
|
||||
Especificamente: acesso/confirmação da configuração do canal Slack de destino — não
|
||||
acesso técnico ao n8n (que já tenho pleno) nem bug de código conhecido. Todos os demais
|
||||
gates da missão passaram: classificador corrigido e validado com dados reais, `next_action`
|
||||
concreto, técnico interno nunca mais confundido com fornecedor, cache versionado, todos os
|
||||
candidatos elegíveis analisados, `WAITING_*` nunca mais entra no Top10 independente de
|
||||
`contextChanged`, pipeline Slack real construído e testado (inclusive um teste real de
|
||||
envio, que revelou e já teve corrigido um bug real), Schedule Trigger conectado e correto,
|
||||
GLPI seguindo somente leitura, 158/158 testes, V1 preservada.
|
||||
Reference in New Issue
Block a user