Uai, Data

Já tinha ouvido falar, mas somente recentemente aconteceu comigo: passei por uma etapa de processo seletivo que envolvia uma entrevista via áudio do WhatsApp.

Resolvi contar como foi e apontar alguns momentos que me fizeram levantar as sobrancelhas.

Os robôs vão roubar seu emprego (mas talvez não da maneira que está pensando).

Primeiro contato

O processo começa com um e-mail dizendo que o currículo enviado foi selecionado e a primeira etapa é uma entrevista, obrigatória, via WhatsApp. Além dos detalhes, também é enviado um pequeno PDF com uma pequena prévia do que acontecerá a seguir.

O link para iniciar o processo de triagem é simplesmente um atalho para iniciar uma conversa no WhatsApp, tal como é ilustrado na primeira mensagem do PDF. O usuário inicia a comunicação com o robozinho do serviço utilizando uma mensagem com um identificador.

Após o usuário informar um e-mail, nome e sobrenome para cadastro, ele é cadastrado na plataforma e uma mensagem deve ser recebida com suas credenciais (nome e senha) na caixa de entrada.

Como é o próprio usuário que inicia o processo de comunicação com o robô, o controle sobre os dados a seguir são todos dele. Ele pode, por exemplo, pedir para uma pessoa mais experiente iniciar esse procedimento e realizar esta etapa da entrevista totalmente em seu nome.

Pera, como?

Às vezes é um detalhe que pode passar desapercebido ao olhar menos criterioso, mas outro fato que chama a atenção nesta etapa por um motivo: O usuário não escolhe sua senha em momento algum. A senha é gerada no serviço e enviada para o usuário, em texto plano e, em momento algum, ele é notificado para alterá-la. Se quiser até pode, mas a sensação que se passa é durante o processo inteiro é a de que o usuário não deve ser incomodado, e esta etapa não é diferente.

A senha é curta (8 caracteres), sem caracteres especiais e formada somente por letras e números.

Isso é um problema por um motivo relativamente simples: Para enviar uma senha desta maneira — novamente, em texto plano — para um usuário, o serviço deve ter uma maneira de conhecê-la em algum momento. Se o serviço pode ter acesso ao código, outros também podem fazê-lo.

Há várias maneiras para corrigir esse problema, mas algumas que já me deparei ao usar outros serviços são: – A plataforma deve enviar um link para o usuário completar o registro. Esse é aquele caso onde nós recebemos um endereço e nos deparamos com um formulário parcialmente preenchido, e somos levados a completá-lo com a nossa própria senha, e inclusive confirmando-a, digitando mais uma vez . – O serviço pode criar uma senha de uso única, com tempo de vida curto, que é descartada assim que alguém abre o endereço pela primeira vez. Neste momento o usuário é levado a criar a sua própria senha, e a original — aquela gerada pela plataforma — perde sua validade para sempre. – O serviço também pode escolher não lidar com credenciais de acesso de forma alguma, delegando esta tarefa para empresas mais robustas. Essa é a motivação por implementar alternativas de acesso a partir do Login with Google, Login with Apple, e outros.

Em qualquer caso, a única parte interessada em conhecer a senha do usuário deve ser o próprio usuário.

Entrevista de uma pessoa só

Antes mesmo que o e-mail do passo anterior percorra todas as linhas de fibra ótica e fios de cobre, do serviço de entrevistas até a caixa de entrada do e-mail informado, a “entrevista de uma pessoa só” já pode começar.

Uma primeira pergunta, mais genérica, supostamente serve como aquecimento para o entrevistado se soltar. Para o entrevistador (neste caso, algum modelo de inteligência artificial) serve para avaliar a qualidade do áudio recebida através do aplicativo e , de alguma forma, determinar se há possibilidade de transcrever as respostas que irá receber logo em seguida.

A partir deste momento o usuário deve responder quatro questões, sempre recebidas com um singelo “hmmm, entendi!” pelo robô do outro lado.

Suspeito que neste momento o modelo ainda não entendeu nada. Esta resposta é recebida muito rapidamente, literalmente 1 segundo após o envio do áudio, e benchmarks de um modelo famoso para transcrição de áudio (openai-whisper) mostram que a transcrição, mesmo nos hardwares mais potentes do mercado, ainda não chegaram neste patamar de eficiência.

Mas é apenas uma suspeita, um detalhe, e faz sentido para simular uma experiência de “conversa humanizada” (palavras deles, não minhas.)

As perguntas não surpreendem em questão de qualidade, inovação ou criatividade. Não é claro se foram criadas através de algum grande modelo de linguagem, se foram recicladas de processos anteriores, quando ainda eram ôrganicos, ou se passaram por uma revisão por humanos.

Por falar em “entender”...

Alguns pares de perguntas respondidas depois, o candidato recebe um feedback de todas as respostas, e uma promessa de que alguma pessoa de carne e osso irá avaliar seu desempenho e entrar em contato a respeito das próximas etapas.

Não me ocorreu no momento, mas depois eu fiquei pensando o que poderia acontecer se o candidato, por exemplo, gaguejasse, ou tivesse um sotaque muito forte.

Explico: sou mineiro e, como todo mineiro, tenho um sotaque. Também tenho alguns vícios de linguagem e quem já me ouviu conversando, dando oficinas ou assistiu algumas aulas, sabe que minha cabeça pode trabalhar mais rápido que minhas cordas vocais. Não tenho uma dificuldade de fala de fato (e o processo prevê tratamentos especiais para candidatos que necessitem de melhor acessibilidade, o que é um ponto positivo, caso sejam eficientes), mas ao somar tudo isso fiquei curioso sobre o que o modelo utilizado pudesse ter entendido a partir do que eu falei.

Aquele site em que o candidato é registrado (com os problemas já apontados) possui, em verdade, as perguntas feitas pela plataforma, os áudios enviados pelo usuário e o feedback recebido. Não tem, entretanto, o que o modelo de inteligência artificial entendeu do que foi falado. Eu sou mineiro, mas acho que minha demografia deve ser bem representada nas bases de treinamento. Isso também pode ser dito a respeito das pessoas manauaras? Os modelos usados entendem bem o(s) sotaque(s) nordestino(s), ou só aquele das novelas da Globo?

Como resolver a hipotética questão em que o candidato fala “X”, o modelo entende “cheese” e dá um feedback potencialmente irrelevante e equivocado? Os modelos de speech-2-text não são especialmente diferentes de outros de sua categoria e também podem cometer alucinações (é um problema conhecido, documentado e pesquisado ), assim como os modelos de geração de texto. A imprensa já reportou a respeito do problema no passado.

Resumindo o problema: Podem estar sendo utilizadas ferramentas que, devido à sua natureza probabilística, gerar resultados que se distanciam da realidade.

A ausência da transcrição gerada pelo modelo de speech-2-text — que serve então de elemento de entrada para um outro modelo generativo — é capaz de multiplicar erros que são eternizados até sabe-Deus-lá-quando.

A possibilidade de serem produzidas informações incorretas é prevista na política de privacidade da Diga.Aí.

Os consumidores da plataforma são, de acordo com o documento, “aconselhados a verificar e confirmar qualquer informação”, mas como seria possível fazer essa verificação se não há o que verificar? É possível ouvir os arquivos de áudio originais, é verdade, mas se é necessário fazer esse passo em razão da incerteza do que foi gerado, qual exatamente foi o ganho de produtividade realizado por utilizar essa plataforma?

A falta de transparência gera dúvidas. Dúvidas, em um momento delicado, evoluem para desconfiança. Não fica claro se é somente o candidato que não recebe a transcrição, ou se a empresa que utiliza a ferramenta também não consegue ter acesso a esses dados. De toda forma, é pouco agradável para quem depende do processo, ainda que de diferentes maneiras, ter que confiar cegamente no resultado gerado.

Feedback do feedback

O processo de seleção de novos colaboradores é, sem sombra de dúvidas, algo que deve receber a nossa atenção: Para os candidatos em busca da sua primeira oportunidade é uma verdadeira tarefa sisifiana — e potencialmente traumatizante. Para algumas empresas, uma necessidade. Para outras, um serviço em potencial e oportunidade de disrupção e destaque.

Em relação ao tratamento dos dados, fora o que já foi levantado, vale apontar a verdadeira teia de conexões que é necessária seguir para tentar entender o que é feito neste caso:

  1. Meu processo se iniciou a partir do contato da empresa CI&T
  2. A CI&T contrata os serviços e produtos da Diga.Aí
  3. O produto da Diga.Aí é desenvolvida pela Edutalent. A Diga.Aí, inclusive, remete a este site para, enfim, ter o acesso à política de privacidade e proteção de dados.

  1. O site da Edutalent, finalmente, faz menções discretíssimas a respeito do armazenamento de dados pessoais, como o os dados de voz do candidato e seus dados pessoais, como nome, sobrenome, e-mail.

Incluir este documento na mensagem enviada originalmente evitaria o trabalho de ter que procurar essas informações e visitar três sites diferentes. Garantiria também, eu imagino, um melhor consentimento.

Enfim

O hype em cima da inteligência artificial generativa representa, de fato, uma oportunidade de melhoria de processos. Mas deve vir acompanhada de transparência e boas experiências por todas as partes envolvidas em cada etapa.

O desenvolvimento de aplicações que surfam nesta onda aproveitam da facilidade de criar com IA e criar usando IA. De repente, ficou tão rápido apresentar uma produto que quase não dá pra pensar em questões como:

  • O que exatamente estou tentando vender?
  • Que tipo de problema eu estou resolvendo?
  • Esse produto funciona ou só roda?

No caso, senti falta de falar com um ser humano do outro lado. Parte importante da comunicação é interpretar sinais não verbais do receptor da mensagem e também poder expressar os meus. Não senti que a oportunidade de utilizar todas essas ferramentas foi bem aproveitada.

Ainda, a tarefa de escrever um texto é difícil e cansativa justamente por ter que reduzir toda essa dimensionalidade às poucas letras do alfabeto. Transmitir, novamente, todos esses sinais somente com a fala, sem a possibilidade de riscar o que foi dito, pensar a respeito por vários minutos, consultar outros recursos... Novamente, qual é exatamente o ganho ao optar pela via da entrevista via áudio do WhatsApp?

A aplicação de um modelo de inteligência artificial para transcrever áudios e conduzir uma entrevista de emprego é novo, mas não acho inovador. É preciso criatividade e interesse para ultrapassar o clássico algoritmo que detecta palavras-chave da vaga com as palavras-chave incluídas no currículo, caso contrário chegamos à mais uma roda reinventada (e que, convenhamos, nunca girou muito bem para os Sísifos que buscam uma oportunidade de conseguir um emprego bom por aí).

Este texto, claro, é somente um relato de experiência e não deve ser visto como uma forma de difamar determinada empresa ou quem faz parte da sua cartela de clientes. Todas as eventuais críticas foram acompanhadas de propostas de melhoria e foram feitas de boa-fé, acreditando na capacidade de aperfeiçoamento de todas as partes.

Deve existir algum tipo de lista tarefas e serviços que mais geram buscas nas mãos de pessoas que tentam usar a internet atualmente para realizar tarefas criativas. Imagino que ela deve ser algo parecido com:

  1. Como gerar gerar imagem de IA rápido — o prazo tá curto e o chefe tá em cima!
  2. Como baixar e editar vídeos do YouTube (e outros sites)
  3. Modelos diferentões para o Canva
  4. Como retirar fundo de fotos e marca d'água

Eu não me interesso muito por algumas dessas tarefas, mas o item 2 é algo que me pedem frequentemente.

Ao mesmo tempo em que eu utilizo (e recomendo!) as ferramentas yt-dlp para fazer o download de mídias de uma infinidade de sites diferentes, e ffmpeg para manipular qualquer tipo de formato de vídeo, também concordo que a interface delas não são muito amigáveis. Elas foram criadas para serem utilizadas a partir da linha de comando, e o terminal pode ser um pouco assustador, ou simplesmente o usuário final não está com tempo pra aprender a usá-lo.

A interface do yt-dlp no Terminal pode ser um pouco assustadora

O usuário pode querer fazer o download de mídias de sites por uma infinidade de motivos, que vão desde montar uma biblioteca local de músicas e clipes para uma viagem que ficará sem internet — e, portanto, sem a opção de utilizar serviços de streaming — até o interesse em compartilhar algum tipo de conteúdo em grupos de Whatsapp e Telegram.

Seja lá o motivo que for, o usuário fará tudo que for necessário e estiver ao seu alcance para satisfazer esta necessidade a partir do momento que ela surgir. Isso inclui utilizar serviços e aplicativos potencialmente perigosos e intrusivos. No fim das contas este post é uma tentativa de criar um serviço de redução de danos.

De lá para cá

O serviço que eu tenho recomendado atualmente para download de vídeos é o cobalt. Com uma interface minimalista — mas configurações poderosas — é difícil errar o processo de descarregar uma mídia para a máquina do usuário.

O processo é tão simples e direto que quase não tem o que ser dito a respeito dele: basta colar a URL da página que contém o vídeo e, sendo de um site ou rede social válido, o download deve começar em alguns instantes. É só isso. Sem criação de contas, sem propagandas intrusivas e sem dor de cabeça.

Ao gosto do freguês

Um dos motivos para fazer download desses conteúdos é o desejo de editá-lo: cortar e manter somente os pedaços que interessam, emendar junto a outros vídeos e fazer uma sequência, extrair frames para ilustrar um projeto ou converter para um formato específico — converter um filme em .mkv para .mp4 e fazê-lo ser aceito na TV, por exemplo.

Não há uma alternativa melhor do que o ffmpeg nesse momento: se você não está usando ele diretamente, provavelmente está utilizando uma ferramenta criada a partir dele, como o HandBrake ou o Kdenlive. Não deixe de conferir esses dois, apesar de tudo.

A interface do ffmpeg, entretanto, como mencionei, não é das mais amigáveis. Quem o conhece sabe o quanto é poderoso e flexível, mas essa flexibilidade tem um custo alto: a sua linguagem é, sem medo de exagerar, terrível, e depende de passos como definir variáveis de ambiente e consultar a documentação oficial para tentar montar o quebra cabeças necessário para fazê-lo funcionar.

Pois recentemente encontrei o projeto ffmpeg-online (código fonte disponível em xiguaxigua/ffmpeg-online) que elimina metade do problema: agora o ffmpeg pode ser executado diretamente no navegador!

Basta escolher o arquivo (clicando no botão ou arrastando para o lugar correto), definir os parâmetros que definem como o arquivo será manipulado e definir o nome do arquivo de saída.

Por exemplo, para cortar um vídeo grande e transformá-lo em um vídeo menor, o seguinte comando é utilizado no programa original:

ffmpeg -i VIDEO_ORIGINAL.mp4 -ss 00:00:00 -t 00:00:05 -vcodec copy -acodec copy VIDEO_FINAL.mp4

No site, por outro lado, as caixinhas de opções ficam configuradas na seguinte ordem

ffmpeg
-i
{VIDEO_ORIGINAL.mp4} \\ preenchido automaticamente depois de selecionar o arquivo
-ss 00:00:00 -t 00:00:05 -vcodec copy -acodec copy
VIDEO_FINAL.mp4

Neste caso, os números após os valores -ss e -t correspondem, respectivamente, ao início e o fim, no arquivo original, que deverão ser extraídos para gerar o novo vídeo.

Outro exemplo útil é a conversão de um vídeo .mkv em um arquivo de áudio. A gente pode fazer assim:

ffmpeg
-i
{VIDEO_ORIGINAL.mkv}
-vn -acodec libmp3lame -ab 192k -ar 44100
AUDIO_FINAL.mp3

Após dizer que está utilizando o ffmpeg, o ChatGPT e outros modelos são úteis para descobrir qual o comando certo — pelo menos para tarefas mais simples.

Nos shoppings, aeroportos e portões de universidades ainda podemos encontrar ofertas de brindes duvidosos ao fechar um pacote de assinaturas de revistas que deixaram até de ocupar lugar em salas de espera por aí. Na era da internet essas ofertas ainda existem, mas seu alcance é maior, enquanto o esforço para alcançar o potencial consumidor é significativamente menor.

Lembro que na primeira década dos anos 2000 fiquei fascinado com a possibilidade de receber itens de graça pela internet. Existiam serviços que enviavam adesivos, pulseiras e até — dizem por aí, já que espero a minha até hoje — bolas de golfe. Também foi assim que tive a minha primeira experiência com Ubuntu, através dos CDs enviados pela Cannonical. Pedi uns 10 e distribuí para alguns amigos nerds na escola que usaram também.

Quase 20 anos depois, a prática de enviar amostras grátis de produtos para consumidores que se cadastram em sites continua. O ecossistema, porém, é diferente. No lugar do boca a boca dos fóruns de antigamente, entraram os links patrocinados. No lugar do funcionário explorado e ignorado pelos caminhantes enquanto segura sua prancheta, a própria pessoa preenche e entrega, com a velocidade das teclas do computador, os próprios dados para Deus-sabe-lá-quem.

O post de hoje é sobre dados pessoais, golpes virtuais e saber a hora de parar.

Tão estranho que parece golpe

Desde 2020 até o final de 2024 atuei como cientista de dados em uma agência de notícias especializada em combater a desinformação. Durante esse tempo, analisando dados de redes sociais durante uma pandemia mundial, uma eleição presidencial e duas eleições municipais no Brasil, fui me familiarizando um pouco com as marés dos discursos e identificando tendências daqueles que praticam e se especializaram a arte do migué.

Desinformar vai além da óbvia atividade política-partidária e alcança vários espaços em nossa vida, chegando até ao campo da fraude e golpes. Golpes não são novidade, assim como também não é novidade também a desvirtuação da frase “Todo dia saem um malandro e um otário de casa. Quando se encontram, dá negócio.”. Hoje poderia ser “Todo dia um malandro e um otário se conectam na internet...

E foi pensando nisso que, em outubro de 2024, em plena campanha eleitoral em que o migué atingiu níveis nunca antes vistos, enquanto percorria a biblioteca de anúncios da Meta, percebi a chamada para um site que oferecia produtos grátis mediante cadastro. A prática é corriqueira e ocorre quase sempre da mesma maneira: o produto aparece em uma loja falsa, o usuário é levado a uma página que imita a identidade visual de uma grande operadora de pagamentos e e ele só precisa pagar o frete. É Mentira.

Primeiros passos

Com esse protocolo em mente, comecei a vasculhar a sua estrutura tentando entender quais pistas foram deixadas para trás que me permitiriam enteder como o golpe funcionava, como seria possível prová-lo e, quem sabe, identificar autoria.

O primeiro lugar que visito, na esmagadora parte dos casos, é arquivo sitemap.xml na raiz do domínio. Em outras palavras, isso significa digitar www.nomedosite.com.br/sitemap.xml na barra de endereços e conferir se consigo chegar em uma página com conteúdo. Caso exista, tenho — literalmente — em mãos um mapa do site.

Esse arquivo é utilizado, dentre outras formas, para facilitar o trabalho de indexadores de conteúdo da Internet — os robôs do Google, Bing e outros, que vasculham a internet e guardam os registros do que veem em seus bancos de dados o utilizam para agilizar seus algoritmos.

Em sites fraudulentos este é um bom ponto de partida de investigações porque podemos , por exemplo: – Encontrar dados interessantes, como os nomes de usuários registrados na plataforma – Encontrar páginas que não estão linkadas em nenhuma outra página. – Verificar datas de criação e modificação das páginas, o que permite criar uma linha do tempo da atividade do site.

Escrevi, então, rapidamente um script em Python para raspar as informações das páginas do sitemap:

import requests
from bs4 import BeautifulSoup as bs
from datetime import datetime
import pandas as pd

def extract_info_from_xml(page:str)-> None:
    r = requests.get(page)
    soup = bs(r.content, features='xml')
    # A página raiz do sitemap distribui as demais páginas em tags `sitemap`
    # O mesmo não ocorre dentro das páginas filha, que distribuem os outros endereços
    # em tags `url`
    rows = soup.find_all('sitemap')
    rows.extend(soup.find_all('url'))
    
    for row in rows:
        date = row.find('lastmod').get_text()
        date = datetime.strptime(date, '%Y-%m-%dT%H:%M:%S+00:00')
        URL = row.find('loc').get_text()
        info.append((date, URL))
        if URL.endswith('.xml'):
            extract_info_from_xml(URL)

info = []

extract_info_from_xml('https://www.endereco_do_site.com.br/sitemap.xml')
info = pd.DataFrame(info, columns=['date', 'URL'])
info.to_csv('URLs-and-dates.csv', index=False)

Esse script acessa, recursivamente, os endereços contidos no mapa de páginas e armazena o endereço e data de modificação em um pequeno arquivo CSV. Simples e útil. Finalmente podemos checar como o site foi se modificando com o tempo:

Os gráficos ilustram a quantidade de modificações ao longo do tempo. Em durante quatro anos a quantidade de modificações aumentou gradativamente, com picos em junho de 2022 e primeiro semestre de 2024.

Até aí tudo bem

Alguns detalhes até agora: – A página mais antiga, de acordo com o sitemap, é a termos-de-uso, criada em Julho de 2021. – O primeiro pico de alterações aconteceu somente em Junho de 2022, com 69 modificações num dia. e 42 no dia anterior. – A maioria dessas modificações, na verdade, ocorreram na forma de criações de páginas dentro de um diretório chamado newsletter. Essas páginas foram batizadas com o nome — presumo — das pessoas que se cadastraram para receber informações sobre o produto ou empresa. – No primeiro semestre de 2024 o site foi encontrado por um robô que, sem contexto, tentou utilizar o formulário de inscrição da newsletter como caixa de mensagens e criou vários registros por engano. Desconsiderando as pegadas desse robô, o último ano de modificações é ilustrado da seguinte maneira: – Desconsiderando a quantidade de alterações criadas pelo robô que enviou SPAMs, somente um pico de alterações aconteceu durante 2024

Por essa eu não esperava

A base de páginas tem 7994 registros, sendo que 7732 deles pertencem ao diretório de newsletters com nomes de pessoas. Em outras palavras: só 262 páginas compõem o site “de verdade”. Simplesmente por excluir essas páginas desinteressantes meu trabalho acabou de ser reduzido em 96%!

Uma das primeiras páginas que me chamou a atenção foi uma que possuía o endereço parecido com www.endereco_do_site.com.br/exporta_dados, com algumas estatísticas do produto ofertado na propaganda encontrada na biblioteca de anúncios e um grande botão escrito “Exportar”.

Detalhe importante: Os dados exportados são nada menos do que 57 mil linhas de dados pessoais de indivíduos que inseriram ali suas informações e o produto que gostariam de receber, assim como eu fiz com os meus, 20 anos atrás, para receber uma bola de golfe de graça.

Comecei a escrever funções para obfuscar os dados mais sensíveis para serem apresentados no blog...

def obfuscate_email(email:str) -> str:
    try:
        at_pos = email.index('@')
        username = email[:at_pos]
        domain = email[at_pos:]
        new_email = (
            username[:2] # aproveita os dois primeiros caracteres do username 
            + ('*' * (len(username) - 2)) # cria a quantidade necessárias de asteriscos para igualar o tamanho original do username
            + domain # junta com o dominio do e-mail novamente
            )
        return new_email
    except AttributeError as not_string:
        return None

df["Email"] = df["Email"].apply(obfuscate_email)

E quais foram os meses com mais usuários registrados...

subs_month = (
    df
    .groupby(pd.Grouper(
        key='Data',
        freq='ME')
        )
    .agg(
        unique_CPF=pd.NamedAgg(
            column='CPF',
            aggfunc='nunique'),
        unique_address=pd.NamedAgg(
            column='CEP_Numero',
            aggfunc='nunique'
        )
        )
)

subs_month = subs_month.drop(
    subs_month[
        (subs_month['unique_CPF'] == 0) &
        (subs_month['unique_address'] == 0)
    ].index
)

Além disso: – Números de telefones foram checados utilizando o Whatsapp – Endereços foram visitados no Google Maps. – Calculei quantas ocorrências se registraram com dados diferentes, mas pediram que o produto fosse enviado para o mesmo endereço – Verifiquei se os CPFs inseridos passavam por algum tipo de validação

Até que a ficha do que estava fazendo finalmente caiu: O que uma era uma vez uma suspeita de golpe se tornou uma certeza de exposição de dados pessoais sensíveis.

Não há dúvidas de que se eu entrasse em contato com alguma dessas 57 mil (!) pessoas e tentasse verificar estes dados, estaria colocando-as em situação complicada, afinal de contas estaria pedindo para elas repetirem o erro de compartilharem suas informações pessoais novamente. Eu também estaria em risco, pois poderiam me achar o comportamento suspeito — e com razão — e me denunciarem.

Neste momento a melhor decisão, na minha opinião, é entender o lugar que o analista ocupa. Se não houve permissão para obter esses dados, fechar os arquivos e conferir esporadicamente se as estatísticas do site se alteravam é a melhor opção.

Amarrando as pontas soltas

Um último passo para tirar o peso da consciência a respeito da existência do golpe foi a verificação do domínio utilizando a ferramenta whois do Registro.Br. O domínio possui final .com.br, então os dados das pessoas responsáveis estão disponíveis para consulta.

Checando o domínio dos e-mails (corporativos) associados e o Linkedin das pessoas, todas apontam ao detentor da marca de fato.

Ainda em Dezembro de 2024 uma versão resumida deste post foi enviada ao Ministério Público Federal em forma de Manifestação, incluindo os links relevantes e arquivos que comprovam o vazamento. Tão logo eu receba uma notificação de que o site e os dados das pessoas envolvidas forem tornados inacessíveis eu considerarei editar este texto e incluir informações faltantes.

Esse é um caso que ilustra. de forma clara, como o vazamento de dados pessoais não é culpa do indivíduo que abriu mão deles em troca de um benefício qualquer. Também é: – Do time de pessoas que planejou uma campanha publicitária que normalizou a ideia de abrir mão dos dados pessoais. – Como não é a primeira e única vez que este tipo de campanha aconteceu, há a necessidade de se questionar a respeito da cultura deste tipo de campanha que permanece. – Do time que construiu a plataforma de forma apressada e não praticou a higiene necessária. – Do time jurídico que resumiu a Lei Geral de Proteção de Dados (LGPD) em poucos artigos e orientou a empresa a “apenas focar em evitar multas”.

Não era e — acredito que — não foi uma fraude em nenhum momento. Foi descaso.

No segundo semestre de 2024 pude concluir o MBA de Jornalismo de Dados do Instituto Brasileiro de Ensino, Desenvolvimento e Pesquisa (IDP). Parte dos requisitos para a finalização do curso era a criação de uma reportagem jornalística, em equipe, a partir da orientação de um professor orientador.

A equipe da qual eu fazia parte optou, após outras tentativas — fracassadas por falta de tempo, dados compatíveis, ou ambos —, em tentar obter informações sobre possíveis privilégios que certos grupos empresariais teriam recebido após realizarem doações para campanhas de prefeituras do país em 2020.

“É fácil, é só...”

A lógica da proposta era relativamente direta. Utilizando Python deveríamos obter:

E em seguida, de posse de ambas as bases, imaginamos, é possível cruzá-las e:

  • Identificar quem são as pessoas que compõem sociedades empresariais
  • Que foram declaradas como doadores em campanhas de candidatos que se tornaram vitoriosos nos pleitos municipais.
    • Em resumo: identificar os indivíduos que estão em ambas as bases ao mesmo tempo.
  • Através de pesquisas em reportagens e documentos públicos, publicados entre 2020 e a data de finalização do trabalho, analisar o relacionamento dos entes públicos e privados que selecionamos.

Infelizmente cometi o erro de também considerar esse processo todo como simples.

O destino aparentemente se viu obrigado, assim como faz com todas as pessoas que se consideram diante de um problema de rápida solução, a me colocar no meu lugar de humildade novamente. Esse post explica o porquê de eu estar errado no meu primeiro momento e os passos necessários para encontrar uma solução.

Não é bem assim

Os dados das principais bases que foram usadas no desenvolvimento deste projeto são disponibilizadas em formatos de arquivo de texto, com valores separados por ;. No caso dos arquivos de sócios de empresas disponibilizado pela Receita Federal o arquivo ainda conta com uma modificação da extensão do arquivo, mas que em nada muda o processo de leitura.

Amigos não deixarem amigos exportarem para csv. Infelizmente não sou amigo de nenhum servidor destes órgãos, logo tive que lidar com os arquivos em sua forma original.

Caso fosse um projeto a ser mantido em produção constante, este seria um momento propício para inserir estes dados em um sistema de gerenciamento de bases de dados mais robusto, como o PostgreSQL, ou até mesmo utilizar soluções mais simples, como o Sqlite. Essa decisão, entretanto, demandaria mais tempo e linhas de código a serem escritas, logo foi descartada.

Do HD para RAM

Este momento contou com a preocupação de nosso orientador, que temia pela quantidade de memória RAM da máquina que poderia ser consumida ao carregar todas as bases ao mesmo tempo para que fizesse o cruzamento.

Meu computador pessoal provavelmente não sofreria com este problema, mas considerando que a metodologia deveria ser útil para que outros jornalistas e pesquisadores conseguissem replicar as instruções, a eficiência do código deveria ser levada em consideração também.

Para contornar estes problemas, alguns artifícios foram adotados:

Seleção de colunas

Após uma breve análise do conteúdo de cada coluna é possível escolher somente aquelas que serão úteis para a análise que se deseja realizar. O Pandas — biblioteca do Python que me acompanha em 9 de 10 casos necessários para manipular dados tabulares — permite essa construção facilmente utilizando o parâmetro usecols dentro da função read_csv().

Fazendo um paralelo com SQL seria o mesmo que escrever uma consulta utilizando

SELECT A, B, C
FROM table;

Assim obtemos:

df_socios = pd.read_csv(
    ...
    names=[
        "cnpj_basico", "identificador_socio", "nome_socio",
        "cpf_socio", "qualificacao_socio", "data_entrada_sociedade",
        "pais", "cpf_representante_legal", "nome_representante_legal",
        "qualificacao_representante", "faixa_etaria"
        ],
    usecols=[
      "cnpj_basico", "cpf_socio",
      "data_entrada_sociedade", "nome_socio"
      ])

e também

df_extratos_candidatos = pd.read_csv(
    ...
    usecols=[
        "NR_CNPJ_PRESTADOR_CONTA", "DS_CARGO_PRESTADOR_CONTA", "SG_PARTIDO",
        "NM_PRESTADOR_CONTA", "TP_PESSOA", "DT_LANCAMENTO", "NR_DOCUMENTO",
        "VR_LANCAMENTO", "NR_CPF_CNPJ_CONTRAPARTE", "NM_CONTRAPARTE"])

E neste ponto vale um acender um alerta: a base de sócios disponibilizada pela Receita Federal não possui cabeçalhos, razão pela qual é necessário utilizar também o parâmetro names com os nomes dos campos como descrito pelo dicionário de dados para a função.

Dividir para conquistar

Ler o arquivo em partes e processar pequenos pedaços de informações antes de retornar ao escopo geral pode ser uma boa ideia caso o arquivo original seja suficientemente grande a ponto de ocupar grande parte da memória RAM e impossibilitar manipular os dados nos passos seguintes.

Um fator crucial na nossa análise foi o de que estávamos interessados em analisar somente grupos já pré estabelecidos: 1. candidatos à prefeitura (excluem-se, portanto, candidatos à vereadores) 2. sócios de empresas que fizeram doações à campanhas daqueles.

Desse modo foi possível diminuir consideravelmente a quantidade de linhas que compõem a tabela final de prestação de contas.

Novamente, se em SQL teríamos algo parecido com

SELECT (
  NR_CNPJ_PRESTADOR_CONTA,
  DS_CARGO_PRESTADOR_CONTA,
  SG_PARTIDO,
  NM_PRESTADOR_CONTA,
  TP_PESSOA,
  DT_LANCAMENTO,
  NR_DOCUMENTO,
  VR_LANCAMENTO,
  NR_CPF_CNPJ_CONTRAPARTE,
  NM_CONTRAPARTE
  )
FROM extratos_candidatos
WHERE (TP_PESSOA = "1") AND (DS_CARGO_PRESTADOR_CONTA = "PREFEITO")
;

... para que sejam retornados somente os dados de extratos que nos interessa.

Convertendo para a sintaxe do Pandas chegamos em:

colunas = [
    "NR_CNPJ_PRESTADOR_CONTA", "DS_CARGO_PRESTADOR_CONTA", "SG_PARTIDO",
    "NM_PRESTADOR_CONTA", "TP_PESSOA", "DT_LANCAMENTO",
    "NR_DOCUMENTO","VR_LANCAMENTO", "NR_CPF_CNPJ_CONTRAPARTE", "NM_CONTRAPARTE"
  ]

selecionados = []

# Neste ponto o pandas irá gerar "mini dataframes" com a quantidade de linhas estipuladas no parâmetro chunksize
# Esses mini dataframes (nomeados "chunks" neste exemplo) passarão por uma filtragem simples
# O resultado de cada uma dessas filtragens é, enfim, adicionado à lista "selecionados"."""

for chunk in df_extratos_candidatos = pd.read_csv(usecols=colunas, chunksize=500_000):
  
    filtrado = chunk[
        (chunk[TP_PESSOA == "1"])
        &
        (chunk[DS_CARGO_PRESTADOR_CONTA == "PREFEITO"])
        ]
    selecionados.append(filtrado)

df_extrados_filtrados = pd.concat(selecionados)

Dados preenchidos por humanos e dados omitidos

As informações usadas nesta análise são derivadas de dados oficiais, visto que foram carregadas diretamente dos repositórios de órgãos públicos federais. Mesmo assim possuem características que demandam cautela durante seu tratamento.

Partindo-se da pressuposto de que os dados societários têm uma boa confiabilidade — já que são derivados dos registros de empresas — eles foram considerados o equivalente ao padrão ouro dentro desse contexto: os dados de doadores seriam submetidos à verificação perante a eles, e não o contrário. Isso pela simples razão de que a maneira mais fácil de ter resultados inconsistentes em uma análise é introduzir o fator humano.

Inserir dados em determinado sistema é uma etapa delicada da alimentação de um banco de dados, e quando feito por humanos, os motivos das possíveis catástrofes que podem ser encontradas variam desde o simples erro de digitação até a aplicação de regras pouco definidas e documentadas — e que resultam em variações entre equipes ou de tomadas de decisão entre o dia atual e o anterior.

Isso será ilustrado com os os dados de doadores das campanhas mais à frente.

Apesar da alta qualidade dos dados da Receita Federal, por razões de privacidade, o órgão omite parte do Cadastro de Pessoa Física (CPF) resultando em um dado no formato ***NNNNNN**. É importante ressaltar esse fato porque, em função do espaço limitado de possibilidades para gerar um número de CPF válido, aliado com a quantidade de dígitos ocultados, torna possível que surjam “colisões” durante o cruzamento entre as duas bases. Em outras palavras: o analista deve ficar atento para que duas pessoas diferentes, — cuja única semelhaça entre si seja a mera coincidência de que os mesmos números componham a parte interna de seus respectivos CPFs — não sejam consideradas como um mesmo indivíduo.

Duas medidas em relação aos nomes das pessoas foram tomadas para garantir que a variabilidade de dados fosse reduzida: a normalização dos nomes e o cálculo da similaridade.

A normalização de valores de nomes foi feita a partir de uma função simples:

def normalize_str(string: str) -> str:
    """
    Função auxiliar para remover os acentos e outros caracteres de nomes de prefeitos.
    Adaptado do livro Python Fluente (Luciano Ramalho).
    """
    import unicodedata

    try:
        normalized = unicodedata.normalize("NFKD", string)
        string = "".join([c for c in normalized if not unicodedata.combining(c)])
        string = string.casefold()
        return string
    except TypeError:
        return "error"

df_socios['nome_socio'] = df_socios['nome_socio'].apply(normalize_str)

O cálculo de similaridade, por outro lado, foi feita a partir de outra função presente na biblioteca padrão do Python. Como é uma função simples, a aplicação via lambda foi suficiente:

from difflib import SequenceMatcher

df_socios_extratos['distance'] = df_socios_extratos.apply(
        lambda x: SequenceMatcher(None, x['NM_CONTRAPARTE'], x['nome_socio']).ratio(),
        axis=1)

A documentação oficial pode ser consultada para maiores detalhes sobre o cálculo utilizado.

Neste momento foi possível obter uma ideia melhor de como a base se comportou após o cruzamento, assim como a escala do problema das colisões entre pessoas com partes internas do CPF semelhantes.

Tabela que mostra alguns registros após o cruzamento das bases. São registros de nomes que não são iguais entre si, mas provavelmente se referem a mesma pessoa e se diferenciam somente por uma abreviação indevida ou omissão de sobrenomes.]

A distribuição da similaridade entre os nomes das duas bases é ilustrada por uma curva bastante semelhante à uma curva de sino. Há um um pico substancial nos valores que indicam correspondência exata entre nomes.]

Ficou claro que um cruzamento simples não seria suficiente para gerar resultados satisfatórios nesta análise, visto que a maior parte dos registros resultaram em colisões entre indivíduos que possuem nomes bastante diferentes. O histograma acima ilustra bem o problema: O cruzamento dos banco de dados com base nos CPFs — parcialmente omitidos — dos indivíduos resultou em uma grande parcela de relacionamentos criados entre sujeitos com pouca similaridade entre seus nomes.

A partir de análises de amostragens de diferentes percentis de similaridade decidiu-se que o índice de semelhança entre nomes acima de 0.8 foi suficiente para, com boa margem de segurança, admitir que os resultados correspondiam à mesma pessoa. Assim: – As diferenças a partir deste ponto (similaridade > 0.80) são, em sua absoluta maioria, frutos de abreviações ou omissões de partes do nome dentro da base de doações do TSE. – Os resultados abaixo deste patamar (similaridade < 0.80) foram, por outro lado, considerados como pouco confiáveis e fruto de colisões indesejadas e, portanto, descartados.

Conclusões

Utilizando a biblioteca memory_profiler foi possível medir o impacto das decisões tomadas em direção à melhorar ao gerenciamento de memória.

Avaliando os algoritmos na minha máquina com 64GB de memória RAM os resultados obtidos são:

  • O pico de maior utilização de memória ocorreu ao carregar o arquivo de extratos dos candidatos.

    • O consumo de memória atingiu o pico de 1213 MiB (cerca de 1.3 GB).
    • Considerando que o arquivo original tem 1.42 GB em disco e não foram possíveis fazer filtros significativos em relação a ele, este já era um resultado esperado.
  • Em relação a base de dados societários da Receita Federal, o pico de consumo ocorreu em 724.2 MiB (cerca de 759 MB).

    • Considerando que a base em seu formato original em disco possui 2.32 GB, houve um consumo de memória pouco menor que 1/3 da base original.

Em ambos os casos o programa parece ser capaz de ser executado em basicamente qualquer máquina minimamente moderna. Assim, considero que as preocupações do orientador — que se tornaram as minhas também — foram satisfeitas.

A partir deste momento os dados foram enviados para os colegas repórteres que produziram a reportagem apresentada para a banca avaliadora. A ferramenta CruzaGrafos, mantida pela Abraji, também foi utilizada para enriquecer a análise nesta etapa.

Finalmente, todos os arquivos foram disponibilizados em um repositório, assim como a reportagem final.