Files
SLACK_COORD_RELATORIO/coordenador/n8n/nodes/compare-legacy-v2.node.js
T
Gustavo Santos e322cec92b chore: import coordenador (subtree)
git-subtree-dir: coordenador
git-subtree-mainline: facbe2e340
git-subtree-split: ce7af94d55
2026-10-01 14:25:55 -03:00

84 lines
3.8 KiB
JavaScript

// @requires dataQuality.js
// Este node roda depois de "Merge - Unir Shadow" (append: Code - Montar Digest Coordenacao +
// MySQL - Legado Top 10 (Shadow)), que existe so para GARANTIR que os dois ja executaram --
// os dados continuam sendo lidos por nome, nao pelo item mesclado, para nao depender de ordem.
const digestData = $('Code - Montar Digest Coordenacao').first().json; // { mensagem, selected, excluded, dateStr, digest, changedBlocked }
const legacyRows = $('MySQL - Legado Top 10 (Shadow)').all().map((item) => item.json);
const legacyIds = legacyRows.map((row) => Number(row.id));
const v2Ids = digestData.selected.map((item) => Number(item.ticket.id));
const added = v2Ids.filter((id) => !legacyIds.includes(id));
const removed = legacyIds.filter((id) => !v2Ids.includes(id));
const addedReasons = added.map((id) => {
const item = digestData.selected.find((s) => Number(s.ticket.id) === id);
return { id, motivo: item ? `${item.aiClassification.classification} - ${item.aiClassification.reason}` : 'desconhecido' };
});
const removedReasons = removed.map((id) => {
const excludedItem = digestData.excluded.find((e) => Number(e.ticket.id) === id);
return { id, motivo: excludedItem ? (excludedItem.exclusionReason || excludedItem.aiClassification.classification) : 'nao estava nos candidatos V2' };
});
// --- observabilidade (secao 24 da missao) ---
const allItems = [...digestData.selected, ...digestData.excluded];
const cacheHits = allItems.filter((i) => i.resolucao === 'cache').length;
const aiRequested = allItems.filter((i) => i.resolucao === 'ia').length;
const aiFallbackCapCount = allItems.filter((i) => i.resolucao === 'fallback_cap').length;
const aiSuccess = allItems.filter((i) => i.resolucao === 'ia' && !i.aiClassification.is_fallback).length;
const aiErrors = allItems.filter((i) => i.resolucao === 'ia' && i.aiClassification.is_fallback).length;
const aiFallbackCount = allItems.filter((i) => i.aiClassification.is_fallback).length;
const aiFallbackByReason = {};
allItems
.filter((i) => i.aiClassification.is_fallback)
.forEach((i) => {
const r = i.aiClassification.fallback_reason || 'unknown';
aiFallbackByReason[r] = (aiFallbackByReason[r] || 0) + 1;
});
const classificationCounts = {};
allItems.forEach((i) => {
const c = i.aiClassification.classification;
classificationCounts[c] = (classificationCounts[c] || 0) + 1;
});
const actionableRealCount = allItems.filter((i) => !i.aiClassification.is_fallback
&& (i.aiClassification.classification === 'ACTIONABLE_NOW' || i.aiClassification.classification === 'UNASSIGNED_ACTIONABLE')).length;
const criticalOverrideCount = allItems.filter((i) => i.decisionReason && String(i.decisionReason).endsWith('_critical_override')).length;
const observability = {
candidate_count: allItems.length,
cache_hits: cacheHits,
cache_misses: allItems.length - cacheHits,
ai_requested: aiRequested,
ai_success: aiSuccess,
ai_errors: aiErrors,
ai_fallback_count: aiFallbackCount,
ai_fallback_cap_count: aiFallbackCapCount,
ai_fallback_by_reason: aiFallbackByReason,
classification_counts: classificationCounts,
insufficient_context_count: classificationCounts.INSUFFICIENT_CONTEXT || 0,
actionable_real_count: actionableRealCount,
critical_override_count: criticalOverrideCount,
top10_count: digestData.selected.length,
blocked_count: digestData.excluded.length,
coordination_count: classificationCounts.NEEDS_COORDINATION || 0,
};
const dataQualityReview = buildDataQualityReview(allItems);
return [{
json: {
run_at: Date.now(),
legacy_ids: JSON.stringify(legacyIds),
v2_ids: JSON.stringify(v2Ids),
added: JSON.stringify(added),
removed: JSON.stringify(removed),
summary: JSON.stringify({ addedReasons, removedReasons, observability, dataQualityReview }),
mensagem: digestData.mensagem,
digest: digestData.digest,
},
}];