DataFog acha CPF em nota clínica? Rodei 200 notas e 196 ainda vazaram

O DataFog 4.9.0 (engine regex) cobriu 26 de 26 e-mails, 8 de 133 CPFs e 0 de 120 CNS em 200 notas sintéticas. Depois do redact(), 196 notas ainda mostravam CPF ou CNS inteiro.

Rogério Rodrigues

Rogério Rodrigues

Engenheiro de IA · mestrando em Informática em Saúde (UFSC)

Nota clínica sintética com tarja preta só no e-mail e no fim do telefone, e CPF e CNS circulados em vermelho. À direita: 196 de 200 notas ainda mostram um CPF ou CNS inteiro depois do redact(), DataFog 4.9.0 engine regex.

Rodei o DataFog 4.9.0, na configuração padrão, em 200 notas clínicas sintéticas em português. Ele cobriu 26 de 26 e-mails, 8 de 133 CPFs e 0 de 120 CNS. Depois do redact(), 196 das 200 notas ainda mostravam um CPF ou CNS inteiro.

Código, dados e saída gravada: demos/09-datafog-pii-pt no nursia-research-lab.

Como rodei

  • DataFog 4.9.0, instalado com pip install datafog==4.9.0, Python 3.11, CPU. Engine regex, o padrão. Sem modelo, sem chave.
  • As mesmas 200 notas da demo 06: quatro estilos (evolução, alta, encaminhamento, prescrição), semente fixa, tudo sintético. CPF e CNS passam no dígito verificador, mas são sorteados e não pertencem a ninguém.
  • O corpus tem casos difíceis de propósito. Metade dos CPFs vem sem pontuação (66 de 133).
  • Telemetria do DataFog desligada no script, com DATAFOG_NO_TELEMETRY=1 e DO_NOT_TRACK=1.

O núcleo do teste são duas chamadas (engine é o motor de detecção; span, o trecho do texto onde o dado está):

entidades = datafog.scan(texto).entities
redigido = datafog.redact(texto, entities=entidades).redacted_text

Para cada dado pessoal do gabarito, medi três coisas. Exato: mesmo início, mesmo fim e tipo certo. Coberto: o dado inteiro cai dentro de alguma entidade, de qualquer tipo. É o que importa pra redigir. Vazou: o texto do dado ainda aparece na nota depois do redact().

O que deu

Tipo Suporte Exato Coberto Parcial Vazou após redact
E-mail 26 26 (100%) 26 (100%) 0 0
Data 98 17 (17,3%) 17 (17,3%) 0 81
Telefone 60 0 16 (26,7%) 27 (45,0%) 17
CPF 133 0 8 (6,0%) 0 125
CNS 120 0 0 0 120
Pessoa 292 0 0 0 292
Endereço 58 0 0 0 58

Das 200 notas, todas têm CPF ou CNS. Em 196, pelo menos um dos dois continua inteiro depois do redact().

Onde falha, e por quê

CPF e CNS não existem pro engine regex

O engine regex do 4.9.0 lista sete tipos: EMAIL, PHONE, SSN, CREDIT_CARD, IP_ADDRESS, DATE e ZIP_CODE. Não tem CPF, CNS, nome nem endereço brasileiro. Pessoa e endereço em 0% são o esperado, porque o engine não tem esses tipos.

Os 8 CPFs cobertos foram acidente. Todos estavam sem pontuação, e o DataFog os rotulou como PHONE. Dos 66 CPFs sem pontuação, 8 foram cobertos; dos 67 com pontuação, nenhum.

Telefone vira CEP, e o DDD sobra

Telefone no formato 99123-4567 saiu como ZIP_CODE, 43 vezes nas 200 notas. Com DDD, a entidade pega só o final e o DDD fica no texto: 27 casos parciais. Os 16 cobertos por inteiro são celulares sem DDD. Os 17 que sobram são telefones fixos de oito dígitos, no formato 9999-9999, sem entidade nenhuma.

Data: o regex lê mês/dia

Achou 17 de 98. As 50 datas por extenso, como “3 de março”, não entram. Das 48 numéricas, entraram as 17 cujo primeiro campo é até 12. As 31 com primeiro campo acima de 12, como 24/04/25, passaram batido. O regex lê o formato americano, mês antes do dia.

A primeira nota do corpus não mudou nada

Em results/exemplo.txt, a nota 1 tem CNS, CPF, endereço e dois nomes. Depois do redact(), o texto saiu idêntico.

Falso positivo quase não houve

Nos números que não são dado pessoal (lote, leito, dose, pressão, protocolo), o DataFog marcou 2, ambos em protocolo.

Como ler essa tabela

A coluna “Exato” é a métrica de benchmark: o span e o tipo precisam bater. Ela é severa demais pra decidir se o dado vaza. Um CPF rotulado como telefone, por exemplo, conta como erro no “Exato” e como acerto no “Coberto”, porque a tarja cobre os onze dígitos do mesmo jeito. Por isso a coluna que decide é “Coberto”, e a prova final é “Vazou após redact”: procurei o texto original do dado na nota redigida.

Há um ponto cego. “Vazou” olha o texto inteiro do dado. Num telefone parcial, o DDD fica e o resto sai. Por isso o telefone tem 17 vazados e 27 parciais, em vez de 44 vazados. Dado parcial ainda é informação que sobra, e a coluna “Parcial” existe pra isso.

Para comparar

Na demo 06, o Presidio 2.2.364 padrão com spaCy pt deixou CPF ou CNS passar em 200 das 200 notas. Com o presidio-br, o vazamento foi de 0 em 200. Em CPF e CNS, o DataFog regex fica no mesmo lugar do Presidio padrão. Em nome e data, o Presidio com spaCy foi melhor (recall de 82,2% e 49,0%, contra 0% e 17,3% do DataFog). Nenhum dos dois traz reconhecedor de CPF de fábrica. O Presidio aceita um reconhecedor novo; escrevi como em reconhecedor de CPF no Presidio. Eu não testei como acrescentar um padrão de CPF no DataFog.

O que eu faria

  • Antes de confiar numa biblioteca de anonimização, rode ela no seu corpus sintético e procure o dado na saída. Não confie no nome da entidade.
  • Meça a nota depois do redact(), não só a lista de entidades. Os dois números diferem: a lista de entidades diz o que foi achado, o texto diz o que sobrou.

Limites

  • Só o engine regex. O DataFog tem engines com NER (spaCy, GLiNER) que baixam modelo. Não rodei. O resultado vale pro que vem com pip install datafog, não pra biblioteca inteira.
  • 200 notas sintéticas, uma semente. Nota real tem mais ruído.
  • Mede detecção e o texto depois do redact(). Não mede reidentificação por combinação de campos.
  • A opção locales não ajuda em português: o 4.9.0 rejeita pt e aceita só de, alemão. Vi pela mensagem de erro; a demo não usa a opção.
  • Tempo médio de cerca de 0,2 ms por nota nesta máquina (0,15 a 0,21 ms nas rodadas que fiz).

Fonte e como reproduzir

Depois de clonar o repositório e entrar em demos/09-datafog-pii-pt, são três comandos:

pip install -r requirements.txt
python3 demo.py
python3 -m pytest tests -q

NursIA é pesquisa do PPGINFOS/UFSC, com bolsa FAPESC.

In English

I ran DataFog 4.9.0 (default regex engine) on 200 synthetic Brazilian Portuguese clinical notes. It covered 26/26 emails, 8/133 CPF numbers (all mislabeled as phone) and 0/120 CNS numbers. After redact(), 196 of 200 notes still held a full CPF or CNS.

Conhece alguém que precisa ler isso?

Manda pra essa pessoa. Leva dez segundos e é o que mais ajuda esse trabalho a chegar em quem usa IA com dado de verdade.

Rogério Rodrigues

Rogério Rodrigues

Engenheiro de IA há 20 anos em software, mestrando em Informática em Saúde na UFSC. Escrevo o que rodei: IA com dado de saúde, FHIR e LGPD, com código aberto.

Ficou uma dúvida? Discorda de algo? Me chama.

Ver publicações científicas