elosys README

The project's README from github.com/YuriRDev/elosys. As one markdown file: README.md.

Elosys — cruzamento de dados públicos de políticos brasileiros

Elosys monta, a partir de fontes oficiais e públicas, uma base de dados consolidada de candidatos e eleitos brasileiros (2014–2026), cruzada por CPF/CNPJ, para investigar relações entre políticos e levantar indícios de padrões suspeitos (doação circular, fracionamento de doações, empresas de fachada, enriquecimento incompatível).

Indício não é prova. Nada aqui é acusação. O sistema gera sinais de alerta para serem checados por quem tem competência para isso (Ministério Público, TCU, Receita, COAF). Todo dado exibido aponta para o arquivo público de onde saiu, e qualquer pessoa pode re-baixar esse arquivo e conferir o hash.

Backend: pipeline de coleta em Python + banco SQLite (elosys.db). Frontend: app Next.js só-leitura em /web — busca candidato e mostra a ficha completa com a fonte de cada campo.

Já coletado: 1,63 M candidaturas (consulta_cand 2014–2026, ~1,18 M pessoas), 1,04 M CNPJs de campanha, 5,16 M doações (R$ 26,7 bilhões, quem doou pra cada CNPJ e quanto), 9,47 M despesas contratadas (R$ 16,2 bilhões, pra quem a campanha pagou), 10,89 M pagamentos (R$ 18,85 bilhões, regime de caixa — quando o dinheiro de fato saiu), 841 mil redes sociais declaradas por candidatura (Facebook/Instagram/X/site, obrigatório desde 2018), 25,5 mil sanções federais CEIS/CNEP (474 empresas sancionadas já cruzam com doador/ fornecedor de campanha) e 3,25 M bens declarados (R$ 445,5 bilhões, patrimônio no registro de candidatura). Banco ~11,4 GB.


Rodar com banco de dados

O jeito mais rápido: baixar o banco já pronto (~11,4 GB) e abrir o app web, sem rodar nenhum crawler.

1. Baixar o banco

Arquivo elosys.zip (2,84 GB; descompactado, elosys.db tem ~11,4 GB). Os dois links trazem o mesmo arquivo:

Link Origem
archive.org/details/elosys Internet Archive
huggingface.co/datasets/YuriRDev/elosys Hugging Face

SHA-256 do elosys.zip:

96fb819b681752250db0c6cdc62566d1773338547daf6ea524edcd4024b16693

Confira o download antes de descompactar:

sha256sum elosys.zip                    # Linux/macOS/Git Bash
Get-FileHash elosys.zip -Algorithm SHA256   # PowerShell

Descompacte e deixe o elosys.db na raiz do repositório (ao lado deste README). O banco já vem com todas as tabelas e com o índice de busca por nome de doador/fornecedor.

2. Executar localmente

Requisitos: Node.js 20.9+ e git.

git clone https://github.com/YuriRDev/elosys.git
cd elosys
# coloque o elosys.db baixado aqui, na raiz

cd web
npm install
npm run dev        # http://localhost:3000

O app é só leitura: abre o .db em modo readonly e nunca escreve nele. Para apontar outro caminho:

ELOSYS_DB_PATH=/caminho/para/elosys.db npm run dev

Build de produção: npm run build && npm run start.


Reconstrói tudo a partir das fontes oficiais. Demora: os arquivos do TSE são grandes (a prestação de contas passa de 1 GB por ano), o download vai para dados_tmp/ e é apagado depois de processado, e algumas etapas levam de minutos a horas. Tenha ~15 GB livres em disco.

Requisitos: uv (instala Python 3.12 e as dependências).

uv sync
uv run elosys init-db --db elosys.db

Etapa 1 — coletar as fontes

Cada crawler é independente e rewrite-only: apaga as tabelas que possui e as reconstrói. Sem --years, processa todos os anos suportados.

uv run elosys tse-candidates --db elosys.db      # candidaturas 2014–2026 (TSE consulta_cand)
uv run elosys tse-accounts   --db elosys.db      # CNPJ de campanha, doações e despesas (a etapa mais longa)
uv run elosys tse-social     --db elosys.db      # redes sociais declaradas
uv run elosys tse-assets     --db elosys.db      # bens declarados
uv run elosys transparencia-sanctions --db elosys.db   # CEIS/CNEP
uv run elosys transparencia-earmarks  --db elosys.db   # emendas parlamentares

Se um download der HTTP 403 (filtro anti-bot da fonte), baixe o .zip no navegador, coloque em dados_tmp/ com o nome que o log mostrou e rode o crawler de novo: ele usa o arquivo local.

Etapa 2 — enriquecer (incremental, opcional)

uv run elosys receita-cnpj --db elosys.db --limit 500     # cadastro e sócios de CNPJ (BrasilAPI, 1 request por CNPJ)
uv run elosys tse-photo-urls --db elosys.db --limit 500   # foto oficial dos candidatos

Os dois são incrementais (não apagam o que já existe): rode várias vezes para cobrir mais CNPJs/candidatos.

Etapa 3 — índice de busca por nome

A busca por doadores/fornecedores que nunca foram candidatos usa uma tabela FTS5 que não é preenchida por nenhum crawler. Rode depois de cada tse-accounts:

uv run python - <<'PY'
import sqlite3
con = sqlite3.connect("elosys.db")
con.executescript("""
DELETE FROM pessoa_fisica_search;
INSERT INTO pessoa_fisica_search (cpf, name)
SELECT cpf, max(name) FROM (
  SELECT donor_cpf_cnpj AS cpf, donor_name AS name FROM campaign_donation
    WHERE donor_company_id IS NULL AND donor_cpf_cnpj IS NOT NULL
      AND length(donor_cpf_cnpj) = 11 AND donor_name IS NOT NULL
  UNION ALL
  SELECT supplier_cpf_cnpj AS cpf, supplier_name AS name FROM campaign_expense
    WHERE supplier_company_id IS NULL AND supplier_cpf_cnpj IS NOT NULL
      AND length(supplier_cpf_cnpj) = 11 AND supplier_name IS NOT NULL
) GROUP BY cpf;
""")
con.commit()
PY

Etapa 4 — regras de detecção

Rodam sobre o que já foi coletado e geram os sinais de alerta.

uv run elosys rule-disproportionate-expense --db elosys.db
uv run elosys rule-circular-donations       --db elosys.db   # ~25 min na base real
uv run elosys candidate-supplier-partner    --db elosys.db   # precisa do receita-cnpj (quadro societário)

Etapa 5 — camadas opcionais com LLM / X

Precisam de chave em variável de ambiente (nunca commite chaves):

export DEEPSEEK_API_KEY=...                                  # segunda opinião sobre os sinais
uv run elosys ai-review --db elosys.db --limit 100 --order tight

export APIFY_TOKEN=...                                       # posts do X de contas declaradas ao TSE
uv run elosys social-x      --db elosys.db --scope federal
uv run elosys social-review --db elosys.db --limit 5000

Etapa 6 — manifesto e testes

uv run elosys manifest --db elosys.db     # grava manifest.json com URL + SHA-256 de cada fonte
uv run pytest                             # testes (fixtures, sem rede)

Cada crawler é rewrite-only: a tabela passa a conter exatamente os anos que você passou em --years. Para um estado 100% limpo, apague elosys.db e recomece.


Como funciona, em uma frase

Cada fonte de dados tem um crawler independente (elosys/tse/*.py). Rodar um crawler apaga as tabelas dele e reconstrói tudo do zero a partir dos arquivos do governo. O banco é um artefato descartável; a prova de integridade fica no manifest.json, versionado no git.

O que fica no banco

Tabela O que é Crawler
politician_history Uma linha por candidatura por eleição (nome, partido, cargo, UF, situação, resultado, dados de registro) tse-candidates
people Pessoa (chave surrogate; cpf/voter_id como identificadores) — todo JOIN por pessoa passa aqui, nunca por CPF cru compartilhada
companies Empresa por CNPJ (kind: campaign/donor/supplier/sanctioned) compartilhada
campaign_org O CNPJ que cada candidatura abre para a campanha (Receita, natureza 409-4) tse-accounts
campaign_donation Cada doação recebida: quem doou (CPF/CNPJ, nome), quanto, quando, por qual via tse-accounts
campaign_expense Cada despesa contratada: pra quem a campanha pagou, quanto, por qual serviço tse-accounts
campaign_expense_payment Quando cada despesa foi de fato paga (pode ser em parcelas) tse-accounts
social_media Redes sociais/site declarados no registro da candidatura (URL + rede detectada) tse-social
declared_assets Bens declarados no registro da candidatura (tipo, descrição, valor) tse-assets
company_registry / company_partner Data de abertura, situação cadastral e sócios de um CNPJ receita-cnpj (incremental)
sanction CEIS/CNEP: quem está impedido de contratar com o governo ou punido por corrupção transparencia-sanctions
rejected_cpf CPFs descartados por ambiguidade (mesmo CPF em >1 título eleitoral, etc.) e o motivo tse-candidates
source / collection / collection_file / parse Proveniência: de qual órgão, qual URL, qual arquivo, qual hash, qual parser saiu cada linha todos
rule_run / signal / signal_actor / signal_evidence Sinais de alerta gerados por regras de detecção (não vêm de nenhuma fonte — ver ADs/dados_derivados.md) rule-disproportionate-expense, rule-circular-donations
signal_ai_review Segunda opinião de um LLM sobre cada sinal (rotineiro vs. bizarro), com a resposta e o prompt salvos verbatim ai-review (opcional, precisa de DEEPSEEK_API_KEY)
candidate_supplier_partner Candidato que aparece no quadro societário de empresa que recebeu pagamento de campanha (match nome + 6 dígitos do CPF — não determinístico) candidate-supplier-partner

Toda linha de dado tem provenance_id → parse → collection → source. "De onde veio isso?" é um JOIN.

Sinais de alerta (regras de detecção)

Diferente das tabelas acima, rule_run/signal/signal_actor/signal_evidence não vêm de nenhuma fonte — são geradas pelo nosso código sobre os dados já coletados. Mesma disciplina de proveniência: toda linha em signal_evidence aponta pra uma linha real (que por sua vez tem seu próprio provenance_id), e severity só existe como low/medium/high — nunca "confirmado" ou "fraude". Ver ADs/dados_derivados.md.

Primeira regra: disproportionate_expense ("despesa desproporcional") — item tipicamente barato (18 categorias: caneta, adesivo, crachá...) identificado em campaign_expense.description com valor muito acima do normal para aquela categoria: medium a partir de 15x a mediana histórica da categoria, high a partir de 30x (piso de R$ 1.000; categorias com menos de 20 despesas usam o piso fixo de R$ 5 mil / R$ 50 mil). Rodada contra a base real: 28.743 sinais (13.856 high, 14.887 medium). Maior caso: R$ 2.504.200,00 em "PRAGÕES, BIG HAND, PERFURADO, PRAGUINHA, ADESIVO" (9.879x a mediana de R$ 253,50). O TSE não publica quantidade nesse arquivo, só o valor total — a regra não calcula preço unitário. Pode ser lote grande, item não detalhado na descrição, ou erro de digitação — por isso é indício, não prova. A página /sinais/despesa-desproporcional lista quem mais gastou por categoria, como % da receita da campanha e contra a média de candidatos do mesmo cargo e estado.

Segunda regra: circular_donations ("doação circular") — constrói um grafo dirigido (doador → candidato, candidato → fornecedor) sobre a base inteira e usa Tarjan (SCC) + DFS limitado em profundidade (padrão: 5 nós) pra achar ciclos: dinheiro que sai de uma campanha e volta pra mesma cadeia. Rodada contra a base real: grafo de 5,35M nós / 7,66M arestas, 108.400 ciclos encontrados; só viram sinal os que movimentaram mais de R$ 10.000 no total: 38.267 sinais (10.705 high ≤ 3 nós, 27.562 medium). Interface em /sinais/doacao-circular — filtro por severidade, ordenável por valor movimentado ou tamanho do caminho — com link direto pro grafo interativo. Mesma disciplina: indício, não prova — pode ser coincidência de coligação, ressarcimento, ou merecer checagem manual.

Cruzamento: candidate_supplier_partner — candidato que aparece no quadro societário de uma empresa que recebeu pagamento de campanha. O CPF do sócio vem mascarado da Receita, então o match é normalize(nome) == canonical_name E os 6 dígitos visíveis do CPF batendo — não é identidade confirmada (fica fora de people/signal_actor, rotulado "possível"). Ambíguos (2+ pessoas batendo) são descartados. Rodado contra a base real: 533 vínculos possíveis (R$ 197,9 mi movimentados nessas empresas), 66 onde a própria campanha do candidato pagou a empresa dele (gráfica própria imprimindo material de campanha, escritório de advocacia próprio, etc.). Interface em /sinais/socio-fornecedor.

Camada opcional: ai-review (elosys/rules/ai_review.py) — passa os fatos de cada sinal de doação circular / despesa desproporcional pra um LLM barato (DeepSeek) e pergunta: rotineiro ou genuinamente estranho? A resposta, a explicação, os fatos citados e o prompt exato ficam salvos em signal_ai_review (um por sinal+modelo), pra um humano poder conferir o raciocínio do modelo contra os dados. Incremental (não rewrite-only): roda de novo pra revisar mais, --refresh re-revisa. Ordena por valor (--order amount, pega os casos de conta-de-partido que o modelo tende a achar plausíveis) ou por ciclo mais curto (--order tight). Chave via DEEPSEEK_API_KEY no ambiente — nunca commitada. Interface em /sinais/analise-ia. Continua sendo indício — agora com a opinião de uma máquina, que também erra.

Os arquivos de log e auditoria

Cada execução de crawler escreve/atualiza, ao lado do .db:

Além disso, o próprio console é um log: cada crawler imprime a URL, o tamanho e o hash do arquivo, o progresso da leitura linha a linha, e um resumo por ano.

Conceitos de arquitetura (ADs)

As decisões estão em ADs/, uma por assunto. Resumo:

AD Ideia central
confiabilidade.md Não repúdio. Nenhuma linha existe sem apontar para a coleta que a originou. Não guardamos o arquivo bruto — guardamos URL + data + SHA256, e você re-baixa para conferir.
imutabilidade.md Rewrite-only. O banco é reconstruído do zero a cada execução. A garantia contra "editar o passado" é o manifest.json commitado + build determinístico, não triggers nem hash chain.
banco.md SQLite, arquivo único, sem servidor. Convenções de tipo (datas ISO em UTC, dinheiro em centavos, nomes de tabela/coluna em inglês).
identidade.md Identidade é afirmação nossa, não dado da fonte. Match determinístico por título eleitoral e CPF. Na dúvida (CPF ambíguo), não afirma — descarta o CPF e registra o porquê.
politician.md Sem tabela "o político X"; só "a candidatura de X no ano N" (politician_history). CPF mascarado de 2024 tratado por título eleitoral. campaign_org para o CNPJ de campanha.
dados_derivados.md Correlações e flags (disproportionate_expense, circular_donations) também têm proveniência: qual regra, qual versão, sobre quais linhas. Severidade só low/medium/high, nunca "confirmado".

Fontes de dados (todas oficiais/públicas)

Fonte O que traz Estado
TSE — consulta_cand Cadastro de candidaturas ✅ coletado (2014–2026)
TSE — prestação de contas eleitorais CNPJ de campanha; doações e despesas ✅ coletado (CNPJ, doações, despesas contratadas e pagas)
TSE — rede_social_candidato Redes sociais/site declarados no registro (obrigatório desde a Res. 23.610/2019) ✅ coletado (2018–2026)
X/Twitter (via Apify) Posts/replies de contas declaradas ao TSE, filtrados por léxico e triados por LLM (social-x + social-review, ver ADs/dados_derivados.md §1.4) ✅ eleitos federais
TSE — bem_candidato Bens declarados no registro (base do sinal de patrimônio) ✅ coletado (2014–2026)
TSE — foto_cand (DivulgaCandContas, busca por CPF) Foto oficial por candidatura ✅ coletado (parcial, incremental — ver ADs/politician.md §5)
TSE — DivulgaCandContas Certidões criminais ⬜ pendente
Receita Federal (BrasilAPI) Quadro societário de CNPJ, data de abertura, capital ✅ coletado (incremental — não é rewrite-only, ver ADs/politician.md §2.3)
CNJ — DataJud Metadados de processos judiciais públicos ⛔ inviável pela API pública — ver nota abaixo
Portal da Transparência — CEIS/CNEP Empresas/pessoas impedidas de contratar com o governo ou punidas por corrupção ✅ coletado (snapshot diário)
Portal da Transparência — contratos/emendas Contratos, convênios, emendas parlamentares ⬜ pendente
Câmara / Senado — dados abertos Mandatos em exercício, votações, cota parlamentar (CEAP) ⬜ pendente

Não coletamos (protegido / sigiloso): endereço residencial, telefone e e-mail pessoal de candidatos; antecedentes fora de processo público; relatórios do COAF.

Por que "quantidade de processos judiciais por candidato" não dá pra fazer (hoje)

Pesquisamos a API Pública do DataJud (CNJ) especificamente pra isso. Conclusão: os documentos que ela devolve não têm nome, CPF nem CNPJ das partes — só metadado processual (numeroProcesso, tribunal, classe, assuntos, orgaoJulgador, movimentos, datas). O glossário oficial da API (datajud-wiki.cnj.jus.br/api-publica/glossario) não lista nenhum campo de parte — é proposital, por sigilo (Portaria CNJ 160/2020). Sem CPF/nome no índice, não dá pra buscar "todos os processos do candidato X" por essa API; ela só serve se você já sabe o número do processo, ou quer estatística agregada (quantos processos por classe/tribunal no geral), não "quantos processos tem essa pessoa".

A alternativa real seria raspar a consulta processual pública de cada tribunal (TJ/TRF/TRT etc.) individualmente por nome — 90+ tribunais, sem padrão comum de resposta, boa parte também mascara ou omite nome em processos sigilosos, e é exatamente o tipo de fonte "não reprodutível" que já discutimos em ADs/confiabilidade.md §2. Não está no radar de próximos passos por isso — é trabalho de raspagem massivo pra um retorno incerto, não uma tarde de crawler.

elosys (YuriRDev/elosys)

Searching Database…

458 ·GitHub
Что это?Elosys собирает единую базу данных бразильских политических кандидатов (2014–2026) из официальных правительственных источников с перекрёстными ссылками по CPF/CNPJ, чтобы исследовать связи между политиками и выявлять признаки подозрительных схем вроде круговых пожертвований, фирм-пустышек и состояния, несовместимого с заявленным доходом.
Это набор данных или веб-приложение для просмотра?И то и другое: Python-конвейер собирает официальные данные в базу SQLite (elosys.db, ~11,4 ГБ), а приложение Next.js в режиме только для чтения в /web позволяет искать кандидатов и видеть источник каждого поля.
Как это запустить?Скачайте готовый elosys.zip (2,84 ГБ, распаковывается в elosys.db размером ~11,4 ГБ) с Internet Archive или Hugging Face, положите elosys.db рядом с корнем репозитория, затем запустите приложение Next.js на Node.js 20.9+ (npm run build && npm run start для продакшена).
Что нужно, чтобы собрать базу с нуля вместо скачивания?Нужен uv, который установит Python 3.12 и требуемые зависимости, плюс около 15 ГБ свободного места на диске, поскольку исходные файлы TSE могут превышать 1 ГБ за один год.
Есть ли подводные камни при обходе исходных данных с нуля?Некоторые загрузки TSE падают с HTTP 403 из-за антибот-фильтра источника; тогда придётся вручную скачать .zip в браузере, положить его в dadostmp/ с именем файла из лога и перезапустить краулер, чтобы он использовал локальный файл.
Изменяет ли веб-приложение базу при просмотре?Нет, приложение открывает elosys.db в режиме только для чтения и никогда в него не пишет.
Что делает опциональный слой ИИ-проверки?Он отправляет факты за каждым обнаруженным сигналом (круговое пожертвование или непропорциональный расход) в DeepSeek и спрашивает, выглядит ли случай рутинным или действительно странным, сохраняя ответ модели, объяснение и точный промпт в signalaireview для проверки людьми. Требуется переменная окружения DEEPSEEKAPIKEY.
Отмеченный сигнал — это доказательство нарушения закона?Нет. В README прямо сказано, что сигнал это indicium (признак), а не доказательство — серьёзность только низкая/средняя/высокая, никогда «подтверждено» или «мошенничество», и она предназначена для проверки органами вроде Ministério Público, TCU, Receita или COAF.
Как убедиться, что конкретные данные не подделаны?Каждая строка прослеживается по цепочке provenanceid → parse → collection → source, и каждый запуск краулера записывает manifest.json с URL, датой доступа и SHA-256 хешем каждого скачанного файла, коммитя его в git как доказательство того, что и когда было собрано.
Creator
YuriRDev
Size
3.7 MB
Stars
458
Updated
CommitOct 03
Age
1mo
Commits · 90 Days