feat(v2): add auditable final score combining actionability, baseline and cooldown

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Gustavo
2026-08-24 15:17:06 -03:00
co-authored by Claude Sonnet 5
parent 014c48d578
commit 749be7dbae
2 changed files with 119 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
const { computeCooldown } = require('./cooldown');
const CLASSIFICATION_BASE_POINTS = {
ACTIONABLE_NOW: 200,
UNASSIGNED_ACTIONABLE: 180,
WAITING_APPROVAL: 40,
INSUFFICIENT_CONTEXT: 0,
WAITING_REQUESTER: -150,
WAITING_VENDOR: -150,
DEPENDENCY_BLOCKED: -150,
PROJECT_LONG_TERM: -120,
DUPLICATE_OR_OBSOLETE: -200,
SCHEDULED: -1000,
NEEDS_COORDINATION: -1000,
};
function computeScore({ ticket, aiClassification, memory, contextChanged, config, now }) {
const classification = aiClassification.classification;
const cooldown = computeCooldown({ ticket, classification, contextChanged, memory, now, config });
const baseline = Math.min(Number(ticket.score_criticidade) || 0, 150) * 0.4;
const semTecnicoBonus = ticket.sem_tecnico ? 30 : 0;
let actionabilityPoints = 0;
if (!aiClassification.is_fallback) {
actionabilityPoints = CLASSIFICATION_BASE_POINTS[classification] ?? 0;
if (actionabilityPoints > 0 && Number(aiClassification.confidence) < config.aiConfidenceThreshold) {
actionabilityPoints /= 2;
}
}
const scheduledNotDue = classification === 'SCHEDULED';
const excludedFromTop10 = cooldown.blocked || classification === 'NEEDS_COORDINATION' || scheduledNotDue;
const total = baseline + semTecnicoBonus + actionabilityPoints - cooldown.penalty;
let group = 'ranking_geral';
const actionableIsh = classification === 'ACTIONABLE_NOW' || classification === 'UNASSIGNED_ACTIONABLE' || aiClassification.is_fallback;
if (actionableIsh && ticket.sla_vencido && (ticket.prioridade >= 4 || ticket.urgencia >= 4)) group = 'critico_acionavel';
else if (actionableIsh && ticket.sem_tecnico) group = 'sem_tecnico';
else if (actionableIsh && ticket.sla_vencido) group = 'sla_vencido_acionavel';
else if (actionableIsh && ticket.dias_sem_interacao <= 2) group = 'quick_win';
return {
score: Math.round(total * 100) / 100,
group,
excludedFromTop10,
exclusionReason: excludedFromTop10 ? (scheduledNotDue ? 'scheduled_not_due' : cooldown.reason) : null,
breakdown: { baseline, semTecnicoBonus, actionabilityPoints, cooldownPenalty: cooldown.penalty },
cooldown,
};
}
module.exports = { computeScore, CLASSIFICATION_BASE_POINTS };