Desenvolvimento¶
Como o mapa-da-ciencia é organizado e como contribuir. As regras de colaboração estão no CONTRIBUTING.md.
Arquitetura¶
O pipeline em Python produz os arquivos do contrato de dados, e a interface (Svelte) os lê. O painel local e o site publicado são a mesma interface lendo os mesmos arquivos. Só o painel tem a API, que permite rodar etapas e codificar.
flowchart LR
subgraph fontes[Fontes públicas]
AM[ArticleMeta<br/>SciELO]
OA[OpenAlex]
IMP[CSV/RIS<br/>importados]
end
subgraph pipeline[Pipeline Python · CLI mapa]
C[coletar] --> E[embeddings] --> T[tópicos]
C --> G[geografia]
C --> K[classificar<br/>codebook]
K --> V[validar]
T & G & K & V --> X[exportar]
end
OLL[(Ollama<br/>modelos locais)]
E -.-> OLL
T -.-> OLL
K -.-> OLL
AM & OA & IMP --> C
X --> D[/contrato de dados<br/>saida/dados/*.json/]
D --> P[mapa painel<br/>FastAPI + interface]
D --> S[mapa publicar<br/>site estático]
| Parte | Onde | Papel |
|---|---|---|
| CLI | src/mapa_da_ciencia/cli.py |
Só orquestra; a lógica fica nos módulos |
| API para notebooks | api.py |
Fachada com as mesmas etapas da CLI, devolvendo objetos Python (referência) |
| Configuração | config.py, projeto.py |
mapa.yaml, codebook.yaml e layout da pasta do projeto |
| Reprodutibilidade | manifesto.py |
Registro de cada execução de etapa |
| Coleta | coleta.py, fontes/, documento.py, texto.py |
Orquestração da etapa (coleta.py); buscador com cache (fontes/base.py), ArticleMeta, OpenAlex, importação, deduplicação; o Documento normalizado |
| Tópicos | embeddings.py, topicos/ |
Texto de análise e embeddings com cache; kNN exato, UMAP e HDBSCAN com reatribuição do ruído; palavras-chave (c-TF-IDF), macrotemas, identidade e cores estáveis (paleta OKLCH), rótulos pelo modelo de linguagem; topicos/pipeline.py orquestra a etapa (explicação, ADR 0007) |
| Armazenamento | armazenamento.py |
Corpus em Parquet via DuckDB, com views para consulta (ADR 0006) |
| Rede e máquina | rede.py, recursos.py |
HTTP com truststore; memória, swap e disco |
| Modelos | llm/ |
Interface de provedor e adaptador do Ollama (embeddings, saída estruturada); guarda de memória (memoria.py); cache das respostas no estado.sqlite (cache.py); perfis |
| Contrato | contrato/ |
Modelos Pydantic (a fonte da verdade), exportação única de saida/dados/ (exportar.py), exemplo sintético |
| Painel | servidor/app.py |
FastAPI: interface em /, dados em /dados, API em /api |
| Interface | frontend/ |
SvelteKit; veja o frontend/README.md |
Ambiente¶
uv sync --all-groups # Python, dependências, ferramentas de teste e documentação
uv run pytest # testes
uv run ruff check # lint
uv run ruff format # formatação
uv run mkdocs serve # documentação em http://127.0.0.1:8000 (o aviso do Material sobre o MkDocs 2.0 é deles)
A abertura do site ("Céu que se forma") é um modelo próprio do Material, overrides/home.html, com docs/assets/pagina/{pagina.css,pagina.js,dados.json}; o conteúdo antigo da página inicial está em docs/documentacao.md. Os testes dela rodam sobre o site montado:
uv run mkdocs build && (cd frontend && npm run e2e:pagina)
A interface precisa de Node.js 22.18 ou mais recente só para desenvolvê-la:
cd frontend
npm ci
npm run dev # com os dados de exemplo; veja o README do frontend
Arquivos gerados a partir do código¶
Estes arquivos são gerados e versionados. O CI falha se os três primeiros estiverem desatualizados:
| O quê | Gerado por | Quando regenerar |
|---|---|---|
contrato/schema/*.json, contrato/exemplo/dados/ e contrato/exemplo-publicado/dados/ |
uv run python scripts/gerar_contrato.py |
Ao mudar contrato/modelos.py, o gerador de exemplo ou as regras do mapa publicar |
frontend/src/lib/contrato/tipos.ts |
npm run tipos (em frontend/) |
Depois de regenerar os schemas |
docs/referencia/{cli,configuracao,codebook,contrato,api-http}.md |
uv run python scripts/gerar_referencias.py |
Ao mudar comandos, config.py, o contrato ou as rotas do painel |
docs/assets/pagina/dados.json |
uv run python scripts/gerar_pagina.py projetos/cp-scielo (localmente: o piloto não está no repositório) |
Quando o piloto mudar: as estrelas, os números e as histórias da abertura do site |
notebooks/oficina_colab.ipynb |
uv run python scripts/gerar_notebook.py (conferido em tests/test_notebook.py) |
Ao mudar a versão do pacote ou o roteiro da oficina |
src/mapa_da_ciencia/fontes/scielo-revistas.json |
uv run python scripts/gerar_revistas.py (1 requisição à ArticleMeta) |
Para atualizar a lista de revistas do SciELO Brasil |
src/mapa_da_ciencia/geografia/dados/paises.csv |
node scripts/gerar_paises.ts (nomes do CLDR que vem no Node, sem rede) |
Ao atualizar o Node; o teste confere quando a versão do CLDR é a mesma do cabeçalho do arquivo. As variantes (variantes_paises.csv) e as UFs (ufs.csv) são editadas à mão |
src/mapa_da_ciencia/geografia/dados/municipios.csv |
uv run python scripts/gerar_municipios.py (1 requisição ao IBGE) |
Quando o IBGE criar municípios |
tests/fixtures/articlemeta/ |
uv run python scripts/recortar_fixtures.py (precisa do cache do spike, spikes/saida/brutos/) |
Ao precisar de novos casos de teste; os e-mails reais viram anonimo@exemplo.invalid |
Convenções¶
- Português em tudo: interface, documentação, mensagens, identificadores de domínio (
coletar,topicos,revistas). - Mensagens de erro dizem o que fazer, sem traceback para o usuário (veja
_erros_amigaveisemcli.py). - Commits pequenos, um por tarefa modular, no formato Conventional Commits com escopo:
feat(coleta): …,docs(guias): …. Cada commit leva o código, os testes e a documentação da tarefa, e passa no lint e nos testes. - Branches por marco (
m1-esqueleto,m2-coleta…), com merge emmaine tag ao fim de cada marco. - Decisões técnicas relevantes viram ADR em
docs/decisoes/, com evidência e o script que a reproduz.
Fazer uma release¶
- Na branch do marco, o commit
chore(versao): X.Y.Zmuda a versão nopyproject.toml, noCITATION.cff(com a data), nofrontend/package.jsone no endereço do wheel (README, guia de instalação e tutorial "Explorar o exemplo"), regenera o caderno da oficina (scripts/gerar_notebook.py) e fecha a seção doCHANGELOG.md. Com o CI verde, merge--no-ffnamain, tagvX.Y.Ze push das duas. - Publique a release no GitHub a partir da tag, com a seção do CHANGELOG como notas (
gh release create vX.Y.Z --notes-file …). Para atualizar a demo, anexe o site do piloto comopiloto-publicado.zip(o conteúdo da pasta gerada pormapa publicar, com oindex.htmlna raiz do zip). - O workflow Release (
.github/workflows/release.yml) roda sozinho: confere que a tag bate com a versão, constrói o wheel com a interface, confere os metadados comtwine check(o README vira a página do PyPI, então imagens e links precisam de endereço completo), testa-o sem Node, anexa-o à release e roda de novo o workflow Documentação namain, que publica a demo em/demo/a partir da release mais recente. - PyPI, depois de configurado uma vez: crie o projeto
mapa-da-cienciano PyPI com este repositório e o workflowrelease.ymlcomo trusted publisher (ambientepypi), crie o ambientepypino GitHub (Settings › Environments) e a variável de repositórioPUBLICAR_NO_PYPI=true. A partir daí, cada release também vai para o PyPI. - Zenodo: a integração GitHub–Zenodo está ligada desde a 1.0.1. Cada release publicada ganha um DOI de versão, com os metadados de
.zenodo.json(o arquivamento leva de minutos a quase uma hora; acompanhe em zenodo.org › GitHub), sob o DOI de conceito 10.5281/zenodo.22998585, que está noCITATION.cff, no README, na metodologia e no rodapé da abertura (lido doCITATION.cffporoverrides/hooks.py). Uma release só vira DOI se o.zenodo.jsonfor válido: a licença usa o id do vocabulário do Zenodo (mit).
Marcos¶
| Marco | Entrega | Estado |
|---|---|---|
| M0 | Spikes: fontes, embeddings, modelo de classificação, frontend (ADRs 0001–0005) | concluído |
| M1 | Esqueleto: pacote, CLI, configuração, diagnóstico, contrato, painel, documentação, CI | concluído (v0.1.0) |
| M2 | Coleta: ArticleMeta, OpenAlex, importação, deduplicação, cache | concluído (v0.2.0) |
| M3 | Tópicos com rótulos pelo modelo de linguagem e mapa de documentos | concluído (v0.3.0) |
| M4 | Tópicos no tempo e geografia | concluído (v0.4.0) |
| M5 | Classificação por codebook e validação | concluído (v0.5.0) |
| M6 | Painel completo: rodar etapas pela interface | concluído (v0.6.0) |
| M7 | Publicação, figuras, oficina no Colab, release | concluído (v0.7.0) |
| Página | A abertura do site, "Céu que se forma" | concluído (v1.0.0) |
Armadilhas conhecidas¶
ModuleNotFoundError: No module named 'mapa_da_ciencia' no macOS. O uv marca a pasta .venv como oculta no macOS, e às vezes os arquivos .pth da instalação editável herdam a marca. O Python 3.12+ ignora .pth ocultos por segurança. Para corrigir:
chflags nohidden .venv/lib/python3.*/site-packages/*.pth
Os testes não dependem disso (o pytest usa pythonpath = ["src"]).
Typer embute o Click. Desde a 0.27, o Typer traz a própria cópia do Click (typer._click). Use typer.core.TyperGroup e TyperArgument para inspecionar comandos (veja scripts/gerar_referencias.py).
Não renomeie pastas geradas. Muitos projetos ficam na Mesa ou nos Documentos, que o macOS sincroniza com o iCloud Drive. Trocar uma pasta inteira de nome (a nova entra com o nome da velha) faz o iCloud guardar a nova como "dados 2", e o projeto fica sem saida/dados: aconteceu com o piloto. Para pôr no lugar uma versão nova de uma pasta gerada, escreva-a numa pasta temporária ao lado e use pastas.substituir_conteudo, que troca os arquivos um a um e mantém a pasta.
Cópias "arquivo 2" num repositório no iCloud Drive. Com o próprio repositório na Mesa ou nos Documentos, o iCloud também cria cópias de arquivos que o git, o uv ou o build reescrevem: test_servidor 2.py, index 2.md, workflows 2/, até .git/index 2. São versões antigas dos arquivos, e não duplicatas: um test_servidor 2.py coletado pelo pytest falha com a regra de hoje. O .gitignore ignora esses nomes, o pytest (collect_ignore_glob em tests/conftest.py) e o MkDocs (exclude_docs) também, e o CI falha se algum entrar num commit. Para limpar, mova para fora do repositório os arquivos e pastas com nome terminado em espaço e número que tenham o original ao lado; o jeito definitivo é manter o repositório fora das pastas sincronizadas.