const { test } = require('node:test'); const assert = require('node:assert/strict'); const { computeScore } = require('./score'); const CONFIG = { aiConfidenceThreshold: 0.6, cooldownDays: 1 }; const NOW = new Date('2026-08-24T08:00:00Z').getTime(); function ticket(overrides) { return Object.assign({ id: 1, sla_vencido: false, sem_tecnico: false, dias_sem_interacao: 5, dias_aberto: 5, prioridade: 3, urgencia: 3, score_criticidade: 100, }, overrides); } function ai(overrides) { return Object.assign({ classification: 'ACTIONABLE_NOW', actionable_today: true, confidence: 0.9, is_fallback: false, reason: 'x', evidence: [], }, overrides); } test('chamado acionavel + SLA vencido + prioridade alta cai no grupo critico_acionavel com score alto', () => { const t = ticket({ sla_vencido: true, prioridade: 5, urgencia: 5 }); const result = computeScore({ ticket: t, aiClassification: ai({}), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.group, 'critico_acionavel'); assert.ok(result.score > 200); assert.equal(result.excludedFromTop10, false); }); test('WAITING_REQUESTER sem mudanca nao monopoliza (score baixo)', () => { const t = ticket({}); const acionavel = computeScore({ ticket: t, aiClassification: ai({}), memory: null, contextChanged: true, config: CONFIG, now: NOW }); const esperando = computeScore({ ticket: t, aiClassification: ai({ classification: 'WAITING_REQUESTER' }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.ok(esperando.score < acionavel.score); }); test('SCHEDULED nao entra antes da data (excluido do Top10)', () => { const result = computeScore({ ticket: ticket({}), aiClassification: ai({ classification: 'SCHEDULED' }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.excludedFromTop10, true); assert.equal(result.exclusionReason, 'scheduled_not_due'); }); test('sem tecnico e acionavel recebe bonus e grupo sem_tecnico', () => { const t = ticket({ sem_tecnico: true, sla_vencido: false }); const result = computeScore({ ticket: t, aiClassification: ai({ classification: 'UNASSIGNED_ACTIONABLE' }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.group, 'sem_tecnico'); assert.ok(result.breakdown.semTecnicoBonus > 0); }); test('confianca baixa reduz o peso da acionabilidade', () => { const t = ticket({}); const altaConfianca = computeScore({ ticket: t, aiClassification: ai({ confidence: 0.9 }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); const baixaConfianca = computeScore({ ticket: t, aiClassification: ai({ confidence: 0.2 }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.ok(baixaConfianca.breakdown.actionabilityPoints < altaConfianca.breakdown.actionabilityPoints); }); test('NEEDS_COORDINATION e excluido do Top10 diario', () => { const result = computeScore({ ticket: ticket({}), aiClassification: ai({ classification: 'NEEDS_COORDINATION' }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.excludedFromTop10, true); }); test('fallback nao recebe bonus de acionabilidade (comportamento seguro tipo V1)', () => { const result = computeScore({ ticket: ticket({}), aiClassification: ai({ classification: 'AI_FALLBACK', is_fallback: true }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.breakdown.actionabilityPoints, 0); }); test('AI_FALLBACK comum e excluido do Top10 (nao pode competir como se fosse acionavel confirmado)', () => { const result = computeScore({ ticket: ticket({}), aiClassification: ai({ classification: 'AI_FALLBACK', is_fallback: true, confidence: 0 }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.excludedFromTop10, true); assert.equal(result.exclusionReason, 'ai_fallback_no_override'); }); test('AI_FALLBACK em chamado critico (SLA vencido + prioridade alta) entra por excecao explicita', () => { const critico = ticket({ sla_vencido: true, prioridade: 5, urgencia: 5 }); const result = computeScore({ ticket: critico, aiClassification: ai({ classification: 'AI_FALLBACK', is_fallback: true, confidence: 0 }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.excludedFromTop10, false); assert.equal(result.decisionReason, 'ai_fallback_critical_override'); }); test('INSUFFICIENT_CONTEXT comum fica fora da competicao operacional normal (excluido do Top10)', () => { const result = computeScore({ ticket: ticket({}), aiClassification: ai({ classification: 'INSUFFICIENT_CONTEXT', confidence: 0.3 }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.excludedFromTop10, true); assert.equal(result.exclusionReason, 'insufficient_context_no_override'); }); test('INSUFFICIENT_CONTEXT critico entra somente pela excecao definida', () => { const critico = ticket({ sem_tecnico: true, prioridade: 4 }); const result = computeScore({ ticket: critico, aiClassification: ai({ classification: 'INSUFFICIENT_CONTEXT', confidence: 0.3 }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.excludedFromTop10, false); assert.equal(result.decisionReason, 'insufficient_context_critical_override'); }); test('BUG REAL comprovado na Run A anterior: WAITING_VENDOR fica fora do Top10 mesmo com contextChanged=true -- missao "finalizacao para producao" secao 10', () => { const result = computeScore({ ticket: ticket({}), aiClassification: ai({ classification: 'WAITING_VENDOR', actionable_today: false, confidence: 0.8 }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.excludedFromTop10, true); assert.equal(result.exclusionReason, 'blocked_waiting_vendor'); }); test('WAITING_APPROVAL (pontuacao base positiva) tambem fica fora do Top10 -- antes so tinha penalidade de score, nao exclusao', () => { const result = computeScore({ ticket: ticket({}), aiClassification: ai({ classification: 'WAITING_APPROVAL', actionable_today: false, confidence: 0.8 }), memory: null, contextChanged: true, config: CONFIG, now: NOW }); assert.equal(result.excludedFromTop10, true); });