From 4534099b508b72bfa334a0f36e17ae21dedec3fc Mon Sep 17 00:00:00 2001 From: Gustavo Date: Mon, 24 Aug 2026 20:09:52 -0300 Subject: [PATCH] docs: add corrective-cycle final report (Run A/Run B, real execution data) Live DEV sync completed (all 11 changed nodes pushed, including the transitive-dependency fix), then two real executions against production GLPI in shadow mode: - Run A (execution 14259): 45 candidates, 30 real AI calls (0 errors), 15 correctly routed to AI_FALLBACK instead of fake ACTIONABLE_NOW, 3 critical overrides. ACTIONABLE_NOW/UNASSIGNED_ACTIONABLE still 0 -- reported honestly, not forced. - Run B (execution 14263, ~1 min later, no data changes): 0 AI calls, 45/45 cache hits, cached AI_FALLBACK items correctly stayed fallback (proves the cache is_fallback bug fix), Top10 correctly shrank 10->5 as WAITING_VENDOR/WAITING_REQUESTER items hit cooldown. Full reincident-ticket table (7 tickets) with real evidence, real before/after HTML-normalization examples (tickets #37389, #36530), shadow diff vs legacy, webhook-debug-removed confirmation, production- untouched confirmation. Verdict: CONTINUAR SHADOW MODE. Workflow y6JFEnp7Z2IJEUe2 ends this cycle at active:false, 33 nodes, temp webhook removed. Co-Authored-By: Claude Sonnet 5 --- .../2026-08-24-relatorio-ciclo-correcao.md | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 docs/superpowers/notes/2026-08-24-relatorio-ciclo-correcao.md diff --git a/docs/superpowers/notes/2026-08-24-relatorio-ciclo-correcao.md b/docs/superpowers/notes/2026-08-24-relatorio-ciclo-correcao.md new file mode 100644 index 0000000..d909ae4 --- /dev/null +++ b/docs/superpowers/notes/2026-08-24-relatorio-ciclo-correcao.md @@ -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.