feat(secretaria): injeta funcionamento da clínica (horários/feriados) no prompt

brain.ts busca GET /status da agenda-bridge (aberto hoje?, próximo dia aberto,
grade da semana, feriados) e injeta no system prompt via formatClinicStatus,
junto da data já existente. Regra: a Secretária só oferece horário/agenda quando
a clínica está ABERTA — em dia fechado/feriado, informa o horário e oferece o
próximo dia disponível. (Par do satélite: scoreodonto #2.)

Testado em dev: ao pedirem "hoje" num domingo, ofereceu "amanhã" nos horários
configurados.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Deploy Agent
2026-07-06 01:24:20 +02:00
parent 9bbfe498e2
commit 983890c9be
3 changed files with 80 additions and 3 deletions
@@ -57,7 +57,7 @@ export class ProtocolEngine {
if (!agent) throw new Error('Agente não encontrado')
// Monta system prompt a partir dos nós ativos + contexto externo
const systemPrompt = await this.buildSystemPrompt(agent, conversation, opts, userMessage)
let systemPrompt = await this.buildSystemPrompt(agent, conversation, opts, userMessage)
// Carrega apenas as últimas N mensagens (não o histórico completo)
const contextWindow: number = agent.context_window ?? 8
@@ -138,6 +138,18 @@ export class ProtocolEngine {
},
}
// Funcionamento da clínica (aberto hoje?, feriados) — injeta no prompt para a
// Secretária saber quando dizer que está fechado, sem depender de tool call.
const statusClinicaId = opts?.clinicaId ?? secCfg?.clinica_id
if (agendaUrl && statusClinicaId) {
try {
const u = new URL(`${agendaUrl.replace(/\/$/, '')}/status`)
u.searchParams.set('clinica_id', statusClinicaId)
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 */ }
}
let response: string
let usageInfo: any = null
let providerUsed: string | null = null
@@ -203,6 +215,32 @@ 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 /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 {
if (!st?.hoje) return ''
const dm = (s: string) => { const m = String(s).match(/^(\d{4})-(\d{2})-(\d{2})/); return m ? `${m[3]}/${m[2]}` : s }
const janelas = (arr: any[]) => (arr || []).map((h) => `${h.inicio}${h.fim}`).join(', ')
const L: string[] = ['=== FUNCIONAMENTO DA CLÍNICA ===']
const h = st.hoje
L.push(h.aberto
? `Hoje é ${h.dia_nome} (${dm(h.data)}): ABERTO — ${janelas(h.horarios)}.`
: `Hoje é ${h.dia_nome} (${dm(h.data)}): FECHADO${h.motivo ? ` (${h.motivo})` : ''}.`)
if (st.proximo_aberto && (!h.aberto || st.proximo_aberto.data !== h.data)) {
const p = st.proximo_aberto
L.push(`Próximo dia aberto: ${p.dia_nome} (${dm(p.data)}), ${janelas(p.horarios)}.`)
}
if (Array.isArray(st.semana) && st.semana.length) {
const grade = st.semana.map((d: any) => `${d.dia_nome.slice(0, 3)} ${d.aberto ? janelas(d.horarios) : 'fechado'}`).join('; ')
L.push(`Grade da semana: ${grade}.`)
}
if (Array.isArray(st.feriados_proximos) && st.feriados_proximos.length) {
L.push(`Próximos feriados/fechamentos: ${st.feriados_proximos.map((f: any) => `${dm(f.data)} ${f.nome}`).join('; ')}.`)
}
L.push('IMPORTANTE: só ofereça horários e confirme agendamento quando a clínica estiver ABERTA. Se pedirem em dia/horário fechado ou feriado, informe o horário de funcionamento e ofereça o próximo dia disponível.')
return `\n\n${L.join('\n')}\n`
}
// ── System Prompt Builder ─────────────────────────────────────────────────
private async buildSystemPrompt(