Ingerir lista oficial sem quebrar: canário, encolhimento e o arquivo que era HTML

UNIFOKAL8 min de leituraProduto e integração

Dado público não é API. O desenho mínimo de um ingestor honesto de listas oficiais: pacote com data, latin1 contra UTF-8, membro certo do ZIP, canário de integridade e guarda de encolhimento.

Quem faz triagem de sanções, de pessoas expostas politicamente (PEP) ou de impedimentos de contratar depende de arquivos publicados por órgãos públicos, não de APIs contratadas. A diferença parece semântica e não é: uma API tem contrato, versão e aviso de mudança; um arquivo publicado tem endereço, o formato de hoje e o calendário de quem publica.

Isso não é crítica ao publicador. A Lei 12.527/2011, a Lei de Acesso à Informação, no artigo 8º, parágrafo 3º, manda que os sítios oficiais possibilitem gravar relatórios em formatos abertos e o acesso automatizado por sistemas externos em formato legível por máquina (incisos II e III), garantam autenticidade e integridade (inciso V) e mantenham tudo atualizado (inciso VI). O Decreto 8.777/2016 define dado aberto e formato aberto no artigo 2º e manda cada órgão publicar seu cronograma de atualização das bases (artigo 5º, parágrafo 2º, inciso III). A obrigação é publicar e manter atualizado, e ela é cumprida. O que a norma não promete é esquema estável, aviso de mudança de coluna ou que o pacote de hoje já exista quando o seu processo acordar. Cada guarda abaixo nasce de um modo de falha observável, e os números saíram de baixar os pacotes oficiais em 29 de agosto de 2026.

O pacote tem carimbo de data, e a data de hoje pode não existir

Os pacotes de sanções e de PEP da Controladoria-Geral da União são endereçados por data no próprio caminho: o dia para sanções, o mês para PEP. Em 29 de agosto de 2026, o pacote do Cadastro de Empresas Inidôneas e Suspensas (CEIS) carimbado com o próprio dia respondia 403 e o do dia anterior entregava 3,5 MB de ZIP. O de PEP de agosto ainda não existia, o de julho entregava 2,5 MB e o de junho já respondia 403: o publicador não guarda histórico.

Daí saem duas regras. A primeira é a janela de tentativa para trás: peça o carimbo de hoje e, se não houver, recue dia a dia até um teto curto, porque atraso de publicação é rotina, não incidente. A segunda é que o baseline é seu: sem cópia de cada lote aceito, não há com o que comparar o lote de amanhã nem para onde voltar.

Repare no erro: pelo RFC 9110, o 403 diz que o servidor entendeu e recusa a requisição (seção 15.5.4), e quem queira esconder o recurso pode responder 404 (seção 15.5.5). Ou seja, "ainda não publicado" e "sem permissão" chegam iguais.

Latin1 aqui, UTF-8 lá fora

O CSV de PEP de julho de 2026 vem separado por ponto e vírgula, com campos entre aspas, CRLF e cabeçalho acentuado em ISO/IEC 8859-1: o byte 0xE7 do "ç" de "Sigla_Função" está na posição 27 e derruba a decodificação UTF-8 já na primeira linha. Lá fora o quadro se inverte: a lista SDN do OFAC, do Tesouro dos Estados Unidos, chega em ASCII puro e sem declaração de codificação, e a especificação XML 1.0 do W3C exige que entidade sem marca de ordem de bytes e sem declaração esteja em UTF-8; a lista consolidada do Conselho de Segurança da ONU declara UTF-8 no prólogo.

A assimetria que importa é de detecção. UTF-8 tem gramática: o RFC 3629, seção 4, define quais sequências de octetos são válidas, então errar para UTF-8 dá exceção na hora. ISO/IEC 8859-1 atribui caractere a todo byte de 0x00 a 0xFF, então errar para latin1 nunca falha: entrega mojibake em silêncio, e o nome acentuado deixa de casar.

Há ainda uma armadilha de rótulo. O Encoding Standard do WHATWG registra "latin1", "iso-8859-1" e até "ascii" como rótulos do windows-1252, e manda decodificar 0x80 como U+20AC, o euro. Em Node.js isso se vê em uma linha: "Buffer.toString('latin1')" devolve U+0080, enquanto "new TextDecoder('latin1').encoding" responde windows-1252. O CSV de PEP de julho tem cinco bytes na faixa em que os dois discordam: escolha a decodificação explicitamente.

Abra o membro certo, e só ele

O ZIP não se lê do começo. A especificação da PKWARE (APPNOTE 6.3.10) descreve, na seção 4.3.6, um arquivo que termina no diretório central, e a seção 4.3.1 exige que todo ZIP contenha o registro de fim desse diretório. Download truncado passa despercebido por quem só olha os primeiros bytes.

Os nomes internos também não seguem o endereço: no pacote de expulsões da administração federal, o caminho fala em CEAF e o arquivo lá dentro se chama "Expulsoes". E há pacotes com vários membros, como o de servidores civis do Executivo federal: quatro CSVs, 84 MB comprimidos que viram 617 MB descompactados. Selecione o membro por padrão explícito, leia em fluxo e falhe alto se a seleção casar com zero membros ou mais de um.

Canário, piso e guarda de encolhimento

Contagem de linhas é sinal fraco: detecta arquivo vazio e não o caso mais perigoso, volume saudável com semântica quebrada, o cenário da coluna renomeada. O mesmo órgão publica com convenções diferentes: o cabeçalho de PEP usa nomes acentuados com sublinhado, o do CEIS usa caixa alta com espaços e traz um erro de digitação oficial no rótulo de abrangência da sanção. Normalizar "espertamente" esse rótulo quebra o casamento: casa-se o que está publicado, não o que deveria estar.

O canário é a contramedida barata: um registro sabidamente listado que o pipeline inteiro precisa achar no lote novo antes de esse lote substituir o anterior. Se a coluna do documento mudar de nome e o extrator devolver campo vazio, o volume continua ótimo e o canário morre.

Duas guardas quantitativas fecham o conjunto. A primeira é o piso de linhas com documento aproveitável: no CEIS de 28 de agosto de 2026, dos 23.704 registros, quatro trazem documento fora de qualquer formato válido e 6.473 começam com zero, então passar por tipo numérico destrói o casamento. A segunda é a guarda de encolhimento: uma lista de sanções que encolhe 15 por cento de um lote para o outro é anomalia até prova em contrário, e a prova é humana. Use a contagem que o publicador declara: o XML do SDN traz data de publicação e total de registros, e no lote de 28 de agosto de 2026 o declarado, 19.321, batia com as entradas do arquivo. Esse mesmo cabeçalho traz um nome de elemento com uma letra faltando, outro lembrete de que se casa o que existe.

Documento nem sempre vem inteiro. Na lista de PEP, os 133.930 registros trazem o CPF mascarado, com só os seis dígitos do meio: quem espera casar por documento completo conclui, por engano, que ninguém está na lista. Mais sobre isso no artigo sobre pessoa exposta politicamente.

O arquivo que era HTML

Falta o achado que amarra tudo. No mesmo domínio de onde sai o pacote, a página humana do conjunto de dados responde 200 com 86 KB de HTML e um caminho inexistente responde 500 também com HTML; o host de arquivos responde 403 com 111 bytes de XML. O ingestor que confia no status e grava o corpo com extensão de ZIP criou um arquivo que não é ZIP, e o erro só aparece adiante: no melhor caso como exceção de abertura, no pior como zero linhas ingeridas.

O RFC 9110 é claro sobre o limite do que um status promete: o 200 indica que a requisição teve êxito (seção 15.3.1), não que a representação é a que você queria. E a seção 8.3 registra que, na prática, nem sempre o servidor de origem é configurado com o Content-Type correto. A validação, portanto, é estrutural: fim de diretório do ZIP, membro esperado, colunas esperadas, piso de linhas úteis, guarda de encolhimento e, por fim, o canário.

Quando uma guarda falha, o lote não entra e a lista antiga continua valendo, com a data de corte visível. É aqui que a engenharia vira conformidade: a Lei 9.613/1998, no artigo 10, inciso III, exige políticas, procedimentos e controles internos compatíveis com o porte e o volume de operações, e a Circular BCB 3.978/2020 detalha esse dever para as instituições autorizadas pelo Banco Central. Controle auditável é dizer, meses depois, com qual versão de qual lista a decisão foi tomada. Por isso o desfecho de fonte velha tem que ser "não sei", encaminhado a revisão humana, e jamais "nada consta". Na UNIFOKAL, por exemplo, consulta que não pôde ser feita contra dado fresco vira pendência explícita, com a data de corte registrada junto da decisão.

Fontes citadas