datasus-etl
Como funciona Visão técnica

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

    AntesDepois
    "SP\t\n\r""SP"
    "" (string vazia)NULL
    " 04 ""04"
  6. 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.

  7. 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.

    SubsistemaBrutoParseado
    SIHSUS"20200131"2020-01-31
    SIM"1012023"2023-01-01 (DMMYYYY)
    Qualquer"2020-01-31"2020-01-31
  8. 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.

    SubsistemaBrutoNormalizado
    SIHSUS0 / 1 / 3I / M / F
    SIM1 / 2M / F
    SIMM / F (já textual)M / F
  9. 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.

    SubsistemaCódigoRótulo
    SIHSUS"01""Branca"
    SIHSUS"02""Preta"
    SIM"1""Branca"
    SIM"2""Preta"
    Ambos"03/3""Parda"
  10. 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.

    BrutoArray
    "*A01*J128"['A01', 'J128']
    "*B342"['B342']
    "" ou "*"[] (vazio)
  11. 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).

    BrutoValorUnidade
    "403"3anos
    "512"12>100 anos (i.e., 112)
    "307"7meses
  12. 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.

    AntesDepois
    munic_res = 3550308municipio_res = "São Paulo", uf = "SP", regiao = "Sudeste"
    munic_res = 3304557municipio_res = "Rio de Janeiro", uf = "RJ", regiao = "Sudeste"
  13. 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. 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. 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.