sidra-sql 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,629 @@
1
+ Metadata-Version: 2.4
2
+ Name: sidra-sql
3
+ Version: 1.3.0
4
+ Summary: Tabelas de dados agregados do IBGE
5
+ Project-URL: Homepage, https://github.com/Quantilica/sidra-sql
6
+ Project-URL: Repository, https://github.com/Quantilica/sidra-sql
7
+ Project-URL: Issues, https://github.com/Quantilica/sidra-sql/issues
8
+ Author-email: "Komesu, D.K." <daniel@dkko.me>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: brazil,etl,ibge,open-data,postgresql,sidra
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Database
20
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
21
+ Requires-Python: >=3.12
22
+ Requires-Dist: httpx>=0.28.1
23
+ Requires-Dist: orjson>=3.11.7
24
+ Requires-Dist: platformdirs>=4.9.6
25
+ Requires-Dist: psycopg[binary]>=3.2.9
26
+ Requires-Dist: rich>=13.0.0
27
+ Requires-Dist: sidra-fetcher>=0.7.2
28
+ Requires-Dist: sqlalchemy>=2.0.41
29
+ Requires-Dist: typer>=0.24.1
30
+ Description-Content-Type: text/markdown
31
+
32
+ # sidra-sql: Pipeline ETL para dados do IBGE/SIDRA em PostgreSQL
33
+
34
+ ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square) ![Python](https://img.shields.io/badge/python-3.13+-blue.svg?style=flat-square)
35
+
36
+ **Pipeline ETL robusto para baixar, normalizar e carregar tabelas agregadas do SIDRA/IBGE em PostgreSQL.**
37
+
38
+ ![SIDRA-SQL banner](https://raw.githubusercontent.com/Quantilica/sidra-sql/v1.3.0/assets/banner.png)
39
+
40
+ Trabalhar com dados do IBGE é uma tarefa que todo analista e cientista de dados brasileiro conhece bem — e sabe que não é simples. A API SIDRA disponibiliza um acervo imenso de séries estatísticas (PIB municipal, população, inflação, agropecuária e muito mais), mas transformar esses dados brutos em um banco de dados relacional, limpo, normalizado e pronto para consulta é trabalhoso e cheio de armadilhas.
41
+
42
+ Este projeto resolve exatamente esse problema: um pipeline ETL completo, com controle de cache, downloads paralelos, carga em massa via protocolo COPY do PostgreSQL e um esquema de banco de dados cuidadosamente normalizado.
43
+
44
+ ---
45
+
46
+ ## Por que usar este projeto?
47
+
48
+ - **Zero redundância:** nomes de arquivo determinísticos garantem que a mesma requisição nunca seja baixada duas vezes.
49
+ - **Desempenho real:** downloads multi-threaded + carga via `COPY` do PostgreSQL são ordens de magnitude mais rápidos que abordagens ingênuas.
50
+ - **Confiabilidade:** retry com backoff exponencial lida com instabilidades da API sem interromper o pipeline.
51
+ - **Declarativo:** cada pesquisa é descrita em um arquivo TOML — sem código Python para adicionar novas séries.
52
+ - **Transformações:** camada de transformação (TOML + SQL) gera tabelas planas e desnormalizadas, prontas para Power BI, Excel ou qualquer ferramenta analítica.
53
+ - **Banco normalizado:** dados separados em cinco tabelas relacionais com constraints de unicidade e índices otimizados para consultas analíticas.
54
+
55
+ ---
56
+
57
+ ## Funcionalidades
58
+
59
+ | Funcionalidade | Detalhes |
60
+ |---|---|
61
+ | **Download paralelo** | Pool de threads configurável para baixar múltiplos períodos simultaneamente |
62
+ | **Cache inteligente** | Filenames determinísticos — cache-hit evita requisições duplicadas à API |
63
+ | **Retry com backoff** | Até 5 tentativas com delay exponencial (5s, 10s, 20s…) em falhas de rede |
64
+ | **Carga em massa** | Protocolo COPY nativo do PostgreSQL via `psycopg3` para inserção em alta performance |
65
+ | **Versionamento de revisões** | Soft-versioning na própria fato: cada revisão do IBGE é retida, no máximo uma linha ativa por chave (índice único parcial) — habilita snapshots as-of |
66
+ | **Recarga idempotente** | Detecção-de-mudança na carga — re-execução sem dados novos é no-op (zero churn) |
67
+ | **Export CSV + as-of** | `export` grava as saídas em CSV via `COPY`; `--as-of DATE` reconstrói o vintage publicado na data |
68
+ | **Skip inteligente de carga** | Tabela `arquivo_carregado` rastreia arquivos já carregados; re-execuções pulam o trabalho sem repetir I/O |
69
+ | **Normalização completa** | Localidades, dimensões (variável × classificação) e fatos em tabelas separadas |
70
+ | **Suporte a 6 classificações** | Produto cartesiano de até 6 níveis de classificação por variável |
71
+ | **Metadados persistidos** | Agregados, periodicidade e metadados JSON salvos no banco para consulta |
72
+ | **Transformações SQL** | Gera tabelas planas (ou views) prontas para análise, definidas por pares TOML + SQL |
73
+ | **Logging detalhado** | Dual-channel (arquivo rotativo + console) com rastreamento de cada etapa |
74
+
75
+ ---
76
+
77
+ ## Arquitetura
78
+
79
+ O projeto segue uma arquitetura baseada em plugins. O motor core `sidra-sql` gerencia e orquestra a execução de pipelines que são distribuídos via repositórios Git independentes.
80
+
81
+ ```
82
+ ┌─────────────────────────────────────────────────────────────┐
83
+ │ Plugins Independentes (GitHub) │
84
+ │ manifest.toml + fetch.toml + transform.toml │
85
+ │ (declaração das tabelas a baixar) │
86
+ └──────────────────────────┬──────────────────────────────────┘
87
+ │ instalado e lido via CLI
88
+ ┌──────────────────────────▼──────────────────────────────────┐
89
+ │ sidra-sql plugin install <url> │
90
+ │ sidra-sql run <plugin-alias> <pipeline-id> │
91
+ │ │
92
+ │ toml_runner.py │
93
+ │ TomlScript: download → metadata → load │
94
+ └──────┬───────────────────┬──────────────────┬───────────────┘
95
+ │ │ │
96
+ ┌──────▼──────┐ ┌────────▼───────┐ ┌──────▼──────────────┐
97
+ │ sidra.py │ │ database.py │ │ storage.py │
98
+ │ (Fetcher) │ │ (load, upsert, │ │ (filesystem, cache, │
99
+ │ API client │ │ DDL builders) │ │ filename hashing) │
100
+ └──────┬──────┘ └────────┬───────┘ └──────┬──────────────┘
101
+ │ │ │
102
+ ┌──────▼──────┐ ┌────────▼───────┐ ┌──────▼──────┐
103
+ │ SIDRA API │ │ PostgreSQL │ │ Sistema de │
104
+ │ (IBGE) │ │ (ibge_sidra) │ │ arquivos │
105
+ └─────────────┘ └────────┬───────┘ └─────────────┘
106
+
107
+ ┌───────────────────▼────────────────────────┐
108
+ │ transform_runner.py │
109
+ │ (executa o SQL da transformação) │
110
+ └───────────────────┬────────────────────────┘
111
+
112
+ ┌───────────▼───────────┐
113
+ │ PostgreSQL │
114
+ │ (analytics schema) │
115
+ │ tabelas prontas para │
116
+ │ Power BI / Excel │
117
+ └───────────────────────┘
118
+ ```
119
+
120
+ **Princípios de design:**
121
+
122
+ - **Desacoplado:** os pipelines vivem em repositórios próprios; o motor apenas clona e executa os manifestos TOML.
123
+ - **Determinismo:** o mesmo conjunto de parâmetros sempre gera o mesmo nome de arquivo — re-execuções são seguras e baratas.
124
+ - **Dois passos de carga:** o primeiro escaneamento coleta chaves únicas de localidades e dimensões; o segundo transmite os dados via COPY, evitando acúmulo em memória.
125
+ - **Declarativo:** tanto a carga (scripts TOML) quanto a transformação (TOML + SQL) são definidas por arquivos de configuração.
126
+
127
+ ---
128
+
129
+ ## Esquema do Banco de Dados
130
+
131
+ O banco é organizado em cinco tabelas no schema `ibge_sidra` (configurável):
132
+
133
+ ```
134
+ ┌─────────────────┐ ┌──────────────────────────────────────────┐
135
+ │ tabela_sidra │ │ dados (fatos) │
136
+ │─────────────────│ │──────────────────────────────────────────│
137
+ │ id (PK) │◄──────│ tabela_sidra_id (FK) │
138
+ │ nome │ │ localidade_id (FK) ──────────────────────┼──►┌─────────────────┐
139
+ │ periodicidade │ │ dimensao_id (FK) ────────────────────────┼──►│ localidade │
140
+ │ metadados (JSON)│ │ periodo_id (FK) ─────────────────────────┼──►│─────────────────│
141
+ │ ultima_atualizac│ │ v (valor como texto) │ │ id (PK) │
142
+ └─────────────────┘ │ modificacao (date) │ │ nc (nível id) │
143
+ │ ativo (boolean) │ │ nn (nível nome)│
144
+ └──────────────────────────────────────────┘ │ d1c (unidade id)│
145
+ │ d1n (unidade nom│
146
+ ┌──────────────────────────────┐ └─────────────────┘
147
+ │ periodo │◄────(periodo_id)
148
+ │──────────────────────────────│
149
+ │ id (PK) │ ┌──────────────────────────────────────────┐
150
+ │ codigo (ex: "202301") │ │ dimensao │
151
+ │ ano, mes, trimestre, semestre│ │──────────────────────────────────────────│
152
+ │ data_inicio, data_fim │ │ id (PK) │
153
+ └──────────────────────────────┘ │ mc,mn (unidade de medida id/nome) │
154
+ │ d2c,d2n (variável id/nome) │
155
+ │ d4c–d9c (ids das classificações, ≤6) │
156
+ │ d4n–d9n (nomes das classificações) │
157
+ └──────────────────────────────────────────┘
158
+ ```
159
+
160
+ ### Versionamento de revisões (vintage storage)
161
+
162
+ O IBGE revisa valores ao longo do tempo. A tabela `dados` preserva esse histórico: cada revisão distinta de uma chave lógica vira uma linha, e **no máximo uma** fica `ativo = TRUE` (a mais recente). A invariante é garantida fisicamente por um **índice único parcial**:
163
+
164
+ ```sql
165
+ CREATE UNIQUE INDEX uq_dados_ativo
166
+ ON dados (tabela_sidra_id, localidade_id, dimensao_id, periodo_id)
167
+ WHERE ativo;
168
+ ```
169
+
170
+ A carga aplica **detecção-de-mudança** (deactivate-then-insert por diferença de valor), processando cada `modificacao` em ordem crescente:
171
+
172
+ - **Carga inicial / valor novo:** insere a linha como `ativo = TRUE`.
173
+ - **Recarga idêntica:** no-op — nenhuma linha é tocada (zero churn).
174
+ - **Revisão de valor:** a linha ativa anterior vira `ativo = FALSE` (retida) e o novo valor entra como `ativo = TRUE`.
175
+
176
+ Assim, **"verdade atual"** = `WHERE ativo`; **"trilha de revisão"** = todas as linhas da chave por `modificacao`. Isso é o que torna o `sidra-sql export --as-of <data>` possível: por chave, a linha de maior `modificacao <= data` é o valor publicado naquela data.
177
+
178
+ > **Migração:** bancos criados antes desta mudança tinham uma constraint única cheia (`uq_dados`) e descartavam revisões. A migração para o índice parcial é idempotente e roda automaticamente no início de cada `sidra-sql run` (`ensure_vintage_schema`). Para reconstruir a trilha de vintages a partir do cache de arquivos JSON `@modificacao` já baixados, rode `--force-load`.
179
+
180
+ ---
181
+
182
+ ## Pipelines Padrão (Plugin Oficial)
183
+
184
+ O `sidra-sql` vem pré-configurado com o catálogo oficial de pipelines de referência (hospedado em [Quantilica/sidra-pipelines](https://github.com/Quantilica/sidra-pipelines)). Estas pipelines são instaladas automaticamente com o alias `std` na primeira execução do CLI.
185
+
186
+ | Comando | Pesquisa | Tabelas SIDRA |
187
+ |---|---|---|
188
+ | `sidra-sql run std pib_municipal` | **PIB dos Municípios** | 5938 |
189
+ | `sidra-sql run std estimativa_populacao` | **Estimativas de População** | 6579 |
190
+ | `sidra-sql run std censo_populacao` | **Censo Demográfico** | 200 |
191
+ | `sidra-sql run std contagem_populacao` | **Contagem de População** | 305, 793 |
192
+ | `sidra-sql run std ipca` | **IPCA** | 1692, 1693, 58, 61, 655, 656, 2938, 1419, 7060 |
193
+ | `sidra-sql run std ipca15` | **IPCA-15** | 1646, 1387, 1705, 7062 |
194
+ | `sidra-sql run std inpc` | **INPC** | 1686, 1690, 22, 23, 653, 654, 2951, 1100, 7063 |
195
+ | `sidra-sql run std ppm_rebanhos` | **PPM — Rebanhos** | 73, 3939 |
196
+ | `sidra-sql run std ppm_producao` | **PPM — Produção animal** | 74, 3940 |
197
+ | `sidra-sql run std ppm_exploracao` | **PPM — Aquicultura e exploração** | 94, 95 |
198
+ | `sidra-sql run std pam_lavouras_temporarias` | **PAM — Lavouras temporárias** | 839, 1000, 1001, 1002, 1612 |
199
+ | `sidra-sql run std pam_lavouras_permanentes` | **PAM — Lavouras permanentes** | 1613 |
200
+ | `sidra-sql run std pevs_producao` | **PEVS — Produção florestal** | 289, 291 |
201
+ | `sidra-sql run std pevs_area_florestal` | **PEVS — Área florestal** | 5930 |
202
+
203
+ Para criar suas próprias pipelines e distribuí-las como plugin, consulte o **[Guia de Criação de Pipelines](CREATING_PIPELINES.md)**.
204
+
205
+ ---
206
+
207
+ ## Pré-requisitos
208
+
209
+ - **Python 3.13+**
210
+ - **PostgreSQL 14+** (com usuário e banco de dados criados)
211
+ - Acesso à internet para consultar a API SIDRA do IBGE
212
+ - Biblioteca [`sidra-fetcher`](https://github.com/Quantilica/sidra-fetcher) (instalada automaticamente via `pyproject.toml`)
213
+
214
+ ---
215
+
216
+ ## Instalação
217
+
218
+ ```bash
219
+ pip install sidra-sql
220
+ ```
221
+
222
+ Com [uv](https://github.com/astral-sh/uv):
223
+
224
+ ```bash
225
+ uv add sidra-sql
226
+ ```
227
+
228
+ **Dependências principais:**
229
+
230
+ | Pacote | Uso |
231
+ |---|---|
232
+ | [`sidra-fetcher`](https://github.com/Quantilica/sidra-fetcher) | Cliente HTTP para a API SIDRA do IBGE |
233
+ | `psycopg[binary] >= 3.2.9` | Adaptador PostgreSQL com extensões C |
234
+ | `sqlalchemy >= 2.0.41` | ORM e geração de SQL |
235
+ | `orjson >= 3.11.7` | Serialização JSON de alta performance |
236
+
237
+ ---
238
+
239
+ ## Configuração
240
+
241
+ Crie o arquivo `config.ini` na raiz do projeto:
242
+
243
+ ```ini
244
+ [storage]
245
+ # Diretório onde os arquivos JSON baixados serão armazenados
246
+ data_dir = data
247
+
248
+ [database]
249
+ user = postgres
250
+ password = sua_senha
251
+ host = localhost
252
+ port = 5432
253
+ dbname = dados
254
+ schema = ibge_sidra
255
+ ```
256
+
257
+ > **Nota:** O schema `ibge_sidra` será criado automaticamente na primeira execução, incluindo todas as tabelas, índices e constraints.
258
+
259
+ ---
260
+
261
+ ## Uso
262
+
263
+ O sistema gerencia pipelines através de uma interface de linha de comando (CLI). Como as pipelines são plugins externos (repositórios git), o primeiro passo é instalar o plugin desejado.
264
+
265
+ ### 1. Gerenciar Plugins
266
+
267
+ ```bash
268
+ # Instalar um plugin via URL do Git
269
+ sidra-sql plugin install https://github.com/Quantilica/sidra-pipeline-pam.git --alias pam
270
+
271
+ # Listar os plugins instalados e suas pipelines disponíveis
272
+ sidra-sql plugin list
273
+
274
+ # Atualizar um plugin instalado
275
+ sidra-sql plugin update pam
276
+
277
+ # Remover um plugin
278
+ sidra-sql plugin remove pam
279
+ ```
280
+
281
+ ### 2. Criar e Desenvolver Plugins
282
+
283
+ O CLI inclui comandos para criar e iterar sobre plugins localmente, sem precisar escrever os arquivos manualmente.
284
+
285
+ ```bash
286
+ # Criar a estrutura completa de um novo plugin
287
+ sidra-sql plugin scaffold meu-plugin --description "Meus dados do IBGE"
288
+
289
+ # Adicionar um pipeline a um plugin existente (rodar dentro do diretório do plugin)
290
+ cd meu-plugin
291
+ sidra-sql plugin add-pipeline nova_serie --description "Nova série de dados"
292
+
293
+ # Adicionar um pipeline com caminho aninhado
294
+ sidra-sql plugin add-pipeline ipca_municipios --path "precos/ipca" --description "IPCA municipal"
295
+
296
+ # Validar a estrutura do plugin antes de publicar
297
+ sidra-sql plugin validate
298
+
299
+ # Validar um plugin já instalado pelo alias
300
+ sidra-sql plugin validate std
301
+ ```
302
+
303
+ O comando `scaffold` cria:
304
+ ```
305
+ meu-plugin/
306
+ ├── .gitignore
307
+ ├── README.md
308
+ ├── manifest.toml
309
+ └── meu_plugin/
310
+ ├── fetch.toml ← template comentado com referências ao SIDRA
311
+ ├── transform.toml ← declara as saídas (uma ou mais por pipeline)
312
+ └── meu_plugin.sql ← SQL da saída (um arquivo .sql por entrada [[table]])
313
+ ```
314
+
315
+ Para o fluxo completo de criação de plugins, veja o **[Guia de Criação de Pipelines](CREATING_PIPELINES.md)**.
316
+
317
+ ### 3. Executar Pipelines
318
+
319
+ Use o comando `run`, especificando o alias do plugin e o id do pipeline (mostrados via `sidra-sql plugin list`):
320
+
321
+ ```bash
322
+ # Baixa os dados e executa a transformação do pipeline 'lavouras_temporarias' do plugin 'pam'
323
+ sidra-sql run pam lavouras_temporarias
324
+
325
+ # Executa todos os pipelines de um plugin
326
+ sidra-sql run pam
327
+
328
+ # Força a atualização de metadados (re-baixa metadados da API mesmo com cache local)
329
+ sidra-sql run pam lavouras_temporarias --force-metadata
330
+
331
+ # Força o recarregamento de todos os arquivos no banco, ignorando o registro de arquivos já carregados
332
+ sidra-sql run pam lavouras_temporarias --force-load
333
+
334
+ # Combinação: metadados frescos + recarga completa
335
+ sidra-sql run pam lavouras_temporarias --force-metadata --force-load
336
+
337
+ # Executar apenas a etapa de transformação (sem fetch nem recursão)
338
+ sidra-sql transform pam lavouras_temporarias
339
+ ```
340
+
341
+ > **`--force-load`** ignora a tabela `arquivo_carregado` e reprocessa todos os arquivos presentes em disco. Útil quando o schema do banco foi alterado ou quando é necessário reconstruir os dados a partir dos arquivos JSON locais já baixados. Não re-baixa arquivos da API — para isso use `--force-metadata` em conjunto.
342
+
343
+ ### 4. Exportar para CSV
344
+
345
+ O comando `export` grava as saídas (`analytics.*`) de um pipeline em arquivos CSV, via protocolo `COPY` nativo do PostgreSQL (sem dependência de Polars/pandas).
346
+
347
+ ```bash
348
+ # Exporta as saídas materializadas do pipeline para CSV (verdade atual)
349
+ sidra-sql export pam lavouras_temporarias
350
+
351
+ # Diretório de saída (default: ./export)
352
+ sidra-sql export pam lavouras_temporarias --output-dir ./csv
353
+
354
+ # Exporta todas as saídas de todos os pipelines do plugin
355
+ sidra-sql export pam
356
+
357
+ # Snapshot as-of: reconstrói as saídas como o IBGE as publicava na data
358
+ sidra-sql export pam lavouras_temporarias --as-of 2024-03-01
359
+ ```
360
+
361
+ - **Sem `--as-of`** lê as tabelas/views já materializadas (`analytics.<nome>`). Se uma saída ainda não existe, o comando avisa e sugere rodar `sidra-sql run`. Gera `<nome>.csv`.
362
+ - **Com `--as-of DATE`** reconstrói cada saída a partir do histórico de revisões da tabela `dados`, reusando o próprio `.sql` do transform sem alterá-lo (uma view temporária `dados` expõe, por chave, a linha de maior `modificacao <= DATE`). Não toca as tabelas `analytics.*` persistidas. Gera `<nome>@YYYYMMDD.csv`.
363
+
364
+ > O snapshot as-of só enxerga revisões já capturadas no banco (ver **Versionamento de revisões** abaixo). Para reconstruir vintages a partir do cache de arquivos JSON já baixados, rode `sidra-sql run <plugin> <pipeline> --force-load`.
365
+
366
+ ---
367
+
368
+ ## Formato TOML
369
+
370
+ Cada arquivo TOML contém uma lista de entradas `[[tabelas]]`. Cada entrada corresponde a uma chamada à API SIDRA:
371
+
372
+ ```toml
373
+ [[tabelas]]
374
+ tabela_sidra = "5938" # ID da tabela no SIDRA
375
+ variables = ["37", "498"] # IDs das variáveis ("allxp" para todas)
376
+ territories = {6 = ["all"]} # nível territorial → lista de IDs
377
+
378
+ [tabelas.classifications] # classificações e categorias (opcional)
379
+ 315 = [] # lista vazia = todas as categorias
380
+ ```
381
+
382
+ **Níveis territoriais comuns:**
383
+
384
+ | Código | Descrição |
385
+ |---|---|
386
+ | `1` | Brasil |
387
+ | `2` | Grandes Regiões |
388
+ | `3` | Unidades da Federação |
389
+ | `6` | Municípios |
390
+ | `7` | Regiões Metropolitanas |
391
+ | `71` | Regiões Metropolitanas e RIDEs |
392
+
393
+ ### Flags especiais
394
+
395
+ **`unnest_classifications = true`**
396
+
397
+ Busca os metadados da tabela em tempo de execução e gera uma requisição para cada combinação de classificação × categoria:
398
+
399
+ ```toml
400
+ [[tabelas]]
401
+ tabela_sidra = "1613"
402
+ variables = ["allxp"]
403
+ territories = {6 = []}
404
+ unnest_classifications = true
405
+ ```
406
+
407
+ **`split_variables = true`**
408
+
409
+ Emite uma requisição separada para cada variável listada em `variables`:
410
+
411
+ ```toml
412
+ [[tabelas]]
413
+ tabela_sidra = "1002"
414
+ variables = ["109", "216", "214", "112"]
415
+ split_variables = true
416
+ territories = {6 = []}
417
+ classifications = {81 = ["allxt"]}
418
+ ```
419
+
420
+ ### Adicionar uma nova série
421
+
422
+ Para aprender a criar o seu próprio repositório de pipelines compatível com este motor, veja a documentação dedicada:
423
+ 👉 **[Guia: Como Criar Pipelines (Plugins)](CREATING_PIPELINES.md)**
424
+
425
+ ---
426
+
427
+ ## Transformações
428
+
429
+ Após a carga dos dados brutos no banco normalizado, a camada de transformação gera tabelas planas e desnormalizadas, prontas para consumo por ferramentas analíticas (Power BI, Excel, Metabase, etc.).
430
+
431
+ Cada pipeline declara suas saídas em um único `transform.toml` no seu diretório:
432
+
433
+ - **`transform.toml`** — uma ou mais entradas `[[table]]`, cada uma especificando o nome da tabela/view de destino, schema, estratégia e o arquivo `.sql` correspondente
434
+ - **`<saída>.sql`** — query SELECT que produz os dados denormalizados (um arquivo por entrada `[[table]]`)
435
+
436
+ Um pipeline pode produzir múltiplas saídas (ex.: uma tabela detalhada + uma view agregada) declarando múltiplos blocos `[[table]]` no mesmo `transform.toml`. Cada saída é materializada na ordem do array, em sua própria transação — se uma falhar, as anteriores persistem.
437
+
438
+ ### Executar uma transformação
439
+
440
+ A execução via CLI `sidra-sql run <plugin> <pipeline>` já orquestra de forma inteligente a extração e em seguida a transformação de acordo com o `manifest.toml` do plugin.
441
+
442
+ ### Formato TOML da transformação
443
+
444
+ ```toml
445
+ [[table]]
446
+ name = "ipca" # Nome da tabela de destino
447
+ schema = "analytics" # Schema de destino (criado automaticamente)
448
+ strategy = "replace" # Estratégia de materialização ("replace" ou "view")
449
+ sql = "ipca.sql" # Arquivo SQL (relativo a transform.toml)
450
+ description = "IPCA - variação e peso mensal por categoria e localidade"
451
+ primary_key = ["periodo", "localidade_id", "variavel", "categoria"] # Opcional: define PK após carga
452
+ indexes = [
453
+ { name = "idx_ipca_periodo", columns = ["periodo"] },
454
+ { name = "idx_ipca_localidade", columns = ["localidade"] },
455
+ ]
456
+
457
+ # Saída adicional opcional — outra tabela/view do mesmo pipeline:
458
+ [[table]]
459
+ name = "ipca_resumo_anual"
460
+ schema = "analytics"
461
+ strategy = "view"
462
+ sql = "ipca_resumo.sql"
463
+ ```
464
+
465
+ **Estratégias disponíveis:**
466
+
467
+ | Estratégia | Comportamento | Quando usar |
468
+ |---|---|---|
469
+ | `replace` | `DROP` + `CREATE AS` + `PK/Indexes` | Import em Power BI / Excel (refresh completo) |
470
+ | `view` | `CREATE OR REPLACE VIEW` | Conexões live (zero storage, sempre atualizado) |
471
+
472
+ ### SQL da transformação
473
+
474
+ O arquivo `.sql` contém um SELECT puro. Os nomes de tabela (`dados`, `dimensao`, `localidade`, `periodo`) são resolvidos pelo `search_path` configurado em `config.ini` — não use prefixo de schema:
475
+
476
+ ```sql
477
+ SELECT
478
+ p.codigo AS periodo,
479
+ p.ano,
480
+ p.mes,
481
+ l.d1c AS localidade_id,
482
+ l.d1n AS localidade,
483
+ dim.d2n AS variavel,
484
+ dim.d4n AS categoria,
485
+ CASE WHEN d.v ~ '^-?[0-9]' THEN d.v::numeric END AS valor
486
+ FROM dados d
487
+ JOIN periodo p ON d.periodo_id = p.id
488
+ JOIN dimensao dim ON d.dimensao_id = dim.id
489
+ JOIN localidade l ON d.localidade_id = l.id
490
+ WHERE d.tabela_sidra_id IN ('7060', '1419')
491
+ AND d.ativo = true
492
+ ```
493
+
494
+ Valores não numéricos do SIDRA (`"..."`, `"-"`, `"X"`) são convertidos em `NULL` pelo guard `CASE WHEN d.v ~ '^-?[0-9]'`.
495
+
496
+ ### Adicionar uma nova transformação
497
+
498
+ Para detalhes sobre como adicionar ou editar transformações dentro do seu próprio plugin, veja o **[Guia: Como Criar Pipelines](CREATING_PIPELINES.md)**.
499
+
500
+ ### Transformações incluídas
501
+
502
+ | Pipeline | Tabela de destino | Descrição |
503
+ |---|---|---|
504
+ | `pipelines/snpc/ipca/` | `analytics.ipca` | IPCA completo |
505
+ | `pipelines/snpc/inpc/` | `analytics.inpc` | INPC completo |
506
+ | `pipelines/snpc/ipca15/` | `analytics.ipca15` | IPCA-15 completo |
507
+ | `pipelines/pib_munic/` | `analytics.pib_municipal` | PIB dos Municípios |
508
+ | `pipelines/populacao/estimapop/` | `analytics.estimativa_populacao` | Estimativas de população |
509
+ | `pipelines/populacao/censo_populacao/` | `analytics.censo_populacao` | Censo Demográfico |
510
+ | `pipelines/populacao/contagem_populacao/` | `analytics.contagem_populacao` | Contagem da População |
511
+ | `pipelines/ppm/rebanhos/` | `analytics.ppm_rebanhos` | Efetivo dos rebanhos |
512
+ | `pipelines/ppm/producao/` | `analytics.ppm_producao` | Produção de origem animal |
513
+ | `pipelines/ppm/exploracao/` | `analytics.ppm_exploracao` | Aquicultura e exploração |
514
+ | `pipelines/pam/lavouras_permanentes/` | `analytics.pam_lavouras_permanentes` | Lavouras permanentes |
515
+ | `pipelines/pam/lavouras_temporarias/` | `analytics.pam_lavouras_temporarias` | Lavouras temporárias |
516
+ | `pipelines/pevs/producao/` | `analytics.pevs_producao` | Extração vegetal e silvicultura |
517
+ | `pipelines/pevs/area_florestal/` | `analytics.pevs_area_florestal` | Área de florestas plantadas |
518
+
519
+ ---
520
+
521
+ ## Fluxo de Dados
522
+
523
+ ```
524
+ API SIDRA (IBGE)
525
+
526
+ │ GET /agregados/{tabela}/periodos/{período}/...
527
+
528
+ ┌────────────────────────────────────────────────────┐
529
+ │ Fetcher (sidra.py) │
530
+ │ ┌─────────────────────────────────────────────┐ │
531
+ │ │ ThreadPoolExecutor (max_workers=4) │ │
532
+ │ │ → download de cada período em paralelo │ │
533
+ │ │ → retry com backoff (5 tentativas, base 5s)│ │
534
+ │ └──────────────────┬──────────────────────────┘ │
535
+ └─────────────────────┼──────────────────────────────┘
536
+
537
+
538
+ ┌────────────────────────┐
539
+ │ Storage (storage.py) │
540
+ │ data/t-{id}/ │
541
+ │ ├── arquivo1.json │ ← nome determinístico
542
+ │ ├── arquivo2.json │ (tabela+período+terr+
543
+ │ └── ... │ vars+classif+mod)
544
+ └───────────┬────────────┘
545
+
546
+
547
+ ┌────────────────────────────────────────┐
548
+ │ load_dados (database.py) │
549
+ │ │
550
+ │ Passo 1: scan JSON │
551
+ │ → coleta chaves únicas de │
552
+ │ localidades e dimensões │
553
+ │ → upsert em lotes de 5.000 linhas │
554
+ │ → constrói lookup dicts em memória │
555
+ │ Passo 2: scan JSON novamente │
556
+ │ → resolve IDs via lookup │
557
+ │ → usa data de modificação da API │
558
+ │ → stream via COPY para staging table │
559
+ │ → merge por vintage (modificacao): │
560
+ │ deactivate-then-insert por valor │
561
+ └───────────────┬────────────────────────┘
562
+
563
+
564
+ ┌──────────────────┐
565
+ │ PostgreSQL │
566
+ │ │
567
+ │ tabela_sidra │
568
+ │ localidade │
569
+ │ dimensao │
570
+ │ dados │
571
+ └──────────────────┘
572
+ ```
573
+
574
+ ---
575
+
576
+ ## Desenvolvimento
577
+
578
+ ```bash
579
+ git clone https://github.com/Quantilica/sidra-sql.git
580
+ cd sidra-sql
581
+ uv sync --dev
582
+ uv run pytest
583
+ ```
584
+
585
+ A suíte de testes cobre:
586
+
587
+ | Arquivo | O que testa |
588
+ |---|---|
589
+ | `tests/test_config.py` | Carregamento de config, setup de logging |
590
+ | `tests/test_storage.py` | Geração de nomes, leitura/escrita, caminhos de metadados |
591
+ | `tests/test_base.py` | Cache de metadados, deduplicação, download com filepaths |
592
+ | `tests/test_sidra.py` | Retry logic, unnesting de classificações, context manager |
593
+ | `tests/test_database.py` | Limpeza de dados, criação de engine, semântica de carga (vintage merge) |
594
+ | `tests/test_exporter.py` | Builders de SQL do export (COPY, view as-of), parsing de saídas |
595
+ | `tests/test_utils.py` | Produto cartesiano de dimensões, resolução de unidade |
596
+
597
+ ---
598
+
599
+ ## Estrutura do Repositório
600
+
601
+ ```
602
+ sidra-sql/
603
+ ├── src/sidra_sql/
604
+ │ ├── __init__.py
605
+ │ ├── cli.py # Interface de Linha de Comando (Typer)
606
+ │ ├── plugin_manager.py # Gerenciamento de plugins, Git e manifests
607
+ │ ├── scaffold.py # Geração de plugins e pipelines (scaffold, add-pipeline)
608
+ │ ├── validator.py # Validação de estrutura de plugins
609
+ │ ├── toml_runner.py # TomlScript — orquestra o pipeline ETL de extração
610
+ │ ├── transform_runner.py # TransformRunner — materializa TOML+SQL analíticos
611
+ │ ├── exporter.py # Exporter — export CSV das saídas + snapshots as-of
612
+ │ ├── config.py # Leitura de config.ini
613
+ │ ├── database.py # SQLAlchemy, carga, DDL/DCL
614
+ │ ├── models.py # ORM models (tabelas, localidades, dimensões, dados)
615
+ │ ├── sidra.py # Cliente da API SIDRA com retry e cache
616
+ │ ├── storage.py # Filesystem: leitura, escrita, filenames
617
+ │ └── utils.py # Produto cartesiano de dimensões
618
+ ├── tests/
619
+ ├── config.ini # Configurações (não versionado)
620
+ ├── pyproject.toml # Metadados e dependências do projeto
621
+ ├── README.md
622
+ └── CREATING_PIPELINES.md # Guia para criação de plugins
623
+ ```
624
+
625
+ ---
626
+
627
+ ## Licença
628
+
629
+ MIT — veja [LICENSE](LICENSE).
@@ -0,0 +1,20 @@
1
+ sidra_sql/__init__.py,sha256=AsWaOaA0TOIWEw5eMoQl_9rOom0M0Z8mPNxLHKxl3zM,390
2
+ sidra_sql/cli.py,sha256=g0vqXXStGE0q72B5EVv5weMOENTSo_s4SLt5nyZK_0c,20104
3
+ sidra_sql/config.py,sha256=71ODLM_v9EHrQOi9rx-_zAHQ5HTy9zmzgp8z2Vvjwxs,4053
4
+ sidra_sql/database.py,sha256=X9_IrkTAfoxevFPKzwvzmP5QvcVqCZUO0tvOOdUOu1M,27160
5
+ sidra_sql/exporter.py,sha256=roiDvfwr6f44JM308-T8tNcYYoVc9aOcTRuuc0bSUJ8,6387
6
+ sidra_sql/models.py,sha256=WsUFiBldI-hKONLRrZbKoUAoeiYnG_pjYu37i0pqx5M,7519
7
+ sidra_sql/plugin_manager.py,sha256=cV0Bt_VjkW4Y9oB2wyJHFjnGgUl1tbtv-9vCbBfYjmc,6785
8
+ sidra_sql/runner.py,sha256=yQ3Xx-RU7mf72oC7lXQszSpWbmzCDBakiJrvCyZu9R0,2374
9
+ sidra_sql/scaffold.py,sha256=BkdOUKXN5vfRx595RzbeXdiP4Afxg7qV60NsFY1xrOw,8926
10
+ sidra_sql/sidra.py,sha256=kfhtRcSKfNYGEkzUfIwzvu3b_cerBHdYnk7FvbD5oVo,13641
11
+ sidra_sql/storage.py,sha256=No7SWSLQSi4YGFopn-kTP4oVsYqKcS-8frGxqYA3ZlM,6018
12
+ sidra_sql/toml_runner.py,sha256=Hh6OpEpdkSllphwdPd-Ob-AmOqG6CFmK5jEpIazThMg,14075
13
+ sidra_sql/transform_runner.py,sha256=slV5k8XSPdi1NEoJg8whG8vGTA9PTjlW46uTaGTFuBs,5280
14
+ sidra_sql/utils.py,sha256=Y3G1X1JwWuVG6jSY5mtPKHY-9NgaVR-CI_6On6SJWJ8,3495
15
+ sidra_sql/validator.py,sha256=558XeyPA7YwQx8yZYAo29DPlUAMaUGcTfihN0HKJk0c,7070
16
+ sidra_sql-1.3.0.dist-info/METADATA,sha256=5HANlLd_wdfCwDMD8IaXSEohB397nI5OV3R_12IMPQw,32853
17
+ sidra_sql-1.3.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
18
+ sidra_sql-1.3.0.dist-info/entry_points.txt,sha256=w7u07NGGQ8izmgvFLJ2SRt9hryVOAMZTbnzmBy8URR0,49
19
+ sidra_sql-1.3.0.dist-info/licenses/LICENSE,sha256=TugvnTQpZyvRbaauokRh_OlHs5J4CHwUv11YhJnHwas,1066
20
+ sidra_sql-1.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sidra-sql = sidra_sql.cli:main