feat(secretaria): roteamento por especialidade, situações, dúvida→humano, imagem, checklist do dono e desligar global
Secretária IA mais segura e contextual: - Roteamento por especialidade: /contexto (dentistas+especialidades) injetado no cérebro; descobrir o motivo e priorizar clínico geral só p/ avaliação/limpeza. - Situações da clínica: regras editáveis injetadas no prompt (siga à risca). - Dúvida/caso incerto → tool encaminhar_humano (não agenda; pede p/ aguardar). - Imagem/áudio: ext:message.transcription religa no fluxo ext:message.new (guard hadCaption evita processar 2x); a IA reage à descrição da imagem. - Checklist do dono × banco → cautelas no /contexto: NÃO assumir clínico geral por padrão p/ quem já é paciente; sem certeza de quem/qual dentista → humano. - Desligar GLOBAL da Secretária: GET/POST /secretaria/power (kill-switch auto_reply via config.set, efeito imediato, sem restart; visível a todos). Inclui a plumbing da sessão (chat.upsert, ownership de conexão, ws-bridge). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -149,6 +149,13 @@ export class ProtocolEngine {
|
||||
const r = await fetch(u, { headers: { 'x-nw-agenda-secret': agendaSecret ?? '' }, signal: AbortSignal.timeout(6000) })
|
||||
if (r.ok) systemPrompt += ProtocolEngine.formatClinicStatus(await r.json())
|
||||
} catch { /* best-effort: sem status, a Secretária segue com a data já injetada */ }
|
||||
// Contexto clínico: dentistas+especialidades + situações (regras) + roteamento.
|
||||
try {
|
||||
const uc = new URL(`${agendaUrl.replace(/\/$/, '')}/contexto`)
|
||||
uc.searchParams.set('clinica_id', statusClinicaId)
|
||||
const rc = await fetch(uc, { headers: { 'x-nw-agenda-secret': agendaSecret ?? '' }, signal: AbortSignal.timeout(6000) })
|
||||
if (rc.ok) systemPrompt += ProtocolEngine.formatClinicContext(await rc.json())
|
||||
} catch { /* best-effort */ }
|
||||
}
|
||||
|
||||
let response: string
|
||||
@@ -216,6 +223,33 @@ export class ProtocolEngine {
|
||||
return `${p(now.getDate())}${p(now.getMonth() + 1)}${String(now.getFullYear()).slice(-2)}${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`
|
||||
}
|
||||
|
||||
// Formata o /contexto (dentistas+especialidades + situações/regras) p/ o prompt.
|
||||
// Guia o roteamento por especialidade, as regras da clínica e o encaminhamento
|
||||
// de dúvidas para a secretária humana.
|
||||
static formatClinicContext(ctx: any): string {
|
||||
const L: string[] = []
|
||||
if (Array.isArray(ctx?.dentistas) && ctx.dentistas.length) {
|
||||
L.push('=== DENTISTAS E ESPECIALIDADES ===')
|
||||
for (const d of ctx.dentistas) L.push(`- ${d.nome}: ${(d.especialidades || []).join(', ') || 'clínico geral'}`)
|
||||
L.push('ROTEAMENTO: descubra SEMPRE o motivo da consulta. Para avaliação, limpeza, restauração e dúvidas → priorize um CLÍNICO GERAL. Para casos específicos (ortodontia, endodontia/canal, prótese, implante etc.) → o dentista da ESPECIALIDADE.')
|
||||
}
|
||||
if (Array.isArray(ctx?.situacoes) && ctx.situacoes.length) {
|
||||
L.push('')
|
||||
L.push('=== SITUAÇÕES E REGRAS DA CLÍNICA (siga à risca) ===')
|
||||
for (const s of ctx.situacoes) L.push(`- ${s}`)
|
||||
}
|
||||
if (Array.isArray(ctx?.cautelas) && ctx.cautelas.length) {
|
||||
L.push('')
|
||||
L.push('=== CAUTELAS (o sistema ainda não tem todos os dados — NÃO chute) ===')
|
||||
for (const c of ctx.cautelas) L.push(`- ${c}`)
|
||||
L.push('Na dúvida sobre QUEM é o paciente, QUAL dentista atende, ou O QUE ele quer: NÃO agende — use encaminhar_humano e peça para aguardar.')
|
||||
}
|
||||
L.push('')
|
||||
L.push('DÚVIDAS / CASOS INCERTOS: se for uma dúvida inicial, um caso que você não sabe resolver, ou que uma regra acima mande avaliar, NÃO agende automaticamente — use a ferramenta encaminhar_humano para a secretária humana decidir e peça ao cliente para AGUARDAR. Nem sempre o certo é agendar.')
|
||||
L.push('IMAGENS: se o cliente enviar uma imagem (foto de dente, exame), tente identificar do que se trata; se não estiver claro ou for caso clínico, trate como dúvida (encaminhar_humano).')
|
||||
return L.length ? `\n\n${L.join('\n')}\n` : ''
|
||||
}
|
||||
|
||||
// Formata o /status da agenda (funcionamento da clínica) para o system prompt.
|
||||
// A Secretária usa isso para NÃO oferecer horários/agendar em dia/hora fechado.
|
||||
static formatClinicStatus(st: any): string {
|
||||
|
||||
Reference in New Issue
Block a user