Pular para conteúdo

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:

Dia 15/04/2026 (terça):
 - 08:00
 - 08:30
 - 14:00
 - 14:30

Dia 16/04/2026 (quarta):
 - 09:00
 - 14:00

Simples, mas mistura dados com formatação. Difícil de reaproveitar programaticamente.

[
  {"data": "15-04-2026", "hora": "08:00", "dataconsulta": "2026-04-15 08:00:00"},
  {"data": "15-04-2026", "hora": "08:30", "dataconsulta": "2026-04-15 08:30:00"}
]

O LLM formata em texto ele mesmo no prompt de saída. Permite pós-processamento.

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, descartar hora <= 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.