Skip to content

refactor: conforma schemas e tabelas à especificação do banco do MinC - #20

Merged
LuizaMaluf merged 3 commits into
mainfrom
feature/conformidade-banco-minc
Aug 13, 2026
Merged

refactor: conforma schemas e tabelas à especificação do banco do MinC#20
LuizaMaluf merged 3 commits into
mainfrom
feature/conformidade-banco-minc

Conversation

@CaioMelo25

Copy link
Copy Markdown
Collaborator

Contexto

O MinC subiu um banco de homologação e existe uma especificação oficial que define os schemas, as tabelas e as chaves esperadas. O pipeline não estava em conformidade: escrevia em transferegov_fundo_a_fundo e bsc_pnab, não extraía metas, cobria no máximo 5 dos 11 programas e criava tabelas de planilha com nome derivado do nome da aba do Excel.

Isso não podia ser corrigido depois da primeira carga: ClientPostgresDB executa CREATE SCHEMA IF NOT EXISTS a cada insert, então rodar as DAGs como estavam criaria os schemas fora do padrão sozinho, no banco novo.

Schemas e tabelas

Antes Agora
transferegov_fundo_a_fundo, bsc_pnab transferegov, bbagil, relatorio_gestao
raw_programas, raw_planos_acao programa_minc, plano_acao_minc
plano_acao_meta_minc, plano_acao_dado_bancario_minc
raw_bbagil_extrato_transacoes, raw_bbagil_subtransacoes extrato_bbagil, subtransacao_bbagil
~15 tabelas de planilha, com nome vindo do nome da aba 6 tabelas fixas em relatorio_gestao

Os nomes passam a viver em plugins/schemas_minc.py. Antes eram literais repetidos em oito arquivos — foi assim que o código divergiu da especificação sem ninguém notar.

Extração

  • Escopo: os 11 programas das 4 políticas. Estava [46, 47] numa DAG e [46, 47, 60, 61, 62] noutra, deixando LAB1 e PNAB Ciclo 2 de fora.
  • api_plano_acao_meta_dag (nova): o endpoint /plano_acao_meta não era consultado por nenhuma DAG e a tabela de metas não existia.
  • api_plano_acao_dado_bancario_dag (nova): grava todas as contas de cada plano com todas as colunas. Antes a informação era efeito colateral da DAG do BB Ágil, que guardava uma conta por plano e quatro campos; a escolha da conta virou regra de consumo dentro de extracao_bbagil_dag.
  • Chaves de integração (id_programa, id_plano_acao, cod_ibge, id_agencia_conta, id_plano_acao_dado_bancario) propagadas até extrato, subtransação e planilhas.

Regra territorial

plugins/territorio_ibge.py deriva tipo_ente, cod_ibge, cod_ibge_uf e cod_ibge_municipio. A API do TransfereGov informa o código do município da capital quando o ente recebedor é um estado — sem essa regra, o plano estadual de São Paulo fica gravado como plano do município de São Paulo.

Planilhas dos relatórios de gestão

  • A listagem passa a vir do Postgres (anexos_relatorios → relatorios_gestao → plano_acao_minc) em vez das keys do MinIO: varrer o bucket dá o arquivo, mas não diz de que plano de ação ele é.
  • Cada linha carrega as colunas obrigatórias da especificação e é chaveada por hash_registro. insert_data_por_tabela inseria sem chave nenhuma e duplicava tudo a cada execução.
  • Acima de 1200 colunas, coluna nova vai para payload_origem (JSON): consolidar milhares de layouts numa tabela só esbarraria no limite de 1600 colunas do Postgres.
  • tabela_origem e aba_origem preservam de onde cada linha veio.

dbt

Os 19 modelos que liam os nomes antigos foram repontados. Onde houve renome, é troca de nome; onde 15 tabelas viraram 6, o modelo recupera a fatia por tabela_origem:

from {{ source('relatorio_gestao', 'planilha_dados_lpg') }}
where tabela_origem = 'lpg_dados_pessoa_fisica'

Alguns modelos encolheram: stg_agentes_pf/pj/coletivos faziam union all de 3-4 tabelas de layout idêntico e viraram uma varredura com where tabela_origem in (...). O substring(id_anexo from 'anexo_([0-9]+)') saiu de todos os modelos, porque id_anexo agora guarda o id do anexo e não mais o nome do arquivo.

Configuração necessária antes de rodar

Airflow Variable Valor
transferegov_programas_ids [7, 8, 9, 15, 46, 47, 60, 61, 62, 111, 112]
transferegov_politicas_publicas [{"sigla": "LPG", "politica_publica": "...", "id_programas": [46, 47]}, ...]

A segunda existe porque a API não devolve sigla nem politica_publica, que a especificação exige em programa_minc. Sem ela a carga roda, mas essas duas colunas ficam nulas.

Ordem de execução: api_programas_dagapi_planos_acao_dag → (api_plano_acao_meta_dag, api_plano_acao_dado_bancario_dag, api_relatorios_gestao_dagapi_anexos_relatorios_dagdownload_anexos_transferegov_dagextracao_anexos_dag). extracao_bbagil_dag roda à parte e depende de plano_acao_dado_bancario_minc carregada.

Verificação

  • 11/11 DAGs parseiam no container do Airflow.
  • dbt run contra um Postgres com a estrutura nova: 32/32 modelos, 0 erros. As contagens por modelo batem com a fixture (as fatias sem linha da origem correspondente retornam 0).
  • Smoke em Postgres real: nenhum ente estadual com cod_ibge de 7 dígitos, as duas contas do plano gravadas, seleção da conta ativa correta, carga de planilha idempotente em duas execuções, coluna excedente preservada em payload_origem e apenas os 3 schemas da especificação criados.
  • 14 testes unitários (regra territorial e roteamento das planilhas). Ruff e mypy sem achado novo.

Falta a conferência numérica dos modelos dbt sobre dados reais — só é possível depois que a carga rodar em homologação.

Divergências assumidas

  1. transferegov.politica_publica não foi criada: sigla e politica_publica são colunas de programa_minc, que é o que a especificação já exige nessa tabela.
  2. Sem catálogo versionado (programas.json): o escopo é configuração de Airflow Variable.
  3. linha_origem é a posição da linha dentro da subtabela extraída, não a linha física do Excel — quem identifica o registro é hash_registro.
  4. bbagil.fato_bbagil segue declarada como source sem existir no banco; o modelo que a lê está enabled=false.

Fora do escopo

Anexos .ods e arquivos acima de 10 MB continuam sem virar linha; colunas técnicas restantes; índices; validações de qualidade automatizadas; tipagem (toda coluna nasce TEXT); FKs declaradas; mascaramento de dados pessoais; o filtro tipo_relatorio_gestao = 'FINAL' na ingestão de relatórios.

plugins/agencias_transferegov.py ficou sem nenhum importador depois que o BB Ágil passou a ler as contas do banco. Não foi removido aqui — merece PR próprio.

CaioMelo25 and others added 2 commits August 12, 2026 11:25
… território)

Rodar as DAGs como estavam criaria sozinho, no banco de homologação, os
schemas fora do padrão: `ClientPostgresDB` executa CREATE SCHEMA IF NOT
EXISTS a cada insert, então `transferegov_fundo_a_fundo` e `bsc_pnab`
nasceriam junto com a primeira carga. Este commit alinha a ingestão ao
documento antes disso acontecer.

Estrutura:
- schemas e tabelas passam a ser os do documento (transferegov, bbagil,
  relatorio_gestao), centralizados em plugins/schemas_minc.py — os nomes
  estavam como literais repetidos em oito arquivos, que foi como o código
  divergiu da especificação sem ninguém perceber;
- as ~15 tabelas de planilha, cujo nome saía do nome da aba do Excel (um
  layout novo criava uma tabela nova), viram as seis tabelas fixas de
  relatorio_gestao, com `tabela_origem`/`aba_origem` preservando a origem.

Escopo e chaves:
- o escopo passa a ser os 11 programas das 4 políticas (era [46, 47] numa
  DAG e [46, 47, 60, 61, 62] noutra, deixando LAB1 e PNAB Ciclo 2 de fora);
  `sigla`/`politica_publica` vêm da Variable transferegov_politicas_publicas,
  já que a API não devolve esses campos;
- nova api_plano_acao_meta_dag: `/plano_acao_meta` não era consultado por
  nenhuma DAG e a tabela de metas simplesmente não existia;
- nova api_plano_acao_dado_bancario_dag: grava todas as contas de cada plano
  com todas as colunas. Antes a informação era efeito colateral da DAG do BB
  Ágil, que guardava uma conta por plano e quatro campos — a escolha da conta
  virou regra de consumo, dentro de extracao_bbagil_dag;
- id_programa, id_plano_acao, cod_ibge, id_agencia_conta e
  id_plano_acao_dado_bancario propagados até extrato, subtransação e planilhas.

Território (regra 7.1, validação 12.7):
- plugins/territorio_ibge.py deriva tipo_ente/cod_ibge/cod_ibge_uf/
  cod_ibge_municipio. A API informa o código do município da capital quando o
  ente é um estado, e era assim que o plano estadual de São Paulo virava um
  plano do município de São Paulo.

Planilhas:
- a listagem vem do Postgres (anexos -> relatórios -> planos), não das keys do
  MinIO: varrer o bucket dá o arquivo, mas não diz de que plano de ação ele é;
- cada linha carrega as colunas obrigatórias da seção 7.3 e é chaveada por
  hash_registro, o que torna a carga idempotente — insert_data_por_tabela
  inseria sem chave nenhuma e duplicava tudo a cada execução;
- acima de 1200 colunas, coluna nova vai para payload_origem: consolidar
  milhares de layouts numa tabela só esbarraria no limite de 1600 do Postgres.

dbt continua lendo os nomes antigos por views de compatibilidade criadas pela
ingestão (plugins/views_compatibilidade.py), para não conflitar com a branch
da Meta 5 — os modelos não mudam uma linha.

O que ficou de fora, e por quê, está em docs/conformidade-minc.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ews de compatibilidade

O commit anterior tinha mantido os ~20 modelos de cotas_dbt/agentes_dbt lendo os
nomes antigos, por meio de views recriadas pela ingestão. Duas coisas derrubam
essa escolha:

- a colisão com a branch da Meta 5, que justificava a camada, é de UM arquivo
  (agentes_dbt/gold/primeiro_acesso_contemplados.sql) — não do conjunto;
- as views eram remendo sobre remendo: recriavam o `id_anexo` no formato antigo
  ("anexo_123_...") só para o `substring(id_anexo from 'anexo_([0-9]+)')` dos
  modelos continuar casando, sendo que esse regex existia justamente porque a
  ingestão gravava o nome do arquivo onde deveria ir o id do anexo.

Agora os modelos leem as tabelas do documento direto:

- sources.yml passa a declarar as fontes reais (transferegov, relatorio_gestao,
  bbagil) com os nomes das tabelas do documento;
- onde era só renome, é troca de nome (raw_planos_acao -> plano_acao_minc);
- onde ~15 tabelas viraram 6, o modelo recupera a fatia por `tabela_origem`.
  Vários modelos encolheram: stg_agentes_pf/pj/coletivos faziam `union all` de
  3-4 tabelas de mesmo layout e viraram uma varredura com `where tabela_origem
  in (...)`; stg_editais idem para as quatro abas de instrumentos;
- o `substring(id_anexo from 'anexo_([0-9]+)')` sai de todos os modelos, porque
  id_anexo agora é o id do anexo.

plugins/views_compatibilidade.py e a task que o chamava foram removidos.

Verificado contra um Postgres com a estrutura nova: `dbt run` PASS=32 ERROR=0, e
as contagens por modelo batem com a fixture (as fatias sem linha da origem
correspondente retornam 0, as demais retornam o esperado). As 11 DAGs seguem
parseando no container. A conferência numérica sobre dados reais continua
pendente até a carga rodar em homologação.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@CaioMelo25 CaioMelo25 self-assigned this Aug 12, 2026
Os testes novos importam módulos de `plugins/` (`territorio_ibge`,
`cliente_postgres`), que o Airflow carrega como módulos de topo. Isso só
funcionava com o PYTHONPATH exportado pelo Makefile; o CI chama `pytest`
direto e quebrava na coleção com ModuleNotFoundError.

A opção `pythonpath` do pytest repete os mesmos diretórios do Makefile, então
`pytest tests` passa a funcionar igual dentro e fora do make.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LuizaMaluf
LuizaMaluf merged commit 0875eb6 into main Aug 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants