Resolver médico por nome¶
O agente IA recebe o nome do médico do paciente em linguagem natural
("Dr. João", "Dra. Sabrina", "o Mauricio Siqueira", "o ortopedista").
O cliente OTIMUS precisa transformar isso em um agenda_id numérico.
Esta é a armadilha #1 do padrão OTIMUS
Ver Problema: primeiro nome do doutor antes de implementar.
Estratégia canônica: lookup estrito por prioridade¶
Não use fuzzy matching. Use uma lista ordenada de keywords, em ordem de prioridade:
- Multi-palavra primeiro (ex:
"JOAO PAULO"). - Primeiros nomes únicos.
- Sobrenomes garantidamente únicos como fallback.
_DOCTOR_KEYWORDS = [
# Multi-palavra (testar antes de primeiro nome)
("JOAO PAULO", 6),
# Primeiros nomes — TODOS únicos entre os N médicos
("MAURICIO", 0),
("VALMIR", 1),
("JURIADYSON", 2),
("REGINA", 3),
("SABRINA", 4),
("CHRISTIANE", 5),
# Sobrenomes 100% únicos (fallback se primeiro nome não bateu)
("BRITO", 5), # só Christiane tem Brito
("ALMEIDA", 6), # só João Paulo tem Almeida
("SIQUEIRA", 0), # só Mauricio tem Siqueira
]
def buscar_procedimento_por_medico(nome_medico: str) -> dict | None:
nome_norm = _normalize(nome_medico)
for keyword, idx in _DOCTOR_KEYWORDS:
if keyword in nome_norm:
return PROCEDIMENTOS[idx]
return None
Regra de exclusão de sobrenomes compartilhados¶
Não colocar sobrenomes que mais de um médico tem
Ex: se Regina e Sabrina compartilham sobrenome CUNHA, não
coloque "CUNHA" em _DOCTOR_KEYWORDS. A primeira ocorrência ganha
silenciosamente e o paciente vai com a médica errada.
Da mesma forma DALCOL aparece em Juriadyson e Sabrina — excluído.
Validação na inicialização¶
Trave o boot se alguém adicionar médico com primeiro nome duplicado:
# scheduling.py — executado na importação do módulo
def _validate_unique_first_names() -> None:
seen = set()
for p in PROCEDIMENTOS:
first = _normalize(p["medico"]).split()[0]
if first in seen:
raise RuntimeError(
f"ERRO CRITICO: primeiro nome duplicado '{first}' em PROCEDIMENTOS. "
f"Atualize _DOCTOR_KEYWORDS e _validar_agenda_medico para diferenciar."
)
seen.add(first)
_validate_unique_first_names()
Vantagem: erro aparece no boot, não em produção no meio de um agendamento.
Estratégia do CLINFETO (agenda_id direto)¶
O CLINFETO tem um lookup mais simples porque cada médico tem um agenda_id
próprio e mapeia diretamente:
# agendamento.py:54-70 (CLINFETO)
_DOCTOR_KEYWORDS = [
("RENNAN", 59),
("PRISCILA", 54),
("RAFAELLA", 55),
("SUELEM", 57),
("JOANA", 58),
]
_AGENDA_FIRST_NAME = {
59: "RENNAN",
54: "PRISCILA",
55: "RAFAELLA",
57: "SUELEM",
58: "JOANA",
}
def resolver_agenda(nome_medico: str) -> int | None:
norm = _normalize(nome_medico)
for keyword, agenda_id in _DOCTOR_KEYWORDS:
if keyword in norm:
return agenda_id
return None
Validação pós-resposta (agendaNome)¶
A keyword acertou o agenda_id certo, mas o OTIMUS às vezes retorna a
agenda errada (por bug ou cache). Revalide o campo agendaNome da resposta:
# scheduling.py:157-188 (MEDCENTER) — versão explicada
def _validar_agenda_medico(dias: list[dict], medico_esperado: str) -> bool:
"""Confirma que a agenda retornada é do médico certo.
- Compara pelo PRIMEIRO NOME (ou multi-palavra 'JOAO PAULO')
- Aceita se agendaNome estiver ausente (só filtro por agenda_id vale)
- Rejeita se agendaNome presente e divergente
"""
medico_norm = _normalize(medico_esperado)
if not medico_norm:
return False
if "JOAO PAULO" in medico_norm:
check_token = "JOAO PAULO"
else:
check_token = medico_norm.split()[0] # primeiro nome único
for dia in dias:
for agenda in dia.get("agendas", []):
agenda_nome_raw = agenda.get("agendaNome", "")
agenda_nome = _normalize(agenda_nome_raw)
if not agenda_nome:
continue
if check_token not in agenda_nome:
logger.error(
"VALIDACAO FALHOU: agendaNome='%s' nao contem '%s'",
agenda_nome_raw, check_token,
)
return False
return True
Caso "sem preferência de médico"¶
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"))
Se sim, itera todos os médicos que oferecem o procedimento até achar um com horários.
O que NÃO fazer¶
| Anti-padrão | Por quê |
|---|---|
Usar difflib.get_close_matches / rapidfuzz |
Match probabilístico dá falso positivo silencioso |
for m in medicos: if nome_medico in m["medico"]: return m |
"Ana" bate com "Ana Claudia" e "Ana Maria" |
| Só primeiro nome sem validar unicidade | Escala mal — dois "João" e acabou |
Sobrenome genérico (SILVA, SOUZA, OLIVEIRA) como keyword |
Nenhum é único — ambiguidade garantida |
Não validar agendaNome no response |
OTIMUS já retornou agenda errada em produção — dá bug grave |
Caminho de evolução (para escalar)¶
O padrão atual trava em ~10 médicos por clínica. Se uma clínica nova precisa de mais:
- Na conversa, forçar o paciente a escolher da lista numerada (não aceitar texto livre para médico).
- Passar para a tool um
medico_idcanônico (ex:medico_3) em vez do nome. O lookup viraint → dictdireto. - Manter o
_validar_agenda_medicopara defesa.
Ver o problema de escalabilidade.