Files
SLACK_COORD_RELATORIO/coordenador/logic/context.js
T
Gustavo Santos e322cec92b chore: import coordenador (subtree)
git-subtree-dir: coordenador
git-subtree-mainline: facbe2e340
git-subtree-split: ce7af94d55
2026-10-01 14:25:55 -03:00

88 lines
3.4 KiB
JavaScript

const { normalizeGlpiText } = require('./normalize');
function normalizeTicket(row, extras) {
const followups = ((extras && extras.followups) || []).map((f) => ({
id: f.id,
date: f.date,
texto: normalizeGlpiText(f.texto),
}));
const tasks = ((extras && extras.tasks) || []).map((t) => ({
id: t.id,
date: t.date,
status: t.status,
texto: normalizeGlpiText(t.texto),
}));
return {
id: Number(row.id),
titulo: normalizeGlpiText(row.titulo),
descricao: normalizeGlpiText(row.descricao),
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;
}
// Corta pelo tamanho total sem achatar quebras de linha -- usado só no corte final do
// contexto inteiro (o texto de cada seção já foi normalizado/truncado individualmente).
function truncatePreservingLines(text, maxChars) {
const str = String(text || '');
return str.length > maxChars ? `${str.slice(0, Math.max(0, maxChars - 3))}...` : str;
}
// Contexto com secoes delimitadas (TITULO / DESCRICAO / STATUS-TECNICO-GRUPO / FOLLOW-UPS /
// TAREFAS), nao uma unica string sem estrutura semantica. O texto de cada campo ja chega
// normalizado (normalizeTicket ja rodou normalizeGlpiText em titulo/descricao/texto).
function buildAiContextText(ticket, maxChars) {
const budget = Math.max(200, Number(maxChars) || 4000);
const followupsText = ticket.followups
.slice(-5)
.map((f) => `- [${f.date}] ${truncate(f.texto, 300) || '(sem conteudo)'}`)
.join('\n');
const tasksText = ticket.tasks
.slice(-5)
.map((t) => `- [${t.date} status=${t.status}] ${truncate(t.texto, 300) || '(sem conteudo)'}`)
.join('\n');
const secoes = [
`TITULO\n${truncate(ticket.titulo, 200) || '(sem titulo)'}`,
`DESCRICAO ORIGINAL\n${truncate(ticket.descricao, 800) || '(sem descricao)'}`,
[
`STATUS: ${ticket.status_nome}`,
`TECNICO: ${ticket.tecnico_atribuido}`,
`GRUPO/ENTIDADE: ${ticket.entidade} | CATEGORIA: ${ticket.categoria}`,
`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'),
`FOLLOW-UPS\n${followupsText || '(nenhum)'}`,
`TAREFAS\n${tasksText || '(nenhuma)'}`,
];
const contextoCompleto = `Chamado #${ticket.id}\n\n${secoes.join('\n\n')}`;
return truncatePreservingLines(contextoCompleto, budget);
}
module.exports = { normalizeTicket, buildAiContextText, truncate, truncatePreservingLines };