diff --git a/logic/context.js b/logic/context.js new file mode 100644 index 0000000..9d6d4d5 --- /dev/null +++ b/logic/context.js @@ -0,0 +1,62 @@ +function normalizeTicket(row, extras) { + const followups = (extras && extras.followups) || []; + const tasks = (extras && extras.tasks) || []; + return { + id: Number(row.id), + titulo: row.titulo, + status: Number(row.status), + status_nome: row.status_nome, + aberto_em: row.aberto_em, + ultima_interacao_em: row.ultima_interacao_em, + dias_aberto: Number(row.dias_aberto), + dias_sem_interacao: Number(row.dias_sem_interacao), + sla_vencido: Number(row.sla_vencido) === 1, + sla_em_risco: Number(row.sla_em_risco) === 1, + horas_ate_sla: row.horas_ate_sla == null ? null : Number(row.horas_ate_sla), + prioridade: Number(row.prioridade), + urgencia: Number(row.urgencia), + impacto: Number(row.impacto), + entidade: row.entidade, + categoria: row.categoria, + tecnico_atribuido: row.tecnico_atribuido, + sem_tecnico: Number(row.sem_tecnico) === 1, + link_glpi: row.link_glpi, + score_criticidade: Number(row.score_criticidade), + followups, + tasks, + }; +} + +function truncate(text, maxChars) { + const clean = String(text || '').replace(/\s+/g, ' ').trim(); + return clean.length > maxChars ? `${clean.slice(0, Math.max(0, maxChars - 3))}...` : clean; +} + +function buildAiContextText(ticket, maxChars) { + const budget = Math.max(200, Number(maxChars) || 4000); + const followupsText = ticket.followups + .slice(-5) + .map((f) => `- [followup ${f.date}] ${truncate(f.texto, 300)}`) + .join('\n'); + const tasksText = ticket.tasks + .slice(-5) + .map((t) => `- [tarefa ${t.date} status=${t.status}] ${truncate(t.texto, 300)}`) + .join('\n'); + const header = [ + `Chamado #${ticket.id} - ${ticket.titulo}`, + `Status: ${ticket.status_nome} | Categoria: ${ticket.categoria} | Entidade: ${ticket.entidade}`, + `Tecnico: ${ticket.tecnico_atribuido}`, + `Prioridade: ${ticket.prioridade} Urgencia: ${ticket.urgencia} Impacto: ${ticket.impacto}`, + `SLA vencido: ${ticket.sla_vencido} | Dias sem interacao: ${ticket.dias_sem_interacao} | Dias aberto: ${ticket.dias_aberto}`, + ].join('\n'); + const body = [ + 'Ultimos apontamentos:', + followupsText || '(nenhum)', + '', + 'Ultimas tarefas:', + tasksText || '(nenhuma)', + ].join('\n'); + return truncate(`${header}\n\n${body}`, budget); +} + +module.exports = { normalizeTicket, buildAiContextText, truncate }; diff --git a/logic/context.test.js b/logic/context.test.js new file mode 100644 index 0000000..8304f9e --- /dev/null +++ b/logic/context.test.js @@ -0,0 +1,41 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const { normalizeTicket, buildAiContextText, truncate } = require('./context'); + +function rawRow(overrides) { + return Object.assign({ + id: '38611', titulo: ' Colocar etiqueta sala 03 ', status: '2', status_nome: 'Em atendimento', + aberto_em: '2026-08-01 08:00:00', ultima_interacao_em: '2026-08-22 08:00:00', + dias_aberto: '23', dias_sem_interacao: '2', sla_vencido: '1', sla_em_risco: '0', horas_ate_sla: null, + prioridade: '3', urgencia: '3', impacto: '3', entidade: 'Matriz', categoria: 'Facilities', + tecnico_atribuido: 'Sem tecnico atribuido', sem_tecnico: '1', link_glpi: 'https://glpi/x?id=38611', + score_criticidade: '145', + }, overrides); +} + +test('normalizeTicket converte tipos e booleans 0/1', () => { + const t = normalizeTicket(rawRow({}), { followups: [], tasks: [] }); + assert.equal(t.id, 38611); + assert.equal(t.titulo, ' Colocar etiqueta sala 03 '); + assert.equal(t.sla_vencido, true); + assert.equal(t.sla_em_risco, false); + assert.equal(t.sem_tecnico, true); + assert.equal(t.dias_aberto, 23); + assert.deepEqual(t.followups, []); +}); + +test('truncate corta e adiciona reticencias', () => { + const long = 'a'.repeat(50); + assert.equal(truncate(long, 10), `${'a'.repeat(7)}...`); + assert.equal(truncate('curto', 10), 'curto'); +}); + +test('buildAiContextText respeita o orcamento de caracteres', () => { + const t = normalizeTicket(rawRow({}), { + followups: [{ id: 1, date: '2026-08-20', texto: 'x'.repeat(500) }], + tasks: [{ id: 1, date: '2026-08-19', status: 1, texto: 'y'.repeat(500) }], + }); + const text = buildAiContextText(t, 300); + assert.ok(text.length <= 300); + assert.ok(text.includes('#38611')); +});