fix(v2): AI_FALLBACK replaces the ACTIONABLE_NOW-with-confidence-0 lie, normalize null-like strings

Fallback (ai_disabled, ai_error, invalid_json, ai_max_candidates_cap --
any candidate the AI never actually analyzed) now produces classification
'AI_FALLBACK', an explicit technical state that is never part of the
11-class enum sent to OpenAI (the AI itself can never legitimately return
it). Previously it silently claimed ACTIONABLE_NOW with confidence 0,
which made unanalyzed tickets indistinguishable from genuinely confirmed
ones at the classification-label level.

Also normalizes blocker_type/waiting_for when the model returns the
literal string "null"/"none"/"n/a"/"" instead of JSON null -- done
once, in the validation layer (logic/classification.js), not scattered
around the workflow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Gustavo
2026-08-24 16:49:22 -03:00
co-authored by Claude Sonnet 5
parent db7a261a10
commit 00a42db269
6 changed files with 87 additions and 11 deletions
+15 -3
View File
@@ -4,6 +4,18 @@ const VALID_CLASSIFICATIONS = [
'PROJECT_LONG_TERM', 'DUPLICATE_OR_OBSOLETE', 'INSUFFICIENT_CONTEXT',
];
// O modelo, apesar do schema permitir null de verdade em blocker_type/waiting_for, as vezes
// devolve a STRING "null" (ou variantes equivalentes) em vez do valor JSON null. Normaliza
// aqui, na camada de validacao -- unico lugar que toca a resposta crua da IA.
const NULL_LIKE_STRINGS = new Set(['null', 'none', 'n/a', 'na', '']);
function normalizeNullableString(value) {
if (value === null || value === undefined) return null;
const trimmed = String(value).trim();
if (NULL_LIKE_STRINGS.has(trimmed.toLowerCase())) return null;
return trimmed;
}
function validateAiClassification(raw) {
const errors = [];
if (!raw || typeof raw !== 'object') {
@@ -28,8 +40,8 @@ function validateAiClassification(raw) {
classification: raw.classification,
actionable_today: raw.actionable_today,
confidence,
blocker_type: raw.blocker_type ?? null,
waiting_for: raw.waiting_for ?? null,
blocker_type: normalizeNullableString(raw.blocker_type),
waiting_for: normalizeNullableString(raw.waiting_for),
next_action: raw.next_action,
suggested_action_type: raw.suggested_action_type ?? 'technical_action',
business_risk: raw.business_risk ?? 'medium',
@@ -41,4 +53,4 @@ function validateAiClassification(raw) {
};
}
module.exports = { VALID_CLASSIFICATIONS, validateAiClassification };
module.exports = { VALID_CLASSIFICATIONS, validateAiClassification, normalizeNullableString };