fix(v2): three real bugs found by an actual end-to-end run against production GLPI data
Ran the deployed workflow for real (temporary webhook trigger, briefly
activated, immediately deactivated and removed after each attempt) against
the real GLPI MySQL database and a real OpenAI credential. Found and fixed:
1. $env access is blocked on this n8n instance (N8N_BLOCK_ENV_ACCESS_IN_NODE).
parseConfig($env) threw "access to env vars denied" on the very first
Code node. Config now comes from the "Config - Coordenador GLPI v2" Set
node instead (same pattern the V1 workflow already used) -- expanded
that node with all 13 tunable parameters, and pointed the 3 affected
node drivers (build-sql-candidatos, decide-needs-ai, select-top10) at
it instead of $env.
2. Data Table "upsert" operation requires both `columns` (values) and a
separate `filters` (match condition) -- I had only set columns with
matchingColumns, which isn't enough; n8n refused to activate the
workflow ("Missing or invalid required parameters: filters"). Added
filters.conditions matching on ticket_id to both memory-writing Data
Table nodes.
3. Real architecture bug: "Data Table - Buscar Memoria" sat inline in the
41-candidate chain (Mesclar Contexto -> Buscar Memoria -> Decidir
Necessidade de IA). A bulk "get all rows" Data Table node run with 41
input items produced 41 identical fetches and its OWN output (the empty
memory table on a first run) replaced the 41 candidates entirely --
"Decidir Necessidade de IA" received 0 items and the whole main branch
silently skipped. Moved the memory fetch to a parallel branch off
Config (fires once) and added a real Merge node ("Merge - Aguardar
Memoria") to guarantee both the candidates and the memory rows have
executed before "Decidir Necessidade de IA" runs -- same synchronization
pattern already used for context and for the shadow comparison.
Also fixed a cosmetic bug found in the real message output: buildTopMessage
produced a quadruple newline between the header and the first ticket block
([header, '', ...blocks].join('\n\n') double-counts the blank line already
inserted by the '\n\n' join). Added a regression test.
Verified with a real execution (id 13599, 24/08/2026 19:25 UTC) against the
live GLPI database: 41 real candidates, 30 sent to OpenAI (capped), 11
fallback-capped, memory table populated with 41 real rows including
genuine WAITING_VENDOR/WAITING_REQUESTER classifications with real
blocker_type/waiting_for fields extracted from real ticket task content,
and a real shadow_runs row comparing Legacy x V2 (4 tickets removed by V2:
2 for INSUFFICIENT_CONTEXT, 1 WAITING_REQUESTER, 1 WAITING_VENDOR; 4 added).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
af48ea69d8
commit
ea105d525c
+1
-1
@@ -41,7 +41,7 @@ function buildTopMessage(selected, dateStr, config) {
|
||||
];
|
||||
return linhasDoBloco.join('\n');
|
||||
});
|
||||
const partesDaMensagem = [header, '', ...blocks];
|
||||
const partesDaMensagem = [header, ...blocks];
|
||||
return partesDaMensagem.join('\n\n');
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@ test('digest de coordenacao vazio retorna null (nao manda lista igual todo dia)'
|
||||
assert.equal(buildCoordinationDigest([], '24/08'), null);
|
||||
});
|
||||
|
||||
test('mensagem nao tem linhas em branco duplicadas entre cabecalho e blocos', () => {
|
||||
const msg = buildTopMessage([selectedItem({}), selectedItem({ ticket: { ...selectedItem({}).ticket, id: 38612 } })], '24/08', {});
|
||||
assert.ok(!msg.includes('\n\n\n'), 'nao deveria haver 3+ quebras de linha seguidas');
|
||||
});
|
||||
|
||||
test('digest de coordenacao lista apenas os itens passados (novos/alterados)', () => {
|
||||
const digest = buildCoordinationDigest([{ ticket: { id: 37691 }, aiClassification: { reason: 'aguardando definicao' }, exclusionReason: 'needs_coordination' }], '24/08');
|
||||
assert.ok(digest.includes('#37691'));
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,10 @@
|
||||
// @requires config.js
|
||||
const config = parseConfig($env);
|
||||
const glpiBaseUrl = String($('Config - Coordenador GLPI v2').first().json.glpi_base_url || '').replace(/\/$/, '').replace(/'/g, "''");
|
||||
// Esta instancia n8n bloqueia acesso a $env em Code nodes (N8N_BLOCK_ENV_ACCESS_IN_NODE),
|
||||
// entao a configuracao vem do node "Config - Coordenador GLPI v2" (mesmo padrao do V1),
|
||||
// nao de variaveis de ambiente reais.
|
||||
const configSource = $('Config - Coordenador GLPI v2').first().json;
|
||||
const config = parseConfig(configSource);
|
||||
const glpiBaseUrl = String(configSource.glpi_base_url || '').replace(/\/$/, '').replace(/'/g, "''");
|
||||
|
||||
const queryTemplate = /* __SQL_CANDIDATOS_V2__ */;
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// @requires config.js cache.js
|
||||
const config = parseConfig($env);
|
||||
// Instancia bloqueia $env em Code nodes -- config vem do node "Config - Coordenador GLPI v2".
|
||||
const config = parseConfig($('Config - Coordenador GLPI v2').first().json);
|
||||
const now = Date.now();
|
||||
|
||||
const items = $input.all().map((item) => item.json); // { ticket, contextHash }
|
||||
// $input vem do Merge - Aguardar Memoria (so existe para GARANTIR que os candidatos e a
|
||||
// memoria ja foram buscados antes deste node rodar); os dados de verdade sao lidos por nome.
|
||||
const items = $('Code - Mesclar Contexto e Calcular Hash').all().map((item) => item.json); // { ticket, contextHash }
|
||||
const memoryRows = $('Data Table - Buscar Memoria').all().map((item) => item.json);
|
||||
const memoryByTicket = {};
|
||||
for (const row of memoryRows) memoryByTicket[String(row.ticket_id)] = row;
|
||||
|
||||
@@ -15,7 +15,8 @@ const scored = items.map((data) => ({
|
||||
now: data.now,
|
||||
}));
|
||||
|
||||
const config = scored.length ? scored[0].config : parseConfig($env);
|
||||
// Instancia bloqueia $env em Code nodes -- config vem do node "Config - Coordenador GLPI v2".
|
||||
const config = scored.length ? scored[0].config : parseConfig($('Config - Coordenador GLPI v2').first().json);
|
||||
const { selected, excluded } = selectTop(scored, config.topN, config);
|
||||
|
||||
const selectedOut = selected.map((item) => ({ json: { ...item, _grupo: 'selected' } }));
|
||||
|
||||
@@ -32,12 +32,20 @@
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "glpi-base-url-v2",
|
||||
"name": "glpi_base_url",
|
||||
"type": "string",
|
||||
"value": "https://glpi.suaempresa.com.br"
|
||||
}
|
||||
{ "id": "glpi-base-url-v2", "name": "glpi_base_url", "type": "string", "value": "https://glpi.suaempresa.com.br" },
|
||||
{ "id": "cfg-ai-enabled", "name": "AI_ENABLED", "type": "boolean", "value": true },
|
||||
{ "id": "cfg-ai-provider", "name": "AI_PROVIDER", "type": "string", "value": "openai" },
|
||||
{ "id": "cfg-ai-model", "name": "AI_MODEL", "type": "string", "value": "gpt-4o-mini" },
|
||||
{ "id": "cfg-candidate-pool-size", "name": "CANDIDATE_POOL_SIZE", "type": "number", "value": 60 },
|
||||
{ "id": "cfg-ai-max-candidates", "name": "AI_MAX_CANDIDATES", "type": "number", "value": 30 },
|
||||
{ "id": "cfg-top-n", "name": "TOP_N", "type": "number", "value": 10 },
|
||||
{ "id": "cfg-cooldown-days", "name": "COOLDOWN_DAYS", "type": "number", "value": 1 },
|
||||
{ "id": "cfg-ai-confidence-threshold", "name": "AI_CONFIDENCE_THRESHOLD", "type": "number", "value": 0.6 },
|
||||
{ "id": "cfg-ai-max-context-chars", "name": "AI_MAX_CONTEXT_CHARS", "type": "number", "value": 4000 },
|
||||
{ "id": "cfg-dev-mode", "name": "DEV_MODE", "type": "boolean", "value": true },
|
||||
{ "id": "cfg-shadow-mode", "name": "SHADOW_MODE", "type": "boolean", "value": true },
|
||||
{ "id": "cfg-dias-sem-interacao-alerta", "name": "DIAS_SEM_INTERACAO_ALERTA", "type": "number", "value": 2 },
|
||||
{ "id": "cfg-horas-sla-risco", "name": "HORAS_SLA_RISCO", "type": "number", "value": 8 }
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
@@ -112,7 +120,7 @@
|
||||
"name": "Data Table - Buscar Memoria",
|
||||
"type": "n8n-nodes-base.dataTable",
|
||||
"typeVersion": 1.1,
|
||||
"position": [320, -160],
|
||||
"position": [-1120, 300],
|
||||
"parameters": {
|
||||
"resource": "row",
|
||||
"operation": "get",
|
||||
@@ -121,6 +129,14 @@
|
||||
"returnAll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "merge-aguardar-memoria",
|
||||
"name": "Merge - Aguardar Memoria",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [320, -160],
|
||||
"parameters": { "mode": "append", "numberInputs": 2 }
|
||||
},
|
||||
{
|
||||
"id": "code-decidir-necessidade-ia",
|
||||
"name": "Code - Decidir Necessidade de IA",
|
||||
@@ -259,6 +275,11 @@
|
||||
"resource": "row",
|
||||
"operation": "upsert",
|
||||
"dataTableId": { "mode": "name", "value": "coordenador_glpi_memoria" },
|
||||
"filters": {
|
||||
"conditions": [
|
||||
{ "keyName": "ticket_id", "condition": "eq", "keyValue": "={{ $json.ticket_id }}" }
|
||||
]
|
||||
},
|
||||
"columns": {
|
||||
"mappingMode": "defineBelow",
|
||||
"matchingColumns": ["ticket_id"],
|
||||
@@ -306,6 +327,11 @@
|
||||
"resource": "row",
|
||||
"operation": "upsert",
|
||||
"dataTableId": { "mode": "name", "value": "coordenador_glpi_memoria" },
|
||||
"filters": {
|
||||
"conditions": [
|
||||
{ "keyName": "ticket_id", "condition": "eq", "keyValue": "={{ $json.ticket_id }}" }
|
||||
]
|
||||
},
|
||||
"columns": {
|
||||
"mappingMode": "defineBelow",
|
||||
"matchingColumns": ["ticket_id"],
|
||||
@@ -429,7 +455,8 @@
|
||||
"Config - Coordenador GLPI v2": {
|
||||
"main": [[
|
||||
{ "node": "Code - Montar SQL Candidatos", "type": "main", "index": 0 },
|
||||
{ "node": "Code - Montar SQL Legado", "type": "main", "index": 0 }
|
||||
{ "node": "Code - Montar SQL Legado", "type": "main", "index": 0 },
|
||||
{ "node": "Data Table - Buscar Memoria", "type": "main", "index": 0 }
|
||||
]]
|
||||
},
|
||||
"Code - Montar SQL Candidatos": {
|
||||
@@ -451,9 +478,12 @@
|
||||
"main": [[{ "node": "Code - Mesclar Contexto e Calcular Hash", "type": "main", "index": 0 }]]
|
||||
},
|
||||
"Code - Mesclar Contexto e Calcular Hash": {
|
||||
"main": [[{ "node": "Data Table - Buscar Memoria", "type": "main", "index": 0 }]]
|
||||
"main": [[{ "node": "Merge - Aguardar Memoria", "type": "main", "index": 0 }]]
|
||||
},
|
||||
"Data Table - Buscar Memoria": {
|
||||
"main": [[{ "node": "Merge - Aguardar Memoria", "type": "main", "index": 1 }]]
|
||||
},
|
||||
"Merge - Aguardar Memoria": {
|
||||
"main": [[{ "node": "Code - Decidir Necessidade de IA", "type": "main", "index": 0 }]]
|
||||
},
|
||||
"Code - Decidir Necessidade de IA": {
|
||||
|
||||
Reference in New Issue
Block a user