Do FTP do DATASUS ao seu prompt SQL.
Quinze etapas, cada transformação justificada, com exemplos antes-e-depois. Leia uma vez e você saberá exatamente o que o pipeline mudou nos dados brutos — e o que não tocou.
- 01
Origem: o FTP público do DATASUS
Os microdados públicos de saúde brasileiros são publicados pelo Ministério da Saúde em um servidor FTP público e anônimo: ftp.datasus.gov.br. Cada subsistema (SIHSUS = internações hospitalares, SIM = mortalidade) mantém seus arquivos mensais ou anuais em um diretório fixo. O pipeline não faz scraping — ele lista o diretório, seleciona arquivos que casam com um padrão, e baixa apenas os que estão dentro do intervalo de datas pedido pelo usuário.
- 02
Análise do nome do arquivo
Os nomes de arquivo codificam UF e período. SIHSUS usa RDUFYYMM.dbc — por exemplo, RDSP2401.dbc significa São Paulo, janeiro/2024. SIM é anual e bifurcado por revisão da CID: DOSP2023.dbc é CID-10 (1996+, raiz de 8 caracteres); DORSP80.dbc é CID-9 (1979–1995, raiz de 7 caracteres). O parser distingue pelo comprimento da raiz, não pelo prefixo — verificar "começa com DOR" silenciosamente descartaria todos os óbitos CID-10 de RJ, RN, RO, RR e RS.
- 03
Descompressão: DBC → DBF
DBC é uma compressão proprietária do DATASUS sobre o veterano formato de tabela DBF. Historicamente era preciso usar ferramentas Windows-only (TabWin) para descomprimir. O pipeline usa a biblioteca Python pura datasus_dbc, então toda a cadeia roda em qualquer plataforma.
- 04
Carga: DBF → DuckDB de staging
Cada DBF é transmitido para uma conexão DuckDB em memória nova como tabela de staging. Duas colunas são adicionadas aqui: source_file (auditoria) e uf (extraída do nome do arquivo). Colunas string têm caracteres invisíveis (tabs, quebras de linha, NULs) removidos e são trimadas.
- 05
Limpeza: caracteres invisíveis e strings vazias
Linhas brutas de DBF frequentemente têm whitespace e caracteres de controle que parecem normais em um editor de texto, mas quebram joins e checagens de unicidade.
Antes Depois "SP\t\n\r" "SP" "" (string vazia) NULL " 04 " "04" - 06
Conversões de tipo: TRY_CAST
Toda coluna numérica e de data passa por TRY_CAST para que uma única linha malformada não aborte a importação inteira — valores inválidos viram NULL e um aviso é registrado. Colunas inteiras incluem idade, dias de permanência, contagens. Colunas float incluem campos monetários como VAL_TOT.
- 07
Parsing de datas — cinco formatos, em ordem
Datas em arquivos do DATASUS não são consistentes. O parser tenta cinco formatos em ordem: YYYYMMDD, DDMMYYYY, DMMYYYY (formato de 7 dígitos exclusivo do SIM, onde o dia não tem zero à esquerda), YYYY-MM-DD, e por último uma conversão genérica.
Subsistema Bruto Parseado SIHSUS "20200131" 2020-01-31 SIM "1012023" 2023-01-01 (DMMYYYY) Qualquer "2020-01-31" 2020-01-31 - 08
Normalização do sexo
Cada subsistema codifica sexo diferentemente. O pipeline colapsa ambos em um código comum M/F/I (indeterminado) para que consultas cross-subsistema não precisem tratar cada tabela como caso especial.
Subsistema Bruto Normalizado SIHSUS 0 / 1 / 3 I / M / F SIM 1 / 2 M / F SIM M / F (já textual) M / F - 09
Raça e cor (RACACOR)
RACACOR carrega as mesmas cinco categorias do IBGE em ambos os subsistemas, mas com códigos numéricos diferentes. O pipeline mapeia para o rótulo legível em português.
Subsistema Código Rótulo SIHSUS "01" "Branca" SIHSUS "02" "Preta" SIM "1" "Branca" SIM "2" "Preta" Ambos "03/3" "Parda" - 10
Arrays de causa de óbito (SIM)
O SIM armazena cadeias de códigos CID-10 para cada linha do atestado de óbito (linhaa, linhab, linhac, linhad, causabas, linhaii) como strings delimitadas por asterisco. O pipeline parseia em arrays VARCHAR[] tipados, para que o SQL possa usar UNNEST e operadores de array diretamente.
Bruto Array "*A01*J128" ['A01', 'J128'] "*B342" ['B342'] "" ou "*" [] (vazio) - 11
Idade codificada (SIM)
O SIM empacota a unidade e o valor da idade em um único campo de 3 dígitos, onde o primeiro dígito é a unidade (1=minutos, 2=horas, 3=meses, 4=anos, 5=>100 anos). O pipeline decodifica em idade_valor (número) e idade_unidade (texto).
Bruto Valor Unidade "403" 3 anos "512" 12 >100 anos (i.e., 112) "307" 7 meses - 12
Enriquecimento geográfico (join IBGE)
Ambos os subsistemas armazenam um código IBGE de 7 dígitos para o município (munic_res no SIHSUS, codmunres no SIM), mas sem o nome legível da cidade. O pipeline faz LEFT JOIN com uma tabela de referência IBGE embutida para adicionar o nome da cidade, o estado (já conhecido) e a macrorregião.
Antes Depois munic_res = 3550308 municipio_res = "São Paulo", uf = "SP", regiao = "Sudeste" munic_res = 3304557 municipio_res = "Rio de Janeiro", uf = "RJ", regiao = "Sudeste" - 13
Armazenamento: Parquet particionado
A tabela transformada é gravada em {data_dir}/datasus_db/{subsystem}/uf={UF}/{filename}.parquet usando particionamento Hive. Particionar por UF significa que uma consulta filtrando por um único estado lê apenas os arquivos daquele estado. Compressão ZSTD mantém o footprint em disco pequeno sem prejudicar a velocidade de leitura.
- 14
Interface de consulta: VIEW DuckDB
Os arquivos Parquet são expostos como uma única tabela virtual por subsistema (sihsus, sim). A view faz um glob com partition pruning (read_parquet('datasus_db/sihsus/uf=*/**.parquet')), então as varreduras automaticamente eliminam partições quando a cláusula WHERE filtra por UF ou por ano.
- 15
Consultas customizadas — sua vez
Na página de Consulta você pode escrever qualquer SQL que quiser sobre os dados limpos, enriquecidos e particionados. O botão # ao lado de cada coluna gera com um clique uma consulta GROUP BY que inclui a % population de cada valor — visualizar distribuições sem precisar digitar.