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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
with:
enable-cache: true
- name: Instalar dependências
run: uv sync
run: uv sync --locked
- name: Testes
run: uv run pytest -q
- name: namescommons run --offline sobre o cache montado a partir das fixtures
Expand All @@ -32,7 +32,7 @@ jobs:
- name: namescommons run --offline sobre as amostras das fontes de sources.yaml
run: |
mkdir -p "$RUNNER_TEMP/cache-oficiais"
find tests/fixtures -mindepth 1 -maxdepth 1 -type d ! -name exemplo ! -name planilhas -exec cp -R {} "$RUNNER_TEMP/cache-oficiais/" \;
find tests/fixtures -mindepth 1 -maxdepth 1 -type d ! -name exemplo -exec cp -R {} "$RUNNER_TEMP/cache-oficiais/" \;
uv run namescommons run --offline --cache "$RUNNER_TEMP/cache-oficiais" \
--out "$RUNNER_TEMP/dist-oficiais" --release "$(date -u +%Y.%m)"
for pais in CA ES FR IE NO NZ US; do test -s "$RUNNER_TEMP/dist-oficiais/by_country/$pais.csv"; done
Expand Down
19 changes: 17 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ Dataset mundial de nomes: primeiros nomes e sobrenomes com país, frequência, g

## Stack

Python 3.12, `uv`, `pytest`, `pyarrow`, `pyyaml`, `pycountry`, `xlrd` (XLS, [ADR 0005](docs/adr/0005-leitor-de-planilhas.md)). Build com `hatchling`.
Python 3.12, `uv`, `pytest`, `pyarrow`, `pycountry` e o kit de fontes `benchcommons-tools-dataset` (import `datasettool`), fixado na tag `v0.1.0` do [tools-dataset](https://github.com/BenchCommons/tools-dataset) ([ADR 0013](docs/adr/0013-dependencia-do-tools-dataset.md)). O kit traz o cache de downloads, o leitor de planilhas (com `xlrd` fixado, [ADR 0005](docs/adr/0005-leitor-de-planilhas.md)), a base do adaptador, o registro de fontes e os auxiliares de arquivo do manifesto, e fixa `pyyaml` e `xlrd`. Build com `hatchling`.

O contrato do kit é do tools-dataset ([spec 004 de lá](https://github.com/BenchCommons/tools-dataset/blob/v0.1.0/docs/specs/004-source-toolkit/spec.md)); aqui fica só o que é de nomes ([spec 007](docs/specs/007-tools-dataset/spec.md)). Correção no código genérico é feita lá, numa versão nova, nunca com cópia ou remendo local.

## Comandos

```sh
uv sync # instala as dependências
uv sync --locked # instala as dependências do uv.lock
uv run pytest -q # testes (nunca usam a rede)
uv run namescommons --help # CLI do pipeline
```
Expand All @@ -25,6 +27,19 @@ PYTHONPATH=tests uv run namescommons run --offline --sources tests/fixtures/sour
--adapters adaptadores --cache <cache> --out <saida> --release 2026.10
```

## Atualizar o kit de fontes

Procedimento completo no [ADR 0013](docs/adr/0013-dependencia-do-tools-dataset.md):

1. Troque juntas, em `pyproject.toml`, a versão (`benchcommons-tools-dataset==X.Y.Z`) e a tag (`tag = "vX.Y.Z"` em `[tool.uv.sources]`), e as constantes `VERSAO` e `COMMIT` de `tests/test_tools_dataset.py`.
2. `uv lock --upgrade-package benchcommons-tools-dataset && uv sync --locked`.
3. Confira a integridade: `git ls-remote https://github.com/BenchCommons/tools-dataset 'refs/tags/vX.Y.Z^{}'` dá o mesmo commit do `uv.lock` e de `COMMIT`, e `git tag -v vX.Y.Z` num clone do tools-dataset mostra assinatura válida.
4. `uv run pytest -q` e os dois passos offline da CI; compare as saídas com as da versão anterior (`diff -r`).

Só o `uv` lê a origem git fixada; não instale o projeto com `pip install .` ([tools-dataset#10](https://github.com/BenchCommons/tools-dataset/issues/10)).

O cache do kit recusa entradas que resolvem fora do diretório do cache, inclusive links simbólicos ([tools-dataset#9](https://github.com/BenchCommons/tools-dataset/issues/9)): ponha arquivos baixados à mão no cache por cópia ou link físico.

## Passo anual do INSEE

O INSEE publica cada edição do Fichier des prénoms numa página nova, sem listagem estável da mais recente (conferido em 2026-10: nem a página antiga nem o data.gouv.fr apontam para a nova edição). Quando sair uma edição nova (em geral no começo do ano):
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ Add `--offline` to `ingest` or `run` to use only files already in the cache. Use

The SSA server refuses automated downloads (HTTP 403). Download the national data ZIP in a browser and save it where the error message of `fetch` says, then continue with `--offline` ([spec 003, R4](docs/specs/003-fontes-oficiais/spec.md)).

## Development

```sh
uv sync --locked # installs the locked dependencies, including the source toolkit
uv run pytest -q # tests never use the network
```

The download cache, the spreadsheet reader, the adapter base, the source registry and the manifest file helpers come from the BenchCommons source toolkit, the `benchcommons-tools-dataset` package (import `datasettool`), pinned to a git tag of [tools-dataset](https://github.com/BenchCommons/tools-dataset) ([ADR 0013](docs/adr/0013-dependencia-do-tools-dataset.md)). This repository keeps only what is specific to names: the schema, normalisation, aggregation, variants, the pipeline, the adapters, `sources.yaml` and the CLI ([spec 007](docs/specs/007-tools-dataset/spec.md)). Messages from the toolkit are in English. Files placed in the cache by hand must be copies or hard links, not symbolic links.

## Licences

- Code: Apache-2.0, see [`LICENSE`](LICENSE).
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0005-leitor-de-planilhas.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Status

Aceito (2026-10-12)
Aceito (2026-10-12). Substituído em parte pelo [ADR 0013](0013-dependencia-do-tools-dataset.md) (spec 007): a decisão continua valendo, mas o leitor e a versão fixada do `xlrd` passaram para o pacote `benchcommons-tools-dataset` ([ADR 0005 do tools-dataset](https://github.com/BenchCommons/tools-dataset/blob/v0.1.0/docs/adr/0005-source-toolkit-runtime-dependencies.md)); onde este ADR diz `namescommons.spreadsheet`, leia `datasettool.sources.spreadsheet`.

## Contexto

Expand Down
44 changes: 44 additions & 0 deletions docs/adr/0013-dependencia-do-tools-dataset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 0013. Dependência do kit de fontes do tools-dataset por tag git

## Status

Aceito (2026-10-12). Spec [007](../specs/007-tools-dataset/spec.md). Substitui em parte o [ADR 0005](0005-leitor-de-planilhas.md): a decisão sobre o leitor de planilhas continua valendo, mas o código e a versão fixada do `xlrd` passam a viver no tools-dataset.

## Contexto

O cache de downloads, o leitor de planilhas, a base do adaptador, o registro de fontes e os auxiliares de arquivo do manifesto nasceram neste repositório (specs 002, 003, 003b e 003e). Outros repositórios `dataset-*` da BenchCommons precisam do mesmo código. Em 2026-10-12 o dono decidiu mover a parte genérica para o [tools-dataset](https://github.com/BenchCommons/tools-dataset), como o pacote Python `benchcommons-tools-dataset` (import `datasettool`), definido pela [spec 004 de lá](https://github.com/BenchCommons/tools-dataset/blob/v0.1.0/docs/specs/004-source-toolkit/spec.md). O tools-dataset é o dono desse contrato; este repositório só o consome.

O build precisa continuar reproduzível: o mesmo commit deste repositório deve rodar sempre o mesmo código do kit. O [ADR 0004 do tools-dataset](https://github.com/BenchCommons/tools-dataset/blob/v0.1.0/docs/adr/0004-dataset-repos-depend-on-a-git-tag.md) já compara as opções (PyPI, wheel em release, submódulo ou cópia, branch, tag git) e escolhe a tag git para todos os consumidores.

## Decisão

- Este repositório depende do kit pela versão exata e fixa a origem na tag correspondente, com `uv`:

```toml
[project]
dependencies = ["benchcommons-tools-dataset==0.1.0", ...]

[tool.uv.sources]
benchcommons-tools-dataset = { git = "https://github.com/BenchCommons/tools-dataset", tag = "v0.1.0" }
```

O `uv.lock` versionado registra o commit da tag. Uma branch nunca é usada como origem.
- `pyyaml` e `xlrd` saem das dependências diretas: o código daqui não os importa mais, e o kit os traz fixados (`pyyaml==6.0.3`, `xlrd==2.0.2`, [ADR 0005 do tools-dataset](https://github.com/BenchCommons/tools-dataset/blob/v0.1.0/docs/adr/0005-source-toolkit-runtime-dependencies.md)).
- Correções no código genérico são feitas no tools-dataset, numa versão nova, e chegam aqui por atualização da dependência. Este repositório não guarda cópia nem remendo local do kit.

### Procedimento de atualização

1. Confirmar que a tag nova existe: `gh api repos/BenchCommons/tools-dataset/tags --jq '.[].name'`.
2. Ler as notas da versão e a spec do tools-dataset na tag nova. Enquanto a versão maior for 0, uma versão menor pode quebrar a API.
3. Numa branch, trocar juntas as duas linhas de `pyproject.toml` (`==X.Y.Z` e `tag = "vX.Y.Z"`) e as constantes `VERSAO` e `COMMIT` de `tests/test_tools_dataset.py`, e rodar `uv lock --upgrade-package benchcommons-tools-dataset` e `uv sync --locked`.
4. Conferir a integridade da tag: o commit de `git ls-remote https://github.com/BenchCommons/tools-dataset 'refs/tags/vX.Y.Z^{}'` precisa ser o mesmo que o `uv.lock` registra depois de `#` na fonte do pacote (e o mesmo de `COMMIT`); num clone do tools-dataset, `git tag -v vX.Y.Z` precisa mostrar uma assinatura válida do mantenedor.
5. Rodar `uv run pytest -q` e os dois passos offline da CI, e comparar as saídas com as da versão anterior (`diff -r`). Diferença em saída é mudança de comportamento e precisa de spec ou ADR.
6. Abrir o PR com as duas linhas, o `uv.lock`, as constantes do teste e a evidência dos passos 4 e 5. `tests/test_tools_dataset.py` falha se a versão, a tag, o commit do `uv.lock` e o do pacote instalado divergirem.

## Consequências

- `uv sync` precisa de acesso ao GitHub quando não há cache, como já precisava ao PyPI.
- Só o `uv` lê `[tool.uv.sources]`. Uma instalação por outra ferramenta (`pip install .`, Poetry, pip-tools) procuraria `benchcommons-tools-dataset` no PyPI, onde o nome não está registrado e poderia ser ocupado por terceiros; a reserva do nome ou a regra de instalar só com `uv` é decisão do tools-dataset ([tools-dataset#10](https://github.com/BenchCommons/tools-dataset/issues/10)).
- As mensagens de erro do kit são em inglês; as deste repositório continuam em português. Uma mensagem com várias linhas do registro de fontes pode misturar as duas línguas.
- Uma falha no código genérico só se corrige com uma versão nova do kit (tag de correção), nunca editando a tag existente.
- Os testes do comportamento genérico vivem no tools-dataset; aqui ficam só os testes do que é de nomes e da integração com o kit.
87 changes: 87 additions & 0 deletions docs/specs/007-tools-dataset/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# 007. Adoção do kit de fontes do tools-dataset: plano de implementação

**Objetivo:** trocar as cópias locais do código genérico de fontes pelo pacote `benchcommons-tools-dataset` 0.1.0 (import `datasettool`), sem mudar CLI nem saídas.

**Arquitetura:** `datasettool` entra como dependência fixada na tag `v0.1.0`. `namescommons.adapters` vira uma camada fina: guarda `RawRecord`, `RawLink` e `DEFAULT_PACKAGE`, reexporta os objetos do kit e embrulha `load_adapter` e `check_adapter` com `optional=("links",)`. `namescommons.sources` passa a declarar um `RegistrySchema` com os campos de nomes, a lista de licenças e a regra de uma `primary`, e a fábrica do `Source` daqui. O pipeline chama `datasettool.sources.cache.fetch_resource` com o `User-Agent` deste projeto. O adaptador do INE lê planilhas por `datasettool.sources.spreadsheet`. `build_manifest` usa `datasettool.files.describe_files` com contadores para `.csv` e `.parquet`.

**Stack:** a mesma de antes, com `benchcommons-tools-dataset==0.1.0` no lugar de `pyyaml` e `xlrd` diretos ([ADR 0013](../../adr/0013-dependencia-do-tools-dataset.md)).

**Spec:** [`spec.md`](spec.md). Contrato do kit (dono: tools-dataset): [spec 004](https://github.com/BenchCommons/tools-dataset/blob/v0.1.0/docs/specs/004-source-toolkit/spec.md) e [plano](https://github.com/BenchCommons/tools-dataset/blob/v0.1.0/docs/specs/004-source-toolkit/plan.md) na tag `v0.1.0`. Acompanhamento: [tools-dataset#5](https://github.com/BenchCommons/tools-dataset/issues/5).

## Restrições globais

- Nenhuma cópia nem remendo local do kit. Se algo do kit não servir, a correção é uma issue no tools-dataset.
- Saídas idênticas byte a byte às de `master` (`0153007`) nos dois passos offline da CI e no cache real completo. Linha de base gravada antes de qualquer mudança de código: fixtures em 832 testes verdes, 8 observações no passo do exemplo, 109 no das fontes oficiais, 4.336.051 no cache real (123 s, 2,96 GB de pico).
- Mensagens de nomes continuam em português; só as do kit mudam para o inglês.
- Um arquivo ou teste só é removido depois que o substituto passa: o kit passa 360 testes na tag `v0.1.0` (conferido em 2026-10-12 com `uv run pytest -q` num clone da tag), e a suíte daqui passa com o kit antes de cada remoção.
- `SCHEMA_VERSION` continua `1.2.0`; nenhum campo de saída muda.

## Decisões de implementação

| Decisão | Motivo | Custo se estiver errada |
|---|---|---|
| `namescommons.adapters` reexporta os objetos do kit em vez de os adaptadores importarem de `datasettool` | Os nove adaptadores e os testes continuam com um só ponto de import; `FormatError` é o mesmo objeto, e `except FormatError` pega erros do leitor de planilhas | Uma camada a mais para ler; trocar os imports depois é mecânico |
| `load_adapter` e `check_adapter` embrulhados com `optional=("links",)` e pacote padrão | O kit não tem pacote padrão nem conhece `links`; sem o invólucro, cada chamada teria de repetir os dois | Nenhum: é o uso previsto no plano 2.1 do kit |
| `SourceError = RegistryError` | A CLI e os testes capturam `SourceError`; o nome continua válido e é a mesma classe | Nenhum além de um alias |
| Mensagens dos campos de nomes continuam em português | A spec aceita mudança de língua só no que vem do kit; os testes de nomes ficam iguais | Uma mensagem com vários erros pode misturar português e inglês; traduzir depois é uma issue |
| `parse_count` sem invólucro: o campo padrão vira `count` | Os adaptadores que não passam `field` passam a dizer `count '<5' is neither...`; é mensagem do kit | Nenhum para dados; só o texto do erro |
| `USER_AGENT` em `namescommons/__init__.py`, montado com `make_user_agent` | É o ponto único usado pelo pipeline e por `tools/amostra_wikidata.py`; o texto fica igual ao anterior | Nenhum |
| `pyyaml` e `xlrd` saem das dependências diretas | Nenhum módulo de `src/` os importa; o kit os fixa (ADR 0005 do kit) | Se um módulo daqui voltar a importá-los, precisa declarar de novo |
| `LocalServer` continua em `tests/conftest.py` | O kit não exporta utilidades de teste | Duplicação pequena de código de teste |
| Teste das amostras do INE pelo leitor vai para `tests/test_es_ine_residentes.py` | É o único teste do leitor que o kit não portou (dados CC BY 4.0 daqui, decisão D6 do kit) | Nenhum |
| Testes de registro com campos de nomes ficam; os genéricos saem | O esquema real daqui (pycountry, `strips_accents`) não é o `names_registry.py` de teste do kit | Alguns testes parecidos nos dois repositórios |

## Foco de revisão

1. **Saídas idênticas:** `diff -r` das saídas deste branch contra as de `master` nas fixtures e no cache real (Task 7).
2. **`FormatError` do leitor de planilhas** continua capturado pelo pipeline e pelos testes do INE como erro do domínio (Task 2: `test_reexporta_os_objetos_do_kit`; Task 3).
3. **`User-Agent`** igual ao anterior, byte a byte, no pedido real ao servidor local (Task 4: `test_pipeline_envia_o_user_agent_do_projeto`).
4. **`strips_accents`** continua campo de nomes, com padrão `false`, e as três fontes de `sources.yaml` continuam marcadas (Task 5).
5. **Ordem das mensagens do registro:** nenhum teste confere a ordem das linhas (Task 5).

## Arquivos

| Arquivo | Responsabilidade | Tarefa |
|---|---|---|
| `pyproject.toml`, `uv.lock` | Dependência fixada na tag | 1 |
| `tests/test_tools_dataset.py` | Integração com o kit (R5.3) | 1 a 6 |
| `src/namescommons/adapters/__init__.py` | `RawRecord`, `RawLink`, reexports, invólucros | 2 |
| `src/namescommons/adapters/es_ine_residentes.py` | Leitor de planilhas do kit | 3 |
| `src/namescommons/__init__.py` | `USER_AGENT` do projeto | 4 |
| `src/namescommons/pipeline.py` | `fetch_resource` do kit com `User-Agent` | 4 |
| `tools/amostra_wikidata.py` | `USER_AGENT` de `namescommons` | 4 |
| `src/namescommons/sources.py` | Esquema de nomes sobre o registro do kit | 5 |
| `src/namescommons/manifest.py` | `describe_files` com contadores CSV e Parquet | 6 |
| `.github/workflows/ci.yml` | Tira a exclusão de `tests/fixtures/planilhas` | 3 |
| `AGENTS.md`, `README.md`, `docs/specs/ROADMAP.md` | Documentação | 7 |

### Removidos (R2.6)

| Arquivo ou teste | Substituto no kit (tag `v0.1.0`) |
|---|---|
| `src/namescommons/cache.py` | `datasettool.sources.cache` |
| `src/namescommons/spreadsheet.py` | `datasettool.sources.spreadsheet` |
| `tests/test_cache.py` (22 testes) | `tests/test_cache.py` (28) |
| `tests/test_spreadsheet.py` (43 de 44 testes; `test_r1_3_amostras_do_ine` vai para `tests/test_es_ine_residentes.py`) | `tests/test_spreadsheet.py` (46) |
| `tests/fixtures/planilhas/tipos.xls` | `tests/fixtures/spreadsheets/types.xls`, cópia idêntica |
| `tests/test_manifest.py` (1 teste) | `tests/test_files.py` |
| Em `tests/test_adapters.py`: `test_nome_do_modulo`, `test_carrega_o_adaptador_de_exemplo`, `test_adaptador_inexistente`, `test_adaptador_valido`, `test_adaptador_invalido`, `test_adaptador_sem_atributo` | `tests/test_adapters.py` |
| Em `tests/test_contrato_adaptador.py`: `test_r2_2_parse_count`, `test_r2_2_parse_count_recusa_o_que_nao_conhece`, `test_r2_2_parse_code_traduz_e_recusa_codigo_desconhecido`, `test_r2_6_arquivo_fora_de_resources_lista_os_disponiveis`, `test_r2_6_adaptador_ausente_mostra_o_caminho_esperado`, `test_r2_6_adaptador_quebrado_nao_se_passa_por_ausente`, `test_r2_6_pacote_de_adaptadores_inexistente` | `tests/test_adapters.py` |
| Em `tests/test_sources.py`: `test_arquivo_sem_fontes`, `test_id_com_formato_invalido`, `test_campo_ausente`, `test_id_duplicado`, `test_licenca_nao_textual_vira_source_error`, `test_id_nao_textual_vira_source_error`, `test_entrada_que_nao_e_mapeamento`, `test_arquivo_inexistente`, `test_yaml_malformado`, `test_raiz_que_nao_e_mapeamento`, `test_sources_que_nao_e_lista`, e os casos `retrieved`, `url` e `extra` de `test_campo_invalido` | `tests/test_registry.py` |
| Em `tests/test_sources_extensoes.py`: `test_retrieved_com_hora_e_recusado`, `test_retrieved_com_hora_no_yaml_e_recusado`, `test_url_sem_esquema_http_e_recusada`, `test_url_http_e_https_aceitas`, `test_url_malformada_nomeia_a_fonte_em_vez_de_levantar_value_error`, e o caso `adapter` de `test_campo_opcional_nao_booleano` | `tests/test_registry.py` |
| Em `tests/test_robustez.py`: `test_manifesto_conta_registros_csv_e_nao_linhas_fisicas` | `tests/test_files.py` |

## Cobertura requisito -> tarefa

| Requisito | Tarefa |
|---|---|
| R1.1, R1.2 | 1 (dependência), 3 e 5 (saída de `xlrd` e `pyyaml` quando o último uso some) |
| R2.1, R3.3 | 4 |
| R2.2 | 3 |
| R2.3, R3.1 | 2 |
| R2.4, R3.2 | 5 |
| R2.5, R3.4 | 6 |
| R2.6 | 2 a 6, cada uma remove o que substituiu |
| R3.5, R4 | 7 (verificação), e cada tarefa roda a suíte inteira |
| R5 | 1 a 6 |
| R6 | 7 |
Loading
Loading