basedosdados / basedosdados/pipelines
[rfc] Sincronizar tabela de arquitetura, BigQuery e API
- Dominant language
- Python
- Stars
- 49
- Forks
- 22
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 167
Description
### Descrição da funcionalidade
> RFC gerado com o Claude Code. Descrevi o problema, apresentei uma proposta e integari até essa versão. Pode ter alguns erros.
**Motivação:** [pipelines#1764](https://github.com/basedosdados/pipelines/issues/1764) · **Histórico:** [pipelines#1115](https://github.com/basedosdados/pipelines/issues/1115) · **Respeita:** [ADR-0001](https://github.com/basedosdados/manual-equipe-dados/blob/main/docs/governanca/metadados/adr/0001-manter-tabelas-arquitetura-no-drive.md)
## TLDR
**Problema:** a API da BD e o BigQuery mostram listas de colunas diferentes para a mesma tabela. Cada um é alimentado por uma fonte distinta — a API pela planilha de arquitetura, o BigQuery pelo modelo dbt — e manter as duas iguais é trabalho manual que ninguém tem como conferir.
**Solução:** registrar no `schema.yml` qual planilha descreve cada tabela, verificar no PR se a planilha bate com a tabela materializada em `basedosdados-dev`, e enviar as colunas para a API — staging no PR, produção no merge.
**Nada aqui reabre a [ADR-0001](https://github.com/basedosdados/manual-equipe-dados/blob/main/docs/governanca/metadados/adr/0001-manter-tabelas-arquitetura-no-drive.md)** — a planilha continua sendo onde se edita a arquitetura.
---
## 1. O problema: descrições diferentes da mesma tabela
A issue [#1764](https://github.com/basedosdados/pipelines/issues/1764) registra três tabelas em que a API e o BigQuery discordam. O caso mais claro é `basedosdados.br_bd_diretorios_mundo.pais`: a API publica 13 colunas, o BigQuery tem 15, e os nomes são diferentes.
| Coluna | API (prod e staging) | Repo `.sql` + BigQuery |
|---|---|---|
| `sigla_pais_iso3` | presente | ausente |
| `sigla_iso3` | ausente | presente |
| `nome_ingles` | presente | ausente |
| `nome_en` | ausente | presente |
| `id_cow` / `sigla_cow` | ausentes | presentes |
| **total** | **13** | **15** |
Verifiquei os dois lados: `br_bd_diretorios_mundo__pais.sql` no repo tem as 15 colunas com os nomes novos, batendo com o BigQuery. Ou seja, **o repositório está certo e a API está velha** — e está velha igualmente em prod e em staging.
Isso acontece porque a API só é alimentada pela planilha de arquitetura, via `arch.upload_columns()`. Quem materializa o BigQuery é o `.sql`. São dois caminhos independentes a partir de duas descrições que ninguém garante iguais.
## 2. Por que acontece: manter a planilha em dia dá mais trabalho
Existe um comando que regera o `.sql` e o `schema.yml` a partir da planilha. O problema é que ele **apaga o que foi escrito à mão**:
- `create_yaml_file` remove a entrada do modelo do `schema.yml` e a reconstrói do zero, com apenas `unique_combination_of_columns` e `not_null_proportion`. Todo teste customizado é perdido — `custom_relationships`, `ignore_values`, `config: where: __most_recent_year_month__`, `proportion_allowed_failures`.
- `create_sql_files` reescreve o arquivo inteiro, emitindo `safe_cast( as ) ` linha a linha. Qualquer transformação customizada é perdida.
O tamanho do que seria apagado:
| | |
|---|---|
| **52 de 151** | `schema.yml` com testes que a regeneração apagaria |
| **102 de 958** | modelos com `case`, join, CTE ou `coalesce` |
Um dos 102 é o `br_ibge_ipca__mes_categoria_rm.sql` — outra das tabelas listadas na [#1764](https://github.com/basedosdados/pipelines/issues/1764). Ele tem um `case when peso_mensal = "..." then null` que a regeneração descartaria.
Ou seja: editar o `.sql` e o `schema.yml` direto é o caminho mais curto e mais seguro, e voltar na planilha é trabalho extra sem retorno imediato. A planilha fica atrás, e nada avisa.
A verificação proposta **não depende** de mudar isso: ela acusa a divergência independentemente de o `schema.yml` ter sido atualizado à mão ou regerado. Melhorar o comando de geração continua valendo a pena, mas como conforto, não como pré-requisito.
## 3. Não existe registro de qual planilha é de qual tabela
A URL da arquitetura é passada à mão em `TableArchitecture(tables={...})` e some. Nada no repositório registra essa ligação. Duas consequências concretas:
- Não há como automatizar nenhuma verificação contra a planilha, porque não há como saber qual planilha consultar.
- Planilhas somem sem que ninguém perceba.
Isso já era um custo aceito conscientemente pela [ADR-0001](https://github.com/basedosdados/manual-equipe-dados/blob/main/docs/governanca/metadados/adr/0001-manter-tabelas-arquitetura-no-drive.md) ("já houve casos de tabelas em produção sem arquitetura salva no Drive"). O `arch_url` resolve isso como efeito colateral.
## 4. A proposta
### 4.1 Um campo `arch_url` no `schema.yml`
A ligação modelo → planilha passa a viver versionada, ao lado do modelo, e revisada no mesmo PR. O dbt aceita chaves livres sob `meta`:
```yaml
models:
- name: br_ibge_ipca__mes_categoria_rm
meta:
arch_url: https://docs.google.com/spreadsheets/d//edit#gid=0
description: ...
```
O mesmo campo passa a ser a entrada do gerador: em vez de colar a URL num notebook, `create_yaml_file` e `create_sql_files` a leem do próprio `schema.yml`.
### 4.2 O fluxo
| Onde | O quê |
|---|---|
| Drive | Cria ou edita a tabela de arquitetura — *inalterado* |
| Local | Atualiza o `schema.yml` e o `.sql` |
| Local | **Materializa em `basedosdados-dev`** (`dbt run`) |
| CI · PR | **Novo:** compara a planilha (`arch_url`) com a tabela em `basedosdados-dev` |
| CI · PR | **Novo:** se a comparação passou, envia as colunas da planilha para a **API de staging** |
| Merge | `table-approve` materializa em `basedosdados` — *inalterado* |
| Merge | **Novo:** envia as mesmas colunas para a **API de produção** |
A ordem dentro do PR importa: o envio para staging só acontece se a comparação passou. Não faz sentido publicar em staging um schema que já se sabe divergente.
### 4.3 Por que enviar para a API de staging no PR
A API de staging é uma cópia da de produção, e o que é enviado para ela fica guardado — não é apagada de tempo em tempo. Isso traz duas vantagens:
**1. O envio para produção deixa de ser a primeira execução.** Sem o envio no PR, o passo que escreve em produção rodaria pela primeira vez na vida justamente em produção. Com ele, o mesmo código, com a mesma planilha, já rodou antes contra uma cópia de produção — e o merge só repete o que já deu certo.
**2. Quem contribui não precisa de conta no backend da BD.** Hoje, enviar metadados exige credencial de escrita no backend. Se o envio acontece no CI, essa credencial fica só na automação — não é preciso criar e gerenciar conta para cada pessoa que sobe uma tabela.
### 4.4 O envio para a API de produção no merge
Ao mesclar em `main`, depois da materialização feita pelo `table-approve`, o mesmo workflow envia as colunas para a API de produção. Assim os três artefatos convergem sem ninguém precisar lembrar.
## 5. Por que isso não reabre a ADR-0001
A [ADR-0001](https://github.com/basedosdados/manual-equipe-dados/blob/main/docs/governanca/metadados/adr/0001-manter-tabelas-arquitetura-no-drive.md) decidiu que **a planilha é a superfície de edição**, e os argumentos vencedores foram todos sobre ergonomia: copiar valores entre linhas, ver visualmente quais colunas têm unidade de medida, proximidade com o formato tabular do site, barreira baixa para contribuidor externo. Tudo isso continua exatamente igual nesta proposta — ninguém edita YAML à mão.
A ADR não decidiu onde o artefato é armazenado, o que publica para a API, nem como se detecta divergência. E ela **não ponderou o custo que esta proposta ataca**: a divergência entre BigQuery e API não aparece em nenhuma das suas consequências negativas — é um custo observado depois, na [#1764](https://github.com/basedosdados/pipelines/issues/1764).
De quebra, a proposta recupera três das negativas que a ADR aceitou: passa a existir registro de qual planilha descreve qual tabela, a revisão da arquitetura passa a acontecer dentro do PR, e passa a ser possível rodar validação de CI sobre a arquitetura.
## 6. Decisões em aberto
### 6.1 O que fazer com o estado que fica em staging
Como o que é enviado para staging fica guardado, um PR fechado sem merge deixa staging diferente de produção para sempre. Com o tempo, staging deixa de ser uma cópia fiel — e é justamente a fidelidade que o torna útil para ensaiar o envio. Dois PRs mexendo na mesma tabela também se sobrescrevem, e nenhum dos dois é a verdade.
Isso pede uma política, não código: recarregar staging a partir de produção de vez em quando, ou aceitar a diferença e tratar staging como "o último PR que passou por aqui".
### 6.2 O que a verificação não alcança
Cinco campos da planilha não existem nem no BigQuery nem no `schema.yml` — vão direto para a API e não têm com o que ser comparados: `covered_by_dictionary`, `measurement_unit`, `has_sensitive_data`, `temporal_coverage` e `observations`. O `directory_column` é comparável, mas só contra o teste `relationships` no `schema.yml`, não contra o BigQuery.
Como o PR passa a enviar para staging, esses cinco campos ficam ao menos **visíveis** antes do merge, mesmo que não verificados automaticamente.
## 7. Adoção
**O campo é opcional e a verificação só roda onde ele existe.** Isso resolve o dilema entre bloquear e avisar: adicionar o `arch_url` é o ato de declarar "este modelo está sincronizado", então dá para falhar o build sem travar ninguém que tenha dívida antiga. A cobertura cresce tabela a tabela, conforme cada uma é tocada.
Para o legado cuja planilha se perdeu, `get_architecture_table_from_api` já reconstrói a arquitetura a partir da API — dá para regerar a planilha, colar a URL e entrar no ciclo.
Ordem sugerida: começar pelo `arch_url` e pela comparação no PR, que não escreve em lugar nenhum; depois o envio para staging; e o envio para produção por último.
## 8. Custos e riscos
- **Reconciliação manual do legado.** Numa tabela já divergente como a `pais`, a planilha provavelmente ainda tem os nomes antigos — alguém precisa atualizar a planilha, tabela a tabela. Nenhuma automação resolve isso.
- **Dois workflows passam a escrever no backend.** Precisam de credencial própria da automação, e o do PR dá escrita em staging a qualquer PR do repositório.
- **A planilha continua editável por qualquer um, sem histórico.** Alguém pode alterá-la sem abrir PR e a divergência volta até o próximo PR que tocar a tabela. Uma verificação diária em tudo que tem `arch_url` fecharia esse furo, mas está fora do escopo desta proposta.
- **A verificação passa a depender do que existe em `basedosdados-dev`** e a deixar estado em staging (7.1).
## 9. Perguntas para a equipe
1. Concordamos com o diagnóstico da seção 2 — que o gargalo é o custo de manter a planilha em dia, e não o processo em si?
2. Qual a política para o estado que acumula em staging (7.1)?
3. O envio automático para a API de produção no merge é aceitável, ou preferimos um passo manual explícito no começo?
4. O `arch_url` deve ser obrigatório para tabelas novas a partir de alguma data, ou opcional?
---
*Levantamento feito sobre `basedosdados/pipelines` em 2026-08-10 (958 modelos, 151 `schema.yml`, 164 datasets). Os números e os comportamentos citados foram verificados no código e nos dados.*
cc @basedosdados/dados
### Motivação e benefício
### Proposta de solução
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.