From efba7792fa58e01316d70c68dc02c98ea878237d Mon Sep 17 00:00:00 2001 From: Gustavo Date: Mon, 24 Aug 2026 15:15:50 -0300 Subject: [PATCH] feat(v2): add safe AI fallback and cache/reanalysis decision Co-Authored-By: Claude Sonnet 5 --- logic/cache.js | 8 ++++++++ logic/cache.test.js | 30 ++++++++++++++++++++++++++++++ logic/fallback.js | 18 ++++++++++++++++++ logic/fallback.test.js | 12 ++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 logic/cache.js create mode 100644 logic/cache.test.js create mode 100644 logic/fallback.js create mode 100644 logic/fallback.test.js diff --git a/logic/cache.js b/logic/cache.js new file mode 100644 index 0000000..b82a1d9 --- /dev/null +++ b/logic/cache.js @@ -0,0 +1,8 @@ +function needsReanalysis({ memory, currentHash, now }) { + if (!memory) return { needsAi: true, reason: 'no_cache' }; + if (memory.last_context_hash !== currentHash) return { needsAi: true, reason: 'context_changed' }; + if (memory.next_review_at != null && now >= memory.next_review_at) return { needsAi: true, reason: 'review_due' }; + return { needsAi: false, reason: 'cache_valid' }; +} + +module.exports = { needsReanalysis }; diff --git a/logic/cache.test.js b/logic/cache.test.js new file mode 100644 index 0000000..d37d186 --- /dev/null +++ b/logic/cache.test.js @@ -0,0 +1,30 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const { needsReanalysis } = require('./cache'); + +test('sem memoria previa, sempre precisa de IA', () => { + const result = needsReanalysis({ memory: null, currentHash: 'abc', now: 1000 }); + assert.equal(result.needsAi, true); + assert.equal(result.reason, 'no_cache'); +}); + +test('hash igual e sem next_review_at vencido usa cache', () => { + const memory = { last_context_hash: 'abc', next_review_at: null }; + const result = needsReanalysis({ memory, currentHash: 'abc', now: 1000 }); + assert.equal(result.needsAi, false); + assert.equal(result.reason, 'cache_valid'); +}); + +test('hash diferente forca reavaliacao (mudanca significativa)', () => { + const memory = { last_context_hash: 'abc', next_review_at: null }; + const result = needsReanalysis({ memory, currentHash: 'xyz', now: 1000 }); + assert.equal(result.needsAi, true); + assert.equal(result.reason, 'context_changed'); +}); + +test('next_review_at vencido forca reavaliacao mesmo com hash igual', () => { + const memory = { last_context_hash: 'abc', next_review_at: 500 }; + const result = needsReanalysis({ memory, currentHash: 'abc', now: 1000 }); + assert.equal(result.needsAi, true); + assert.equal(result.reason, 'review_due'); +}); diff --git a/logic/fallback.js b/logic/fallback.js new file mode 100644 index 0000000..19983a1 --- /dev/null +++ b/logic/fallback.js @@ -0,0 +1,18 @@ +function buildFallbackClassification(reason) { + return { + classification: 'ACTIONABLE_NOW', + actionable_today: true, + confidence: 0, + blocker_type: null, + waiting_for: null, + next_action: 'Revisar chamado manualmente', + suggested_action_type: 'manual_review', + business_risk: 'medium', + reason: `Fallback deterministico: ${reason}`, + evidence: [], + is_fallback: true, + fallback_reason: reason, + }; +} + +module.exports = { buildFallbackClassification }; diff --git a/logic/fallback.test.js b/logic/fallback.test.js new file mode 100644 index 0000000..154862a --- /dev/null +++ b/logic/fallback.test.js @@ -0,0 +1,12 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const { buildFallbackClassification } = require('./fallback'); + +test('fallback e sempre seguro e marcado como tal', () => { + const result = buildFallbackClassification('ai_timeout'); + assert.equal(result.is_fallback, true); + assert.equal(result.fallback_reason, 'ai_timeout'); + assert.equal(result.confidence, 0); + assert.equal(result.classification, 'ACTIONABLE_NOW'); + assert.ok(result.reason.includes('Fallback')); +});