Fluxo: Consulta de horários¶
Este é o fluxo mais executado em ambos os projetos: paciente pede horários disponíveis para médico X fazer procedimento Y pelo convênio Z.
Pré-condições¶
O paciente já forneceu (via conversa com o agente):
- Nome (ou parte) do médico
- Nome do procedimento/exame
- Nome do convênio (ou "particular")
A tool consultar_horarios(nome_medico, nome_procedimento, nome_convenio) é
chamada pelo LLM.
Fluxograma¶
flowchart TD
A[tool consultar_horarios<br/>nome_medico, nome_procedimento, nome_convenio] --> B[guardian_validate<br/>corrige inversão convenio/procedimento]
B --> C[buscar_convenio por nome]
C --> D{Convênio válido?}
D -->|não| FAIL1[FALHA_CONSULTA: convenio nao encontrado]
D -->|sim| E{nome_medico vazio<br/>ou "sem preferencia"?}
E -->|sim| LOOP[Itera todos os médicos<br/>até achar um com horários]
E -->|não| F[resolver_agenda por primeiro nome]
F --> G{Médico válido?}
G -->|não| FAIL2[FALHA_CONSULTA: medico nao encontrado]
G -->|sim| H[buscar_procedimento por agenda_id + nome]
H --> I{Procedimento válido?}
I -->|não| FAIL3[FALHA_CONSULTA: procedimento nao encontrado]
I -->|sim| J[POST /agendamento/horarios]
J --> K{Response ok?}
K -->|não após 2 tentativas| FAIL4[FALHA_CONSULTA: erro apos 2 tentativas]
K -->|sim| L[_validar_agenda_medico<br/>agendaNome contém primeiro nome?]
L -->|não| FAIL5[FALHA_CONSULTA: agenda de medico errado]
L -->|sim| M[_filter_and_select_slots<br/>5 dias x 2 manhã + 2 tarde]
M --> N{Slots > 0?}
N -->|não| FAIL6[FALHA_CONSULTA: agenda sem horarios]
N -->|sim| OK[Retorna JSON com slots]
OK --> AG[Agente formata em PT-BR]
Implementação — Medcenter (in-process)¶
# scheduling.py:316-378 (MEDCENTER)
def consultar_horarios(nome_medico: str, nome_procedimento: str, nome_convenio: str) -> str:
logger.info(
"=== CONSULTAR HORARIOS === medico='%s' proc='%s' conv='%s'",
nome_medico, nome_procedimento, nome_convenio,
)
proc = buscar_procedimento_por_medico(nome_medico)
if not proc:
return f"FALHA_CONSULTA: medico '{nome_medico}' nao possui agendamento automatico"
conv = buscar_convenio(nome_convenio)
if not conv:
return f"FALHA_CONSULTA: convenio nao encontrado: {nome_convenio}"
hoje = datetime.datetime.now(_TZ_BR).strftime("%Y-%m-%d")
end_date = (datetime.datetime.now(_TZ_BR) + datetime.timedelta(days=90)).strftime("%Y-%m-%d")
data_items = _chamar_api_horarios(proc, conv, hoje, end_date)
if data_items is None:
return "FALHA_CONSULTA: erro ao consultar horarios apos 2 tentativas"
if not _validar_agenda_medico(data_items, proc["medico"]):
return "FALHA_CONSULTA: agenda retornada nao bate com o medico esperado"
slots = _filter_and_select_slots(data_items)
if not slots:
return "FALHA_CONSULTA: agenda sem horarios disponiveis"
return _formatar_slots_para_paciente(proc["medico"], slots)
Implementação — Clinfeto (via workflow)¶
# agendamento.py:662-732 (CLINFETO)
@entrypoint(checkpointer=agendamento_checkpointer)
def consultar_horarios_workflow(payload: dict) -> str:
nome_medico = payload.get("nome_medico", "")
nome_procedimento = payload.get("nome_procedimento", "")
nome_convenio = payload.get("nome_convenio", "")
nome_convenio, nome_procedimento = guardian_validate(nome_convenio, nome_procedimento)
id_convenio = buscar_convenio(nome_convenio)
if id_convenio is None:
return f"FALHA_CONSULTA: convenio nao encontrado: {nome_convenio}"
hoje = _today_br()
data_inicio = hoje.strftime("%Y-%m-%d")
data_fim = (hoje + timedelta(days=90)).strftime("%Y-%m-%d")
if _is_sem_preferencia(nome_medico):
procs = buscar_procedimentos_todos_medicos(nome_procedimento)
if not procs:
return f"FALHA_CONSULTA: procedimento '{nome_procedimento}' nao encontrado"
for proc_info in procs:
id_agenda = proc_info["id_agenda"]
data_items = _consultar_horarios_medico(proc_info, id_convenio, data_inicio, data_fim, id_agenda)
if data_items is None:
continue
slots = _filter_and_select_slots(data_items)
if slots:
medico_nome = _AGENDA_FIRST_NAME.get(id_agenda, "")
return json.dumps({"medico": medico_nome, "slots": slots}, ensure_ascii=False)
return "FALHA_CONSULTA: nenhum medico tem horarios disponiveis"
id_agenda = resolver_agenda(nome_medico)
if id_agenda is None:
return f"FALHA_CONSULTA: medico '{nome_medico}' nao encontrado"
proc_info = buscar_procedimento(nome_procedimento, id_agenda)
if proc_info is None:
return f"FALHA_CONSULTA: procedimento '{nome_procedimento}' nao encontrado"
data_items = _consultar_horarios_medico(proc_info, id_convenio, data_inicio, data_fim, id_agenda)
if data_items is None:
return "FALHA_CONSULTA: erro ao consultar horarios apos 2 tentativas"
slots = _filter_and_select_slots(data_items)
if not slots:
return "FALHA_CONSULTA: agenda sem horarios disponiveis"
return json.dumps(slots, ensure_ascii=False)
Formato de retorno para o agente¶
O agente IA precisa receber um formato estável que consiga parafrasear. Duas abordagens praticadas:
Simples, mas mistura dados com formatação. Difícil de reaproveitar programaticamente.
Preferência para projetos novos
Use JSON estruturado. O LLM lida bem com isso e o downstream fica testável.
Suporte a "sem preferência de médico"¶
O paciente pode dizer "qualquer médico", "tanto faz", "sem preferência".
# agendamento.py:394-401 (CLINFETO)
def _is_sem_preferencia(nome_medico: str) -> bool:
if not nome_medico:
return True
norm = _normalize(nome_medico)
return any(k in norm for k in ("SEM PREFERENCIA", "QUALQUER", "TANTO FAZ", "NAO TENHO"))
Nesse caso, iterar procedimento igual (US ABDOME TOTAL) através de todos
os médicos que o oferecem, e devolver o primeiro com slots.
Filtragem de slots¶
Ver Filtragem de slots. Resumo:
- Timezone
America/Sao_Paulo. - Descartar dias
< hoje. - Se
dia == hoje, descartarhora <= agora. - Agrupar por dia, depois por período (manhã
<12h, tarde>=12h). - Pegar primeiros 5 dias com disponibilidade.
- Cada dia: 2 slots manhã + 2 tarde (configurável).
Próximo passo¶
Quando o paciente escolhe uma das opções, o fluxo de agendamento completo assume.