Pular para conteúdo

⚠️ Problema crítico: "primeiro nome do doutor"

Esta página existe por pedido explícito — é a armadilha mais perigosa do padrão OTIMUS atual. Leia antes de estender Medcenter ou iniciar projeto novo.

O que é

Para resolver o nome do médico (em linguagem natural do paciente) em um agenda_id numérico, ambos os projetos usam lookup por string pelo primeiro nome. O MEDCENTER ainda adiciona sobrenomes únicos como fallback.

# scheduling.py:157-188 (MEDCENTER)
def _validar_agenda_medico(dias: list[dict], medico_esperado: str) -> bool:
    medico_norm = _normalize(medico_esperado)
    if "JOAO PAULO" in medico_norm:
        check_token = "JOAO PAULO"
    else:
        check_token = medico_norm.split()[0]   # <-- primeiro nome
    ...
# scheduling.py:88-98 (MEDCENTER) — validação no boot
for _p in PROCEDIMENTOS:
    _nome = _normalize(_p["medico"]).split()[0]
    if _nome in _primeiros_nomes:
        raise RuntimeError(
            f"ERRO CRITICO: primeiro nome duplicado '{_nome}' ..."
        )
    _primeiros_nomes.append(_nome)

A premissa é: "todos os médicos têm primeiros nomes únicos".

Por que é frágil

Cenário 1 — dois médicos com mesmo primeiro nome

Clínica tem Dr. João Paulo Almeida (angiologista) e contrata um Dr. João Pedro Silva (clínico). Primeiro nome comum: JOÃO.

  • Validação no boot trava: raise RuntimeError. Sistema não sobe.
  • Desenvolvedor precisa editar _DOCTOR_KEYWORDS manualmente, inventar um novo padrão (ex: "JOAO PAULO" + "JOAO PEDRO").
  • Se alguém não lembra dessa regra, adiciona o médico no PROCEDIMENTOS e esquece de atualizar _DOCTOR_KEYWORDS → container não sobe em produção → incidente.

Cenário 2 — sobrenomes compartilhados

No Medcenter:

  • CUNHA aparece em Regina e Sabrina → não pode estar em _DOCTOR_KEYWORDS.
  • DALCOL aparece em Juriadyson e Sabrina → não pode estar.

Se alguém adiciona CUNHA ou DALCOL como keyword (por engano), a primeira ocorrência na lista sempre ganha — paciente vai ser agendado com o médico errado, silenciosamente.

Cenário 3 — paciente cita sobrenome ambíguo

Paciente: "Quero agendar com a Dra. Cunha".

  • LLM passa nome_medico="Dra. Cunha" para a tool.
  • _DOCTOR_KEYWORDS não tem CUNHA (porque não é único).
  • Nenhuma keyword bate → retorna FALHA_CONSULTA.
  • Agente transfere para humano. Funciona, mas UX ruim.

Cenário 4 — OTIMUS retorna agenda errada

Mesmo com keyword correta, o OTIMUS às vezes retorna a agenda de outro médico (bug deles). A validação _validar_agenda_medico tenta detectar checando agendaNome, mas:

  • Se agendaNome vier vazio, a validação aceita (só confia no agenda_id da request).
  • Se agendaNome vier presente mas sem o primeiro nome esperado, rejeita.
  • Em clínicas grandes com agendas mal nomeadas (agendaNome="Sala 2"), o primeiro nome nunca bate e tudo vira FALHA_CONSULTA.

Impacto em números

No Medcenter, hoje, com 7 médicos e primeiros nomes manualmente curados para serem únicos: zero incidentes. Mas:

  • Adicionar o 8º médico é risco de incidente.
  • Adicionar clínica nova (com mais médicos) herda a fragilidade.
  • A trava-no-boot protege contra duplicatas óbvias, mas não contra sobrenomes ambíguos ou casos como "Ana C./Ana M.".

Mitigações já em vigor

  1. _validate_unique_first_names() no boot (Medcenter): trava se alguém adiciona médico com primeiro nome duplicado.
  2. Multi-palavra na frente ("JOAO PAULO" antes de "JOAO"): permite dois médicos com João se um deles tiver nome composto.
  3. Sobrenomes únicos como fallback ("BRITO", "ALMEIDA", "SIQUEIRA"): cobrem caso em que o paciente só cita sobrenome.
  4. Validação de agendaNome no response: última linha de defesa contra OTIMUS retornar agenda errada.

Solução recomendada (evolução)

Opção A — forçar escolha numerada no menu

No system prompt, em vez de aceitar "Dra. Joana" como input livre, forçar o paciente a escolher de uma lista numerada:

Temos os seguintes medicos disponiveis para agendamento:
1) Dra. Joana Silva (ultrassom geral)
2) Dr. Rennan Santos (doppler)
3) Dra. Suelem Oliveira (obstetricia)

Digite o NUMERO do medico desejado.

A tool passa a receber medico_id (int) em vez de nome. Lookup vira int → dict direto — impossível ambiguidade.

Trade-off: menu numerado é menos natural na conversa.

Opção B — id canônico interno

Manter input livre, mas expor ao LLM IDs canônicos:

MEDICOS = {
    "joana":    {"agenda_id": 58, "nome_display": "Dra. Joana Silva"},
    "rennan":   {"agenda_id": 59, "nome_display": "Dr. Rennan Santos"},
    "suelem":   {"agenda_id": 57, "nome_display": "Dra. Suelem Oliveira"},
}

No prompt, instruir o LLM a usar medico_slug="joana" na chamada da tool. lookup = MEDICOS[slug].

Vantagem: sem parsing. Se o LLM errar e passar string qualquer, KeyError claro → FALHA_CONSULTA com motivo específico.

Opção C — Levenshtein com threshold e rejeição em caso de ambiguidade

Usar fuzzy matching com recusa explícita quando há empate:

from rapidfuzz import process

def resolver_medico(nome: str) -> dict | None:
    candidatos = [(p["medico"], p) for p in PROCEDIMENTOS]
    matches = process.extract(nome, [c[0] for c in candidatos], limit=3)
    best, score, _ = matches[0]
    second, score2, _ = matches[1] if len(matches) > 1 else ("", 0, 0)
    if score < 85:
        return None                   # nenhum suficientemente próximo
    if score2 >= 85 and (score - score2) < 5:
        return None                   # ambiguidade — rejeita
    return dict(candidatos)[best]

Trade-off: mais robusto mas requer dependência adicional e threshold de score empírico.

O que NÃO fazer

  • ❌ Baixar o threshold do match para "ser mais tolerante".
  • ❌ Silenciar a validação de agendaNome.
  • ❌ Remover a trava-no-boot.
  • ❌ Acumular sobrenomes genéricos (SILVA, SOUZA, SANTOS) em _DOCTOR_KEYWORDS achando que vai melhorar o match.

Ação recomendada agora

  1. No Medcenter: manter como está, mas antes de adicionar novo médico, revisar _DOCTOR_KEYWORDS e validar os nomes únicos.
  2. Em projetos novos: adotar Opção A (menu numerado) ou Opção B (slug canônico). Nunca repetir o padrão "primeiro nome único por convenção".
  3. No futuro: migrar Medcenter gradualmente para Opção B durante uma janela de manutenção tranquila.

Relato histórico da decisão

Segundo os logs e comentários do código, o padrão foi escolhido por:

  1. Simplicidade — uma lista de keywords.
  2. Acreditar que "poucos médicos, nomes conhecidos, vai ser ok".
  3. Não havia biblioteca fuzzy instalada.

Na prática, foi adequado para 7 médicos numa clínica. Não escala.