Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
17e7eda
feat(br_ms_sinasc): adiciona flow de carga anual, sem schedule
DaviMacielCavalcante Sep 8, 2026
a409106
fix: aplicando a sugestão do rabbit
DaviMacielCavalcante Sep 8, 2026
e29e5ca
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 9, 2026
4d3d9e7
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 9, 2026
e202124
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 9, 2026
a9c2ceb
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 9, 2026
c2f9811
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 9, 2026
6e20832
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 9, 2026
0bd5323
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 9, 2026
117db1a
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 9, 2026
c911bc9
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 9, 2026
96bd1c7
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
7e292ee
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
fa60f2f
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
0957a32
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
3bc92af
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
8df17fa
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
37f25f6
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
1b24c77
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
5398c77
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
a7857a7
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
e464fd4
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 10, 2026
dc4744d
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 11, 2026
d944932
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 11, 2026
8a4a7e9
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 11, 2026
7cb9493
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 11, 2026
fc16efc
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 11, 2026
a78d885
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 11, 2026
600f602
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 11, 2026
a2a4ef5
chore(br_ms_sinasc): deixa o flow no padrão da sim
DaviMacielCavalcante Sep 11, 2026
ed887bf
doc: acrescentando especificações no readme do conjunto
DaviMacielCavalcante Sep 11, 2026
a358cc2
Merge branch 'main' into pipeline/br_ms_sinasc
mergify[bot] Sep 12, 2026
70f551e
chore(br_ms_sinasc): flow no padrão da sim, com lista de anos
DaviMacielCavalcante Sep 12, 2026
bf0ee31
Merge branch 'pipeline/br_ms_sinasc' of https://github.com/basedosdad…
DaviMacielCavalcante Sep 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions models/br_ms_sinasc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,47 @@ Além da anomalia de município descrita acima, alguns campos naturalmente apres

---

## Linhas sem município de residência

As partições carregadas antes de 2024 não incluem as linhas com `id_municipio_residencia` nulo. A limpeza atual (`pipelines/datasets/br_ms_sinasc/utils.py`) as inclui, com o campo nulo.

Contagens de 2018/RJ:

```sql
select
count(*) total,
countif(id_municipio_residencia is null) res_nulo,
countif(id_municipio_nascimento is null) nasc_nulo,
countif(id_municipio_mae is null) mae_nulo
from basedosdados.br_ms_sinasc.microdados
where ano = 2018 and sigla_uf = 'RJ'
```

| origem | total | res_nulo | nasc_nulo | mae_nulo |
|---|---|---|---|---|
| produção | 220.481 | 0 | 3 | 41.542 |
| limpeza atual sobre o `.dbc` da fonte | 220.499 | 18 | 3 | 41.554 |

As 18 linhas de diferença são as de `id_municipio_residencia` nulo; 12 delas também têm `id_municipio_mae` nulo.

Contagens de 2024, partição carregada pelo ETL local, que inclui essas linhas:

```sql
select ano, sigla_uf, count(*)
from basedosdados.br_ms_sinasc.microdados
where ano = 2024 and sigla_uf in ('AC', 'RJ')
group by 1, 2
```

| sigla_uf | produção | limpeza atual |
|---|---|---|
| AC | 13.101 | 13.101 |
| RJ | 163.722 | 163.722 |

Cada ano reprocessado pelo flow passa a incluir as linhas ausentes daquele ano; os demais seguem sem elas.

---

## Política geral de qualidade

- **Sem imputação sintética**: nenhum valor é gerado ou estimado para substituir ausências. Dados faltantes são representados como `null`.
Expand Down
10 changes: 10 additions & 0 deletions models/br_ms_sinasc/code/br_ms_sinasc_etl.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
"""
br_ms_sinasc — Microdados do SINASC
====================================

**Código legado. Não é mais executado.**

A carga da tabela passou para o flow do Prefect em
``pipelines/datasets/br_ms_sinasc/``, que baixa, limpa e materializa a série
inteira. Este arquivo fica como registro de como os anos anteriores foram
processados — em particular os de 2021 a 2024, que ele produziu. Duas
diferenças de comportamento em relação ao flow estão documentadas em
``pipelines/datasets/br_ms_sinasc/README.md``.

Processamento dos microdados do Sistema de Informações sobre Nascidos Vivos
(SINASC/DATASUS).

Expand Down
111 changes: 111 additions & 0 deletions pipelines/datasets/br_ms_sinasc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# br_ms_sinasc — pipeline

Carga dos microdados de nascidos vivos do SINASC/DATASUS, do FTP até a
materialização em `basedosdados.br_ms_sinasc.microdados`.

Contexto da base, anomalias conhecidas e decisões de tratamento estão em
[`models/br_ms_sinasc/README.md`](../../../models/br_ms_sinasc/README.md).

## Sem schedule, por quê

O DATASUS republica o SINASC uma vez por ano, sem data fixa, e revisa anos já
fechados. O flow é deployado sem `deploy_schedules`: o deployment existe, aceita
execução avulsa e não dispara sozinho. Para armar depois, basta acrescentar a
lista de crons em `flows.py`.

## Só o diretório definitivo

A fonte serve os anos fechados em `SINASC/1996_/Dados/DNRES/` e o ano corrente
em um diretório preliminar à parte. O flow lê apenas o definitivo, como fazia a
carga local, e a tabela não tem coluna que distinga a origem — diferente de
`br_ms_sim`, que carrega os dois e marca `dado_preliminar`.

Passar a carregar o preliminar exige acrescentar a coluna ao modelo e ao
dicionário antes de mexer aqui.

## Parâmetros

| Parâmetro | Padrão | Efeito |
|---|---|---|
| `anos` | vazio | Vazio pega o ano mais recente da fonte. Preenchido é backfill (`[2018, 2019, 2020]`): o flow pula o poll e não mexe no metadado da fonte |
| `materialize_after_dump` | `True` | Sobe para prod e materializa lá |
| `update_metadata` | `True` | Registra a cobertura materializada |
| `force_run` | `False` | Materializa mesmo sem novidade na fonte |

Execução de teste no pool de dev, sem tocar em produção:

```json
{"materialize_after_dump": false, "update_metadata": false, "force_run": true}
```

Os padrões escrevem em **produção**, mesmo saindo do pool de teste.

## Qual ano entra

Sem `anos`, o flow carrega o ano mais recente que existe na fonte, que aqui é o
último ano fechado: o flow lê só o diretório definitivo. Com `anos` preenchido,
carrega a lista inteira numa execução só, um ano de cada vez, e o dbt roda uma
vez no fim — o modelo é `materialized="table"`, então rodar por ano
reconstruiria a série toda a cada ano.

O poll compara esse ano com o fim da cobertura da tabela. Depois que a cobertura
alcança o ano, as execuções seguintes encerram sem carregar nada, e as revisões
que o DATASUS publicar em anos já fechados não entram. Para trazê-las, executar
com `anos` preenchido ou com `force_run`.

## Formato da staging

A staging é CSV desde a carga original. O particionado sai em
`ano=<ano>/sigla_uf=<UF>/data.csv` e sobe com `dump_mode="append"`: reenviar um
ano substitui os arquivos de mesmo nome e preserva o resto da série.
`overwrite` apagaria o prefixo inteiro, com ele 1994 em diante.

**Ressalva para 1994 e 1995.** Esses dois anos ainda vêm do `microdados.csv` que
a carga antiga gravou; de 1996 em diante o arquivo é `data.csv`. Como `append`
não apaga nada e a staging lê todo arquivo do prefixo, recarregá-los deixaria os
dois na partição e o ano sairia em dobro — apagar o velho antes:

```bash
gcloud storage rm --billing-project=basedosdados-dev \
"gs://basedosdados-dev/staging/br_ms_sinasc/microdados/ano=1994/sigla_uf=*/microdados.csv"
```

O flow não alcança esses anos: a fonte os serve em outro diretório
(`SINASC/1994_1995/Dados/DNRES/`, com nomes `DNR<UF><ANO>`), que este código não
lê. Pedi-los em `anos` falha com `nenhuma UF baixada`.

Trocar para parquet exigiria recriar a tabela externa e, com ela, recarregar
toda a série.

## Carga manual

`utils.py` não importa Prefect, então a transformação roda fora do flow:

```python
from pipelines.datasets.br_ms_sinasc import utils

utils.download_table("microdados", 2024)
utils.clean_table("microdados", 2024)
```

## Diferenças em relação à carga local

O flow substitui `models/br_ms_sinasc/code/br_ms_sinasc_etl.py`, com duas
mudanças de comportamento:

- **`id_municipio_mae`** era convertido duas vezes — primeiro de 6 para 7
dígitos, depois cruzado de novo contra o código de 6 —, e o segundo cruzamento
zerava a coluna. A conversão agora é uma só, por valor: código de 6 dígitos
vira 7, o de 7 passa intacto.
- **`data_registro_cartorio`** não estava na lista de datas e chegava à staging
como `DDMMAAAA`, que o `safe_cast(... as date)` do modelo nulifica. Passou a
ser convertida como as demais. O `DNRES` não traz o bloco de cartório em 2018
nem em 2024, então `cartorio`, `registro_cartorio` e `data_registro_cartorio`
saem nulas.

## Pontos de atenção

- `sequencial_nascimento` não é chave: reinicia por lote estadual e repete entre
reprocessamentos. Não há combinação de colunas que garanta unicidade, e o
modelo não tem teste de unicidade.
- O dicionário do conjunto tem staging própria e não é alimentado por este flow.
Empty file.
219 changes: 219 additions & 0 deletions pipelines/datasets/br_ms_sinasc/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
"""
Constantes de br_ms_sinasc.
"""

from enum import Enum


class constants(Enum):
"""Constantes de br_ms_sinasc."""

FTP = (
"ftp://ftp.datasus.gov.br/dissemin/publicos/SINASC/1996_/Dados/DNRES/"
"DN{sigla_uf}{ano}.dbc"
)
FTP_DIR = (
"ftp://ftp.datasus.gov.br/dissemin/publicos/SINASC/1996_/Dados/DNRES/"
)

# Área de trabalho do pod. `input/` recebe os .dbc, `output/` o particionado
# que sobe para o GCS.
PATH = "/tmp/br_ms_sinasc/"

SOURCE_FORMAT = "csv"

UFS = [
"AC",
"AL",
"AM",
"AP",
"BA",
"CE",
"DF",
"ES",
"GO",
"MA",
"MG",
"MS",
"MT",
"PA",
"PB",
"PE",
"PI",
"PR",
"RJ",
"RN",
"RO",
"RR",
"RS",
"SC",
"SE",
"SP",
"TO",
]

TABLES = {
"microdados": {
"file_prefix": "DN",
"file_name": "data.csv",
"partition_columns": ["ano", "sigla_uf"],
},
}

DATE_COLUMNS = [
"data_nascimento",
"data_nascimento_mae",
"data_ultima_menstruacao",
"data_registro_cartorio",
"data_cadastro",
"data_recebimento",
"data_recebimento_original",
"data_declaracao",
]

# Colunas de município que a fonte grava com 6 dígitos até certo ano e com 7
# a partir dele. A conversão é por valor, não por coluna.
MUNICIPIO_COLUMNS = [
"id_municipio_nascimento",
"id_municipio_mae",
"id_municipio_residencia",
]

RENAME = {
"CONTADOR": "sequencial_nascimento",
"CODMUNNASC": "id_municipio_nascimento",
"LOCNASC": "local_nascimento",
"CODESTAB": "codigo_estabelecimento",
"DTNASC": "data_nascimento",
"HORANASC": "hora_nascimento",
"SEXO": "sexo",
"PESO": "peso",
"RACACOR": "raca_cor",
"APGAR1": "apgar1",
"APGAR5": "apgar5",
"IDANOMAL": "id_anomalia",
"CODANOMAL": "codigo_anomalia",
"SEMAGESTAC": "semana_gestacao",
"TPMETESTIM": "semana_gestacao_estimada",
"GESTACAO": "gestacao_agr",
"GRAVIDEZ": "tipo_gravidez",
"PARTO": "tipo_parto",
"MESPRENAT": "inicio_pre_natal",
"CONSPRENAT": "pre_natal",
"CONSULTAS": "pre_natal_agr",
"KOTELCHUCK": "classificacao_pre_natal",
"QTDFILVIVO": "quantidade_filhos_vivos",
"QTDFILMORT": "quantidade_filhos_mortos",
"NATURALMAE": "id_pais_mae",
"CODUFNATU": "id_uf_mae",
"CODMUNNATU": "id_municipio_mae",
"CODPAISRES": "id_pais_residencia",
"CODMUNRES": "id_municipio_residencia",
"DTNASCMAE": "data_nascimento_mae",
"IDADEMAE": "idade_mae",
"ESCMAE": "escolaridade_mae",
"SERIESCMAE": "serie_escolar_mae",
"ESCMAE2010": "escolaridade_2010_mae",
"ESCMAEAGR1": "escolaridade_2010_agr_mae",
"ESTCIVMAE": "estado_civil_mae",
"CODOCUPMAE": "ocupacao_mae",
"RACACORMAE": "raca_cor_mae",
"QTDGESTANT": "gestacoes_ant",
"QTDPARTNOR": "quantidade_parto_normal",
"QTDPARTCES": "quantidade_parto_cesareo",
"DTULTMENST": "data_ultima_menstruacao",
"TPAPRESENT": "tipo_apresentacao",
"STTRABPART": "inducao_parto",
"STCESPARTO": "cesarea_antes_parto",
"TPROBSON": "tipo_robson",
"IDADEPAI": "idade_pai",
"CODCART": "cartorio",
"NUMREGCART": "registro_cartorio",
"DTREGCART": "data_registro_cartorio",
"ORIGEM": "origem",
"NUMEROLOTE": "numero_lote",
"VERSAOSIST": "versao_sistema",
"DTCADASTRO": "data_cadastro",
"DTRECEBIM": "data_recebimento",
# A fonte alterna entre os dois nomes conforme o ano; nenhum arquivo traz
# os dois ao mesmo tempo.
"DTRECORIGA": "data_recebimento_original",
"DTRECORIG": "data_recebimento_original",
"DIFDATA": "diferenca_data",
"DTDECLARAC": "data_declaracao",
"TPFUNCRESP": "funcao_responsavel",
"TPDOCRESP": "documento_responsavel",
"TPNASCASSI": "formacao_profissional_responsavel",
"STDNEPIDEM": "status_dn",
"STDNNOVA": "status_dn_nova",
"PARIDADE": "paridade",
}

COLUMNS = [
"ano",
"sigla_uf",
"sequencial_nascimento",
"id_municipio_nascimento",
"local_nascimento",
"codigo_estabelecimento",
"data_nascimento",
"hora_nascimento",
"sexo",
"peso",
"raca_cor",
"apgar1",
"apgar5",
"id_anomalia",
"codigo_anomalia",
"semana_gestacao",
"semana_gestacao_estimada",
"gestacao_agr",
"tipo_gravidez",
"tipo_parto",
"inicio_pre_natal",
"pre_natal",
"pre_natal_agr",
"classificacao_pre_natal",
"quantidade_filhos_vivos",
"quantidade_filhos_mortos",
"id_pais_mae",
"id_uf_mae",
"id_municipio_mae",
"id_pais_residencia",
"id_municipio_residencia",
"data_nascimento_mae",
"idade_mae",
"escolaridade_mae",
"serie_escolar_mae",
"escolaridade_2010_mae",
"escolaridade_2010_agr_mae",
"estado_civil_mae",
"ocupacao_mae",
"raca_cor_mae",
"gestacoes_ant",
"quantidade_parto_normal",
"quantidade_parto_cesareo",
"data_ultima_menstruacao",
"tipo_apresentacao",
"inducao_parto",
"cesarea_antes_parto",
"tipo_robson",
"idade_pai",
"cartorio",
"registro_cartorio",
"data_registro_cartorio",
"origem",
"numero_lote",
"versao_sistema",
"data_cadastro",
"data_recebimento",
"data_recebimento_original",
"diferenca_data",
"data_declaracao",
"funcao_responsavel",
"documento_responsavel",
"formacao_profissional_responsavel",
"status_dn",
"status_dn_nova",
"paridade",
]
Loading
Loading