Normalização¶
Pequeno conjunto de utilitários que todo projeto OTIMUS usa. Sem eles, a comparação de nomes é quebrada (acentos, case, espaços extras).
_normalize — texto para comparação¶
import unicodedata
def _normalize(s: str) -> str:
"""Remove acentos e converte para UPPERCASE.
Uso: comparações de nomes de médicos, procedimentos, convênios.
"""
if not s:
return ""
nfkd = unicodedata.normalize("NFKD", s)
ascii_text = "".join(c for c in nfkd if not unicodedata.combining(c))
return ascii_text.upper().strip()
Antes / depois:
| Input | Output |
|---|---|
"Joana Silva" |
"JOANA SILVA" |
"Saúde Caixa" |
"SAUDE CAIXA" |
" Priscila " |
"PRISCILA" |
"Dr. João Pãulô" |
"DR. JOAO PAULO" |
_only_digits — extrair dígitos de CPF/telefone¶
Antes / depois:
| Input | Output |
|---|---|
"123.456.789-00" |
"12345678900" |
"+55 (99) 9 9999-9999" |
"5599999999999" |
"" / None |
"" |
Datas — helpers bidirecionais¶
def _yyyymmdd_to_ddmmyyyy(s: str) -> str:
y, m, d = s.split("-")
return f"{d}-{m}-{y}"
def _ddmmyyyy_to_yyyymmdd(s: str) -> str:
d, m, y = s.split("-")
return f"{y}-{m}-{d}"
_normalize_date — aceita várias entradas¶
LLM às vezes devolve 15/04/2026, outras vezes 15-04-2026, outras
2026-04-15. Unificar para ISO:
import re
def _normalize_date(date_str: str) -> str:
"""Converte datas em várias formas para YYYY-MM-DD (ISO)."""
if not date_str:
return date_str
s = date_str.replace("/", "-")
# DD-MM-YYYY -> YYYY-MM-DD
if re.match(r"^\d{2}-\d{2}-\d{4}$", s):
dd, mm, yyyy = s.split("-")
return f"{yyyy}-{mm}-{dd}"
# Já é ISO?
if re.match(r"^\d{4}-\d{2}-\d{2}$", s):
return s
return date_str # deixa como veio; parseador downstream pode explodir
Timezone¶
Sempre em America/Sao_Paulo:
import zoneinfo, datetime
_TZ_BR = zoneinfo.ZoneInfo("America/Sao_Paulo")
def _now_br() -> datetime.datetime:
return datetime.datetime.now(_TZ_BR)
def _today_br() -> datetime.date:
return _now_br().date()
Usar em qualquer operação que envolva "hoje" ou "agora" — incluindo
filtros de slot passado, data_inicio da consulta, etc.
Hora — canonizar¶
Slice fixo em [:5] pega HH:MM. Se o OTIMUS ou o LLM mandar segundos, é
descartado com segurança.
Normalização de nome próprio do paciente¶
Nos projetos atuais não normalizamos o nome que vai para o cadastro (deixamos como o paciente digitou). Isso preserva acentos e capitalização correta no prontuário.
# certo
nome = input_paciente.strip()
# errado (virá tudo em caixa alta no OTIMUS)
nome = _normalize(input_paciente)
Validação de CPF (básica)¶
Projetos atuais não validam CPF (só garantem 11 dígitos). Se quiser adicionar:
def _valid_cpf(cpf: str) -> bool:
cpf = _only_digits(cpf)
if len(cpf) != 11 or cpf == cpf[0] * 11:
return False
# ... algoritmo módulo 11
return True
Em produção, preferimos não validar algoritmicamente: CPF "estranhos" devem falhar no OTIMUS e transferir para humano.
Onde colocar¶
Todos esses helpers ficam em scheduling.py (ou agendamento.py) como
funções privadas (_nome). Não criar módulo separado — overhead
desnecessário.