Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Quantilica — Documentação

Portal público da Quantilica (`docs.quantilica.io`), construído com [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).
Portal público da Quantilica (`docs.quantilica.com`), construído com [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).

## Desenvolvimento local

Expand All @@ -9,7 +9,7 @@ uv sync
uv run mkdocs serve
```

Site disponível em `http://127.0.0.1:8000`.
Site disponível em `https://docs.quantilica.com/`.

## Estrutura

Expand Down
7 changes: 7 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ description: Marcos importantes do ecossistema Quantilica — novos pacotes, mud

Marcos importantes do ecossistema como um todo. Cada pacote mantém seu próprio `CHANGELOG.md` no repositório do GitHub — este aqui é o resumo cross-pacote.

## 2026-05 — Novos pacotes de infraestrutura e integração analítica

- **`quantilica-cloud`** lançado: plugin CLI para sincronizar manifestos de download com o catálogo na nuvem. Registrado via `quantilica.commands`; acessível como `quantilica cloud login/sync/status`. Offline-first por design.
- **`quantilica-catalog`** lançado: modelo de observação canônico (star schema) com adaptadores para BCB-SGS, RTN, INMET, Tesouro Direto e SIDRA. Resolve o cruzamento multi-fonte com um único `JOIN` em `indicator_id + geo_id + date`.
- **`quantilica-io` integrado nos fetchers**: `bcb-sgs-fetcher`, `rtn-fetcher` e `inmet-fetcher` agora exportam Parquet tipado via `save_parquet` / `write_to_parquet` com `DataContract` e manifesto embutido no header.
- **`bcb-sgs-fetcher` padronizado** para as convenções do workspace: logging estruturado, `HttpClient` em `data.py`, `ScraperClient` mantido com httpx direto por precisar de sessão stateful.

## 2026-05 — Reescrita do portal de documentação

- Portal reestruturado com narrativa de três atos no `index.md`.
Expand Down
24 changes: 24 additions & 0 deletions docs/fundacoes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,27 @@ Veja a [Arquitetura do Ecossistema](../concepts/arquitetura.md) para o desenho c
## E o host de CLI?

[`quantilica-cli`](quantilica-cli.md) é o ponto de entrada unificado do ecossistema: descobre fetchers instalados via entry points e os monta como subcomandos. Não é uma fundação no sentido de "todo coletor depende dela" — é um **host** que consome os fetchers. Foi incluído na seção Fundações por afinidade arquitetural (compartilha o mesmo padrão de design domain-neutral).

## `quantilica-cloud` — sincronização com a nuvem

Plugin opt-in que sincroniza os manifestos de download locais com um catálogo na nuvem. Registrado sob `quantilica.commands`, expõe os subcomandos `quantilica cloud login`, `quantilica cloud sync` e `quantilica cloud status`. A coleta de dados nunca depende deste pacote.

→ [Documentação completa](quantilica-cloud.md)

## `quantilica-catalog` — modelo canônico de observações

Resolve o cruzamento multi-fonte: define um star schema comum (`fact_observation` + dimensões de indicador e geográfica) e adaptadores que convertem DataFrames de cada fetcher para esse formato. Torna qualquer `JOIN` entre IBGE, BCB, INMET e demais fontes trivial.

→ [Documentação completa](quantilica-catalog.md)

---

## Visão geral das camadas de infraestrutura

| Camada | Pacote | Depende de | Para quem |
|---|---|---|---|
| I/O resiliente | `quantilica-core` | stdlib + httpx | todo coletor |
| Analítica | `quantilica-io` | core + Polars + PyArrow | quem processa para análise |
| CLI | `quantilica-cli` | core | quem usa a linha de comando |
| Nuvem | `quantilica-cloud` | core + cli (runtime) | quem quer sincronizar manifestos |
| Catálogo | `quantilica-catalog` | io + Polars | quem cruza múltiplas fontes |
181 changes: 181 additions & 0 deletions docs/fundacoes/quantilica-catalog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
---
title: quantilica-catalog
description: Modelo de observação canônico e catálogo unificado para cruzamento de dados de múltiplas fontes brasileiras.
---

# `quantilica-catalog`

Modelo de dados canônico e adaptadores para normalizar observações de diferentes fontes num esquema unificado. Resolve o problema central do cruzamento de dados: cada fonte usa estrutura, nomes de colunas e granularidade geográfica diferentes.

> **O problema:** SIDRA usa `localidade`, BCB-SGS não tem geografia, INMET usa código de estação. Para cruzar um índice BCB com dados climáticos do INMET, você precisa de um modelo comum. `quantilica-catalog` fornece esse modelo.

## Instalação

```bash
uv add "quantilica-catalog @ git+https://github.com/Quantilica/quantilica-catalog.git"
```

## O Modelo: Star Schema

O catálogo organiza os dados em um star schema com uma tabela fato e dimensões de suporte:

```
fact_observation (o que? quando? onde? quanto?)
├── dim_indicator — metadados do indicador (nome, categoria, frequência)
├── dim_geo_entity — entidades geográficas (estados, municípios, estações)
├── dim_geo_relationship — hierarquias entre entidades geográficas
└── dim_geo_code — códigos externos (IBGE, INMET, etc.)
```

## Tabela Fato: `fact_observation`

Cada linha responde: *qual foi o valor do indicador X no tempo T no lugar G?*

```python
from quantilica_catalog import OBSERVATION_CONTRACT
# Schema: indicator_id (Utf8), date (Date), geo_id (Utf8?),
# value (Float64?), date_end (Date?)
```

| Campo | Tipo | Descrição |
|---|---|---|
| `indicator_id` | `Utf8` | `"fonte:id"` — ex.: `"bcb-sgs:433"`, `"sidra:1705:93"` |
| `date` | `Date` | Data de início da observação |
| `geo_id` | `Utf8?` | `null` para séries nacionais sem desagregação geográfica |
| `value` | `Float64?` | Valor numérico |
| `date_end` | `Date?` | Data de fim (séries com intervalo, ex.: trimestres móveis) |

## Adaptadores

Os adaptadores convertem DataFrames no formato de cada fetcher para o formato canônico:

```python
from quantilica_catalog.adapters.bcb_sgs import to_observations as sgs_obs
from quantilica_catalog.adapters.sidra import to_observations as sidra_obs
import polars as pl

# BCB-SGS → observações canônicas (sem geo_id — série nacional)
df_sgs = pl.read_parquet("series_433.parquet") # formato SGS_CONTRACT
obs = sgs_obs(df_sgs)
# indicator_id="bcb-sgs:433", geo_id=null
```

Adaptadores disponíveis e formato do `indicator_id` gerado:

| Módulo | Fonte | Formato do `indicator_id` |
|---|---|---|
| `adapters.bcb_sgs` | BCB-SGS | `"bcb-sgs:{series_id}"` |
| `adapters.rtn` | Tesouro Nacional RTN | `"rtn:{planilha}:{coluna}"` |
| `adapters.inmet` | INMET BDMEP | `"inmet:{variável}"` |
| `adapters.tesouro_direto` | Tesouro Direto | `"td:{tipo_titulo}"` |
| `adapters.sidra` | IBGE SIDRA | `"sidra:{agregado_id}:{variavel_id}"` (ou `:{categoria_ids}` se classificado) |

!!! note "SIDRA com categorias"
Quando o agregado SIDRA tem classificações (ex.: desemprego por faixa etária), o `indicator_id` inclui os IDs das categorias separados por `|`: `"sidra:1705:93:49223|49702"`.

## Dimensão de Indicadores

```python
from quantilica_catalog import IndicatorEntry, DataCategory, Frequency, entries_to_frame

entries = [
IndicatorEntry(
indicator_id="bcb-sgs:433",
source_id="bcb",
source_dataset_id="sgs",
name="IPCA — Variação mensal",
category=DataCategory.PRICES,
unit="%",
frequency=Frequency.MONTHLY,
),
]

dim_df = entries_to_frame(entries)
```

**Categorias** (`DataCategory`): `monetary`, `fiscal`, `financial`, `climate`, `labor`, `trade`, `health`, `demographic`, `economic`, `prices`.

**Frequências** (`Frequency`): `hourly`, `daily`, `weekly`, `monthly`, `quarterly`, `semi_annual`, `rolling_quarterly`, `yearly`, `multi_year`, `irregular`.

## Dimensão Geográfica

### Entidades — `dim_geo_entity`

```python
from quantilica_catalog import GeoEntityEntry, GeoType, state_id, municipality_id

entries = [
GeoEntityEntry(geo_id=state_id("SP"), geo_type=GeoType.TERRITORY, name="São Paulo"),
GeoEntityEntry(geo_id=municipality_id("3550308"), geo_type=GeoType.TERRITORY, name="São Paulo"),
]
```

**Construtores de `geo_id`:**

| Função | Exemplo de saída |
|---|---|
| `country_id()` | `"BR"` |
| `region_id("1")` | `"BR:R:1"` |
| `state_id("SP")` | `"BR:SP"` |
| `mesoregion_id("3515")` | `"BR:MR:3515"` |
| `microregion_id("35061")` | `"BR:MCR:35061"` |
| `municipality_id("3550308")` | `"BR:M:3550308"` |
| `district_id("355030805")` | `"BR:D:355030805"` |
| `census_sector_id("350030805000001")` | `"BR:CS:350030805000001"` |
| `station_id("INMET", "A701")` | `"INMET:A701"` |

### Hierarquias — `dim_geo_relationship`

```python
from quantilica_catalog import GeoRelationshipEntry, RelType, GeoSystem

rel = GeoRelationshipEntry(
from_geo_id=municipality_id("3550308"),
to_geo_id=state_id("SP"),
rel_type=RelType.ADMINISTRATIVE_PARENT,
system=GeoSystem.IBGE_TRADITIONAL,
)
```

**Tipos de relação** (`RelType`): `administrative_parent`, `contains`, `overlaps`, `borders`.

**Sistemas** (`GeoSystem`): `ibge_traditional`, `ibge_rgint`, `ibge_census`, `datasus_health`, `ana_hydrographic`, `inmet`.

### Códigos externos — `dim_geo_code`

```python
from quantilica_catalog import GeoCodeEntry

code = GeoCodeEntry(geo_id=state_id("SP"), system="ibge", code="35")
```

## DDL PostgreSQL

Para criar as tabelas de dimensão geográfica num banco PostgreSQL:

```python
from sqlalchemy import text
from quantilica_catalog.sql.ddl import CREATE_ALL_GEO_TABLES

with engine.connect() as conn:
conn.execute(text(CREATE_ALL_GEO_TABLES))
conn.commit()
```

Cria `dim_geo_entity`, `dim_geo_relationship` e `dim_geo_code` com índices otimizados para lookup por código e consultas de hierarquia.

## Por que um modelo canônico?

Sem `quantilica-catalog`, cruzar IPCA (BCB-SGS) com temperatura por estado (INMET) exige renomear colunas de cada fonte, decidir um identificador de indicador ad hoc e alinhar a granularidade geográfica manualmente.

O catálogo faz isso **uma vez**, tornando os dados inter-operáveis por construção: um `JOIN` em `indicator_id` + `geo_id` + `date` une qualquer par de fontes.

## Saiba Mais

- [quantilica-io](quantilica-io.md) — os `DataContract`s em que o catálogo se apoia
- [Proveniência & Manifestos](../concepts/proveniencia.md) — rastreabilidade dos dados
- [Cookbook — Análise econômica multi-fonte](../cookbook/analise-economica-multi-fonte.md)

## Repositório

[github.com/Quantilica/quantilica-catalog](https://github.com/Quantilica/quantilica-catalog) — MIT.
112 changes: 112 additions & 0 deletions docs/fundacoes/quantilica-cloud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: quantilica-cloud
description: Plugin CLI para sincronizar manifestos de download com o catálogo Quantilica Cloud.
---

# `quantilica-cloud`

Plugin do `quantilica-cli` que sincroniza manifestos de download (`*.manifest.json`) locais com um catálogo na nuvem. A coleta de dados **nunca depende** deste pacote — sincronizar é sempre opt-in. Uma vez instalado, os comandos ficam disponíveis sob `quantilica cloud`.

> **Por que separado?** A filosofia da Quantilica é offline-first: fetchers, manifestos e armazenamento local funcionam sem rede externa. Enviar manifestos à nuvem é uma camada adicional de conveniência, nunca uma dependência.

## Instalação

```bash
uv add "quantilica-cloud @ git+https://github.com/Quantilica/quantilica-cloud.git"
```

Requer `quantilica-cli` instalado para que os comandos `quantilica cloud` funcionem.

## CLI

### Autenticar

```bash
quantilica cloud login --api-key <chave>
```

Grava as credenciais em `~/.quantilica/credentials.toml` com permissão restrita ao dono (`chmod 0o600`). Para apontar a um servidor próprio (self-hosted):

```bash
quantilica cloud login --api-key <chave> --endpoint http://localhost:8000
```

### Sincronizar manifestos

```bash
# Enviar todos os manifestos na pasta /data
quantilica cloud sync --root /data

# Só os dos últimos 7 dias
quantilica cloud sync --root /data --since 7d

# Simular sem enviar
quantilica cloud sync --root /data --since 7d --dry-run
```

O sync varre recursivamente o diretório em busca de arquivos `*.manifest.json`, agrupa-os em lotes de 100 e envia via `POST /v1/manifests:batch`. O servidor é idempotente — reenviar o mesmo manifesto não duplica registros.

### Ver status

```bash
quantilica cloud status --root /data
```

Compara a contagem de manifestos locais com o catálogo na nuvem e exibe o endpoint configurado.

## Referência de opções

| Comando | Opção | Padrão | Descrição |
|---|---|---|---|
| `login` | `--api-key` | _(obrigatório)_ | Chave de API da Quantilica Cloud |
| `login` | `--endpoint` | `https://api.quantilica.com` | URL da API |
| `sync` | `-r / --root` | `.` (diretório atual) | Diretório raiz a varrer |
| `sync` | `--since` | _(todos)_ | Filtro por data: `7d`, `30d`, etc. |
| `sync` | `--dry-run` | `False` | Simula sem enviar |
| `status` | `-r / --root` | `.` | Diretório raiz a varrer |

## Credenciais

As credenciais ficam em `~/.quantilica/credentials.toml`:

```toml
[cloud]
api_key = "sk-..."
endpoint = "https://api.quantilica.com"
```

Não coloque esse arquivo no controle de versão.

## Como está registrado

`quantilica-cloud` usa o grupo de entry points `quantilica.commands` (não `quantilica.fetchers`), por isso os comandos aparecem na raiz do CLI como `quantilica cloud`, e não como `quantilica fetch cloud`:

```toml
[project.entry-points."quantilica.commands"]
cloud = "quantilica_cloud.plugin:app"
```

## Módulos

| Módulo | Responsabilidade |
|---|---|
| `credentials` | Carga e gravação de credenciais em `~/.quantilica/credentials.toml` |
| `client` | `ManifestSyncClient` — comunica com a API REST (push e listagem) |
| `plugin` | App Typer com os comandos `login`, `sync` e `status` |

## Princípios de Design

1. **Offline-first** — fetchers e armazenamento local funcionam sem este pacote.
2. **Plugin, não dependência** — descoberto via entry point; o CLI não acopla diretamente.
3. **Idempotente** — reenvio do mesmo manifesto SHA-256 não duplica registros.
4. **Credenciais locais** — a chave de API fica em `~/.quantilica/`, com permissão `0o600`.

## Saiba Mais

- [Manifestos & Proveniência](../concepts/proveniencia.md) — o que contém um manifesto
- [quantilica-cli](quantilica-cli.md) — arquitetura do host de plugins
- [quantilica-manifests-db](https://github.com/Quantilica/quantilica-manifests-db) — servidor SaaS que recebe os manifestos

## Repositório

[github.com/Quantilica/quantilica-cloud](https://github.com/Quantilica/quantilica-cloud) — MIT.
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ A camada de confiança entre você e os dados públicos do Brasil — para que v
| **[inmet-fetcher](clima/inmet-fetcher.md)** | INMET BDMEP, séries climáticas históricas com encoding limpo |
| **[quantilica-core](fundacoes/quantilica-core.md)** | A fundação: HTTP resiliente, storage atômico, manifestos SHA-256 |
| **[quantilica-io](fundacoes/quantilica-io.md)** | Parquet tipado com proveniência injetada no header |
| **[quantilica-cloud](fundacoes/quantilica-cloud.md)** | Sincronização opt-in de manifestos com o catálogo na nuvem |
| **[quantilica-catalog](fundacoes/quantilica-catalog.md)** | Modelo canônico de observações para cruzamento multi-fonte |

---

Expand Down
18 changes: 9 additions & 9 deletions docs/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ Um snapshot informal do estado conhecido das fontes oficiais brasileiras que os

| Fonte | Status atual | Última verificação | Observações |
|---|---|---|---|
| **IBGE SIDRA** (`apisidra.ibge.gov.br`) | ✅ estável | 2026-05-10 | Rate limit não documentado; preferir horário não-comercial |
| **IBGE Agregados v3** (`servicodados.ibge.gov.br`) | ✅ estável | 2026-05-10 | Janelas curtas de 502 em manutenção |
| **DATASUS FTP** (`ftp.datasus.gov.br`) | ⚠️ intermitente | 2026-05-10 | Cai frequentemente em horário comercial; reduzir `--threads` |
| **Tesouro Transparente CKAN** | ✅ estável | 2026-05-10 | Dataset com timestamp no nome |
| **INMET BDMEP** | ✅ estável | 2026-05-10 | ZIPs por ano; latin-1; valores `-9999` |
| **Siscomex** (Comex) | ⚠️ instável | 2026-05-10 | SSL ruim em janelas curtas; arquivos GB |
| **PDET FTP** (`ftp.mtps.gov.br`) | ⚠️ intermitente | 2026-05-10 | Cai com frequência; CAGED schema diferente em 2020+ |
| **Tesouro Nacional RTN** | ✅ estável | 2026-05-10 | Excel multi-aba publicado mensalmente |
| **IBGE SIDRA** (`apisidra.ibge.gov.br`) | ✅ estável | 2026-05-17 | Rate limit não documentado; preferir horário não-comercial |
| **IBGE Agregados v3** (`servicodados.ibge.gov.br`) | ✅ estável | 2026-05-17 | Janelas curtas de 502 em manutenção |
| **DATASUS FTP** (`ftp.datasus.gov.br`) | ⚠️ intermitente | 2026-05-17 | Cai frequentemente em horário comercial; reduzir `--threads` |
| **Tesouro Transparente CKAN** | ✅ estável | 2026-05-17 | Dataset com timestamp no nome |
| **INMET BDMEP** | ✅ estável | 2026-05-17 | ZIPs por ano; latin-1; valores `-9999` |
| **Siscomex** (Comex) | ⚠️ instável | 2026-05-17 | SSL ruim em janelas curtas; arquivos GB |
| **PDET FTP** (`ftp.mtps.gov.br`) | ⚠️ intermitente | 2026-05-17 | Cai com frequência; CAGED schema diferente em 2020+ |
| **Tesouro Nacional RTN** | ✅ estável | 2026-05-17 | Excel multi-aba publicado mensalmente |

**Legenda:**
- ✅ **Estável** — funciona sem intervenção; eventuais 502 curtos são normais.
Expand Down Expand Up @@ -56,4 +56,4 @@ Encontrou uma fonte fora do ar que esta página marca como estável (ou o contr

---

*Última atualização manual desta página: 2026-05-10.*
*Última atualização manual desta página: 2026-05-17.*
Loading
Loading