Files
brasiliano/tasks/PLANO.md
T
rodado da105a1a46 feat: liga a derivada grande no visor e registra as derivadas de acesso
- medio/ 1.200 px e grande/ 2.000 px para os 13.059 itens (6,0 GB)
- 9 matrizes com JPEG corrompido viram tarefa; derivadas delas saem da
  imagem do DSpace
- MANIFEST com as contagens novas, reenviado ao bucket
2026-10-01 11:32:52 +02:00

460 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PLANO — Brasiliana Fotográfica → S3
> **Comece por aqui.** Documento de transferência: uma sessão nova consegue
> retomar o trabalho só com este arquivo. Detalhe técnico completo em
> `README.md`.
**Objetivo:** os 13.036 itens da Brasiliana Fotográfica — metadados e imagens
em qualidade de preservação — depositados em `s3://brasiliana`, organizados
como acervo arquivístico (procedência + checksums).
**Autorização:** a Biblioteca Nacional foi consultada. Confirmou que o material
é público e que não tem equipe técnica para fornecer um dump; a coleta por
scraping é o caminho combinado. Ritmo cortês e identificação continuam valendo.
> **Reorganização de pastas em 2026-09-10.** Scripts + estado (catálogo,
> `.sqlite`, `.log`, `.tmp/`, credenciais) mudaram de raiz para `pipeline/`;
> visualizações foram para `web/`; `exemplo-item/` virou `examples/exemplo-item/`;
> `censo.py` virou `archive/censo.py`. Todo comando de terminal abaixo já foi
> atualizado para o novo caminho. Detalhe completo em `README.md` →
> "Estrutura do repositório".
>
> **Mudança de plano em 2026-09-10 (importante, ver 2.6): o achado 2.4 abaixo
> não se sustentou.** Cloudflare passou a bloquear bitstream via curl também
> pra itens sem cache de borda — não só HTML. **Tudo (Fases 2 e 3) roda pelo
> navegador agora, trilha única**, não mais A/B separadas por tipo de nome.
> A Fase 4 (legado) segue via curl normalmente — domínio diferente
> (`objdigital.bn.br`), sem esse bloqueio.
---
## 1. Estado em 2026-09-30
**Coleta concluída em 2026-09-13.** Conferido em 2026-09-30 contra o ledger
(`pipeline/subidor.sqlite`, tabela `enviados`) e a listagem do bucket.
| | Estado |
|---|---|
| Fase 1 (metadados) | ✅ `catalogo.jsonl` com **13.060 itens** (o falso-positivo `/1` não está nele), também no bucket, idêntico ao local |
| Imagem de preservação | ✅ **12.991 itens**, 13.194 arquivos, 127,4 GB (121 itens com 2 a 8 imagens: 203 extras) |
| Sem preservação | ⚠️ **69 itens**: a origem responde HTTP 500 ao bitstream (`errors.md`). Todos, menos o `7523`, têm a miniatura |
| Miniatura do DSpace | ✅ 13.060 arquivos em 13.059 itens |
| Fase 4 (legado objdigital) | ✅ 2.137 arquivos (1.899 JPG, 213 PDF, 25 outros), 9,6 GB |
| Derivados | ✅ `thumb256` 13.059 e `medio` 13.059 |
| Blog | ✅ 636 posts em `wordpress/` + `wordpress-posts.jsonl` |
| Bucket | ✅ 81.269 objetos, 139,2 GB, mais 487 objetos (172 MB) em `descricao/` |
| `MANIFEST.md` | ✅ versão final no bucket (2026-09-30) |
| Descrição NOBRADE | ✅ ver §9, também no bucket em `descricao/` |
| Mural | ✅ no ar em https://xn--2dk.xyz/brasiliana/; o visor NOBRADE está no branch `feat/nobrade` (§9, fase 7) |
Nenhum navegador ou subidor precisa estar rodando: a fila local está zerada.
---
## 2. Cinco achados que não devem ser redescobertos
Cada um destes custou horas. Nenhum é óbvio, e vários são contraintuitivos.
### 2.1 O sitemap está sete anos desatualizado
`htmlmap`/`sitemap` são de abril/2019 e listam ~5.300 dos 13.036 itens. Enumerar
por eles perde **59% do acervo sem erro aparente** — o crawl termina limpo, pela
metade. Fonte correta: `browse?type=title&rpp=100&offset=N`, percorrido por
comunidade (o que de quebra etiqueta a instituição de origem).
### 2.2 O arquivo de preservação está no DSpace, não no objdigital
O `DC.identifier` aponta para `objdigital.bn.br` e parece ser o master. É o
contrário: o bitstream do DSpace tem 5533×4065 (7,4 MB) contra ~2600×2100
(147 KB) do objdigital. O maior medido: 8679×6484, 34,7 MB — EXIF mostra
Hasselblad H3D II-39MS a 7216×5412.
**Inverter isso arquiva o acervo a um quarto da resolução, e nada avisa.**
O PDF de 71,9 MB do item 14582 no objdigital não é a mesma imagem melhor: é
outro objeto, um álbum de várias páginas.
### 2.3 O Cloudflare bloqueia automação de navegador — não IP, não geografia
Medido na mesma máquina, mesmo IP, mesmo instante:
| Cliente | Páginas HTML |
|---|---|
| Chrome/Brave do usuário | ✅ 200 |
| `fetch()` de dentro da página | ✅ 200 (>100 seguidas) |
| Playwright *headed* | ❌ 200 na 1ª, 403 depois |
| Playwright *headless* | ❌ 403 |
| curl / httpx | ❌ 403 |
| Servidor (Hetzner Helsinki) | ❌ 403 |
**Proxies não ajudam** — o bloqueio dispara na segunda requisição da mesma
sessão, não por origem. Testado e descartado. Não repita.
### 2.4 Bitstreams passam por curl; páginas HTML não
`bitstream/...` responde 200 a `curl -L` simples, até com UA de curl. Só o HTML
é desafiado. É isso que permite baixar os 116 GB fora do navegador, num servidor.
### 2.5 Nome de arquivo com percent-encoding leva 403
Testado com cinco user-agents (Chrome, Safari, Firefox, Googlebot, curl):
**todos 403** numa URL com `%28`/`%20`, enquanto `curl/8.7.1` puro dá **200**
numa URL de nome simples. O gatilho é o caminho, não o cliente — variar
user-agent não resolve. O navegador busca os dois casos sem problema.
Daí as duas trilhas — **modelo original, superado, ver 2.6**. Itens da BN
usam `imagem.jpg` (trilha A); os do IMS usam nomes descritivos com espaços
e acentos (trilha B).
### 2.6 O achado 2.4 não se sustentou — Cloudflare bloqueia bitstream também
Testado ao vivo em 2026-09-10: item nunca antes acessado, buscado pelo
navegador (200 OK) e **imediatamente depois** via curl na Finlândia — 403,
página real do Cloudflare ("Attention Required"). O único item que passava
por curl (`6066`) só passava porque já tinha sido baixado com sucesso antes
e ficou em cache de borda (`cf-cache-status: HIT`) — sobra de um caso, não
regra geral. Pré-aquecer a URL pelo navegador antes de tentar via curl
**não** transfere o cache pra tornar o curl aceito (testado, falha igual).
**Consequência: não existe mais trilha A via servidor.** Tudo — nome simples
ou com encoding — precisa passar pelo navegador vivo (`fetch()` de dentro da
página). `baixador.py` na Finlândia fica só pra Fase 4 (objdigital, domínio
diferente, sem esse bloqueio — ver 2.11).
### 2.7 Itens com múltiplos bitstreams reais — não é caso raro
115 itens (de ~13.058) têm **2 a 6 imagens reais** no bundle ORIGINAL (contagem final no ledger: 121 itens, 2 a 8 imagens), não
uma. `citation_pdf_url` não é gerado pra esses (o gerador de citação do
DSpace não sabe qual é "a" imagem quando há mais de uma) — por isso ficam
sem `preservation_url` na extração simples. Sintoma: `full_table` tem
`dc.description` mencionando álbum/página (ex.: "Álbum 0259"), igual ao
caso do PDF de 71,9 MB do achado 2.2 — mesmo fenômeno, mas dentro do
próprio DSpace, não só no objdigital.
**Como enumerar as imagens reais de um item**, em ordem de confiabilidade:
1. Contar `.file-wrapper` na página normal do item (não `?show=full`) — bate
com o número real de bitstreams.
2. Tentar sequências `/bitstream/{PREFIX}/{h}/{seq}/qualquer-nome.jpg` pra
`seq` de 1 até N — funciona quando as sequências são contíguas a partir
de 1 (maioria dos casos).
3. Quando (2) falha (sequências não contíguas): pegar o nome real do
arquivo a partir do `<img src>` de cada `.file-wrapper` (é a miniatura,
nome com `.jpg` duplicado no final — ex. `nome.jpg.jpg`), tirar o último
`.jpg`, e buscar `/bitstream/handle/{PREFIX}/{h}/{nome-real}` (rota por
nome exato, não por sequência).
`preservation_url` = a maior imagem encontrada (compatibilidade com o
pipeline); `all_preservation_urls`/`all_preservation_sizes` guardam todas,
ordenadas da maior pra menor.
**Exceção conhecida sem conserto:** handle `20.500.12156.1/7523` — `HTTP
500` do próprio servidor da BN ao buscar os bitstreams (não é problema de
nome/padrão). Provável arquivo corrompido do lado deles.
### 2.8b Achado 2.8 falha ~62% das vezes em alguns lotes — bitstream de LICENÇA, não thumbnail
`seq_master+1` às vezes é o bitstream de **LICENÇA** do DSpace (texto puro,
"LICENÇA DE VEICULAÇÃO E DISTRIBUIÇÃO..."), não a miniatura — visto em
Marinha e IMS, **2.655 de 4.278 thumbnails** (62%!) baixados errado num
lote, **146 já tinham subido pro bucket** antes de descobrir (limpo).
O filtro de tamanho do v1 (`> 2MB → rejeita`) não pega isso: o texto da
licença é pequeno o bastante (781 bytes) pra passar. **Fix:** validar
magic bytes (JPEG `FFD8`/PNG) antes de aceitar, tentar seq+2/+3 se falhar,
e cair pro método do achado 2.7 (`<img src>` da página) como último
recurso — `baixador-thumb.js` v2. Também blindado no `subidor.py`: rejeita
qualquer upload cujo conteúdo não comece com magic bytes de imagem,
independente do nome do arquivo (defesa em camadas).
**Lição:** tamanho pequeno não é prova de "é a miniatura" — só magic bytes
provam que é imagem de verdade.
### 2.8 Miniatura gerada pelo DSpace: sequência N+1 (quando contígua)
Pra item com bitstream original na sequência N, a miniatura que o DSpace
gera automaticamente costuma ficar na sequência N+1 — confirmado no handle
`6066`: seq 1 = master (7,7 MB), seq 2 = thumb (56 KB). Mesma rota
filename-agnostic do achado 2.4 original (o nome na URL não importa, só a
sequência). Quando as sequências não são contíguas (itens do achado 2.7 via
método 3), a miniatura de cada imagem é o próprio `<img src>` do
`.file-wrapper` correspondente — não precisa de request extra.
### 2.9 objdigital às vezes tem PDF (álbum), não só JPG — extração pegava o identificador errado
Alguns itens têm mais de um `DC.identifier` apontando pro objdigital: um
`.htm` (página de landing) e um `.pdf` (o álbum de verdade, múltiplas
páginas — mesmo fenômeno do PDF de 71,9 MB do item 14582, achado 2.2). A
extração original pegava o **primeiro** identificador que batesse com
"objdigital", que pra 44 itens (a maioria uma sequência do Museu
Aeroespacial, `14526-14570`, mais o `584`) era o `.htm`, não o `.pdf` —
perdendo o conteúdo real. De 235 itens com PDF disponível, 191 já estavam
certos por acaso (o PDF vinha primeiro na lista); só 44 precisaram de
correção manual (feita em `catalogo.jsonl` e no IndexedDB, antes do
`baixador.py --somente-legado` alcançar esses handles — nada foi
contaminado no bucket). **Vale re-checar outros itens com múltiplos
`DC.identifier` de objdigital por precaução, não só os que têm `.pdf`.**
Recheque feito (2026-09-10): mapeamos todos os padrões de extensão em
2.231 itens com 2+ identifiers objdigital. Achados adicionais:
- **5 itens** (`1081, 1082, 1084, 4077, 4084`) têm **só HTML**, sem
`.jpg`/`.pdf` nenhum — são páginas de um visualizador de zoom profundo
(OpenSeadragon/DZI, pirâmide de blocos, não um arquivo único). **3 já
tinham sido baixados e subidos como se fossem imagem** (arquivos de
~2,4 KB, só a página HTML do visualizador) — limpo do bucket, `legacy_url`
zerado pros 5 (não vale a complexidade de espelhar a pirâmide de tiles
pra tão poucos itens).
- **11 itens** têm `.sid` (MrSID, formato comprimido de imagem grande)
além do `.jpg` — não capturado, ficou como gap conhecido (baixo volume,
não priorizado).
### 2.10 O crawl da Fase 1 não é 100% completo — achado via referência cruzada com o WordPress
Cruzando os handles citados nos 636 posts do WordPress contra o catálogo,
2 itens reais (`1799` "Princesa Isabel menina", `4798` "Vista panorâmica de
Canudos...") existem no site mas não foram enumerados pela Fase 1 — ambos
sem `citation_pdf_url`, formato de metadado antigo (um é de 2015-04-16,
uma das primeiras entradas do sistema). Adicionados manualmente ao
catálogo depois de descobertos.
**Isso só foi achado por acaso** (porque um post do blog citava esses
handles especificamente) — não existe verificação exaustiva de que a
enumeração por `browse?type=title` capturou 100% dos itens de cada
comunidade. Pode haver mais itens faltando que nenhum post referencia.
Não investigado a fundo: seria preciso comparar a contagem de cada
comunidade contra alguma fonte independente (a BN não expõe OAI-PMH nem
count via API, até onde sabemos).
### 2.11 O objdigital só responde em HTTPS — não era bloqueio
`legacy_url` na fonte vem como `http://objdigital.bn.br/...`. Conexão HTTP
simples (porta 80) é resetada (`Recv failure: Connection reset by peer`),
local e da Finlândia. HTTPS funciona liso com curl puro, local (não
testado da Finlândia — motivo original de "não alcançável" pode ser outro,
não investigado). Fix: forçar `https://` no `legacy_url` antes de baixar
(já aplicado em `baixador.py`).
### 2.12 Caractere `#` no nome do arquivo corrompe URL no IndexedDB
78 itens (mesmo álbum "Album 0035/0259/0260") têm `#` literal no nome real
do arquivo (ex.: `Alb 0260 199 CT #.jpg`). Algum processo que passou esse
nome por `new URL(...).pathname` sofreu o efeito clássico: `#` não
percentado é tratado como início de **fragmento da URL**, e tudo depois
dele é descartado — a URL salva no IndexedDB virou `.../Alb%20...%20CT%20`
(sem sequência, sem `#`, sem `.jpg`), 404 garantido. O `catalogo.jsonl`
local nunca foi tocado por esse bug (tem a URL certa, com `%23`) — fix é
só reescrever o campo no IndexedDB a partir do catálogo
(`pipeline/reparo-hash-url.js`).
**Lição:** nunca usar `new URL()`/`.pathname` pra manipular nomes de
arquivo vindos de fonte externa sem antes confirmar que não têm `#`, `?`
ou outros caracteres especiais de URL não codificados.
### 2.13 O mesmo `#` quebra de novo — desta vez no redirect do próprio DSpace
Mesmo com a URL do achado 2.12 corrigida no IndexedDB (`%23` correto, forma
antiga `/bitstream/PREFIX/ID/SEQ/arquivo`), os 78 itens continuavam dando
404. O DSpace responde a essa forma antiga com um redirect para a forma
canônica nova (`/bitstream/handle/PREFIX/ID/arquivo`, sem número de
sequência) — e o `Location` do redirect tem o `#` sem escapar de novo,
então o `fetch()` do navegador trunca no fragmento ao seguir o redirect
(`finalUrl` chega faltando `#.jpg`, 404). Fix: pedir direto a forma
canônica (`/bitstream/handle/...`), pulando o redirect — testado, `200`,
JPEG válido. `preservation_url` desses 78 itens reescrito no IndexedDB
para a forma canônica.
**Lição:** com `#` no nome do arquivo, não confiar em nenhum redirect do
servidor — ele pode ter o mesmo bug do lado de lá. Preferir sempre a URL
final/canônica quando ela for conhecida.
### 2.14 Dá pra ler o progresso do BAIXA/BAIXA_THUMB sem o Chrome conectado
O progresso das trilhas via navegador (`localStorage.bras_baixados`,
`bras_thumbs_v2`) sobrevive num arquivo leveldb comum no disco —
`~/Library/Application Support/Google/Chrome/Default/Local Storage/leveldb`
— mesmo com a extensão Claude-in-Chrome desconectada (ex.: conflito de conta
OAuth, sessão anterior perdida). Basta copiar o diretório (evita brigar com
o `LOCK` que o Chrome mantém aberto) e ler com uma lib leveldb qualquer
(`classic-level` via npm, sem dependência nativa externa). O valor vem com
1 byte de prefixo indicando encoding (`0x00`=UTF-16LE, `0x01`=UTF-8/Latin1)
antes do JSON de verdade. Script pronto: `pipeline/tools/ler-localstorage.js`.
**Lição:** perder a conexão com a extensão do navegador não significa perder
visibilidade do progresso — boa parte do estado do app mora em arquivos
comuns no disco do usuário, não só "dentro do navegador".
---
## 3. O acervo
DSpace 4.2, handles `20.500.12156.1/*`. **13.058 itens reais** (13.059
descobertos no crawl, menos 1 falso-positivo — `.../1` é a página raiz da
comunidade, não um item). Contagem original por comunidade abaixo era de
13.036; a diferença é ruído de borda (comunidades ganham/perdem item ao
longo do tempo), não erro de coleta.
| Instituição | Itens | | Instituição | Itens |
|---|--:|---|---|--:|
| Instituto Moreira Salles | 5.133 | | Fiocruz | 548 |
| Biblioteca Nacional | 2.726 | | Marinha (DPHDM) | 456 |
| Museu Aeroespacial | 1.143 | | Arquivo Nacional | 383 |
| Museu da República | 906 | | Arq. Geral da Cidade do RJ | 248 |
| Museu Histórico Nacional | 649 | | Leibniz-Institut, Leipzig | 216 |
| IHGB | 206 | | Fund. Joaquim Nabuco | 190 |
| Museu Goeldi | 95 | | FGV CPDOC | 79 |
| IBGE | 58 | | | |
**~139,5 GB projetados** (recalculado em 2026-09-10 com médias reais de
357-474 arquivos já no bucket por categoria, não mais os 6 originais):
preservação+extras ~136,9 GB (13.152 arquivos, 10,7 MB médio), thumbnails
~0,9 GB, legado ~1,5 GB (taxa de sucesso ~75%, muito 404 genuíno),
metadados+WordPress+catálogo <0,2 GB combinados.
---
## 4. Runbook
### Fase 1 — metadados (navegador) ✅ COMPLETA
Cole `pipeline/coletor-resume.js` (pendentes + erros juntos, numa passada só)
numa aba em `https://brasilianafotografica.bn.gov.br/brasiliana/`. Acompanhar
com `await BRAS.status()`. Deixe a aba **ativa na janela dela**: o Chromium
estrangula `setTimeout` em abas de fundo até 1×/min, e o loop passa a
rastejar sem erro nenhum. Ao terminar: `await BRAS.exportar()`.
### Fases 2 e 3 — trilha única via navegador (não tem mais trilha A/servidor)
Ver achado 2.6. Na mesma aba (mesmo IndexedDB `bras`), cole em sequência:
```js
// pipeline/baixador-navegador.js -- imagem principal, todos os itens
BAIXA.quantos(); BAIXA.iniciar();
// pipeline/baixador-thumb.js -- miniaturas
BAIXA_THUMB.quantos(); BAIXA_THUMB.iniciar();
// pipeline/baixador-extras.js -- 2a+ imagem dos itens com multiplos bitstreams
// (rodar reparo-multibitstream.js antes, se ainda nao rodou)
BAIXA_EXTRA.quantos(); BAIXA_EXTRA.iniciar();
```
Em paralelo, em outro terminal:
```bash
cd pipeline
set -a; . .s3env; set +a
python3 subidor.py --vigiar --catalogo catalogo.jsonl
```
Downloads automáticos do Chrome exigem permissão em
`chrome://settings/content/automaticDownloads` (adicionar
`https://brasilianafotografica.bn.gov.br`) — sem isso o download "funciona"
no JS (sem erro) mas nenhum arquivo chega no disco. Vale pro domínio
inteiro, uma vez só.
### Fase 4 — cópias legadas (máquina local)
```bash
cd pipeline
set -a; . .s3env; set +a
python3 baixador.py --catalogo catalogo.jsonl --somente-legado
```
Via curl direto, sem navegador (ver achado 2.11). ~2.707 itens têm
`legacy_url`, ~328 KB em média.
---
## 5. Arquivos e credenciais
Layout do repo (detalhe em `README.md` → "Estrutura do repositório"):
scripts + estado ao vivo em `pipeline/`, visualizações em `web/`, item de
referência em `examples/`, código morto em `archive/`.
| Arquivo | O quê |
|---|---|
| `tasks/PLANO.md` | Este documento |
| `README.md` | Referência técnica completa (raiz do projeto) |
| `pipeline/coletor.js` | Fase 1 — console do navegador, IndexedDB, autoinício |
| `pipeline/baixador.py` | Fases 2 e 4 — curl → S3, um arquivo por vez |
| `pipeline/baixador-navegador.js` | Fase 3 — navegador → pasta Downloads |
| `pipeline/subidor.py` | Fase 3 — Downloads → S3, apaga na hora |
| `archive/censo.py` | Versão Playwright — **não funciona**, mantida pelo parsing |
| `pipeline/.s3env.exemplo` | Modelo; chaves reais em `/Users/polux/Projetos/tocador/.env` |
**S3:** `hel1.your-objectstorage.com`, bucket `brasiliana` (Hetzner).
Credenciais em `/Users/polux/Projetos/tocador/.env`
(`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`). Cópia local em
`pipeline/.s3env`. Na Finlândia: `/root/.s3env`.
**Servidor:** `ssh finland` → 89.167.95.136 (Hetzner Helsinki, root, 31 GB livres).
---
## 6. Estrutura no bucket
```
s3://brasiliana/
MANIFEST.md o que é, como foi coletado, quando
catalogo.jsonl uma linha por item
itens/20.500.12156.1_6066/
metadata.json Dublin Core + URL de origem + data UTC
preservacao/imagem.jpg 5533×4065, 7,4 MB
legado/icon843660.jpg 147 KB, referência de origem
manifest-sha256.txt checksum de cada arquivo
```
Não negociável: **SHA-256 de tudo** e **URL de origem + data/hora de captura**.
Sem os dois é pasta de imagens, não arquivo.
---
## 7. Pendências
1. ~~Terminar a Fase 1 e exportar o catálogo.~~ ✅ 13.060 itens, 2026-09-13.
2. ~~Medir a fração da trilha B.~~ Obsoleto: desde 2026-09-10 tudo roda por uma
trilha única no navegador (ver 2.6), e a coleta acabou.
3. ~~Confirmar a projeção de ~116 GB.~~ ✅ Medido: 127,4 GB de preservação e
139,2 GB no bucket, somando tudo.
4. ~~Decidir sobre os PDFs de álbum do objdigital.~~ ✅ Guardados como cópia
legada: são 213 PDF entre os 2.137 arquivos de `legado/`.
5. ~~Gerar `MANIFEST.md` e `catalogo.jsonl` no bucket.~~ ✅ `catalogo.jsonl`
no bucket é idêntico ao local (ETag multipart confere); `MANIFEST.md`
finalizado e reenviado em 2026-09-30.
6. Os 69 itens sem preservação dependem da origem voltar a servir o
bitstream (`errors.md`). Não há ação possível do nosso lado.
7. O que falta da descrição e do mural está em §9 (0.2, 7.4). Lista única de
tarefas abertas: [`tasks.md`](tasks.md).
## 8. Direitos
Majoritariamente domínio público por antiguidade, mas as 15 instituições têm
políticas distintas. `dc.rights` e a instituição de origem são preservados
**por item**: a decisão de redistribuir é item a item, não do acervo em bloco.
Arquivar para si é uma coisa; republicar é outra.
## 9. Descrição arquivística (NOBRADE)
Resumo da norma e decisões de aplicação em [`CONARQ.md`](../CONARQ.md).
Implementação no branch `feat/nobrade`: `cd pipeline && python3 -m nobrade && python3 -m nobrade.mural`.
| # | Tarefa | Entrega | Depende de | Estado |
|---|---|---|---|---|
| **0 — Decisões** |||||
| 0.1 | Definir nome da entidade custodiadora e código provisório (`BR RJXYZ`) | `pipeline/nobrade/config.py` | — | ✅ provisório `BR RJXYZ` |
| 0.2 | Pedir código de entidade ao CONARQ | `descricao/pedido-codigo-conarq.md` | 0.1 | 📝 rascunho pronto (formulário CODEARQ, Res. CONARQ 28/2009); envio é do usuário |
| 0.3 | Fixar a convenção do código de referência (`BR RJXYZ.BF.IMS.GF.12345.01`) | `CONARQ.md` §5.2 | 0.1 | ✅ |
| **1 — Normalizadores (cada um gera uma tabela de revisão)** |||||
| 1.1 | Datas: mais de 20 formatos de `dc.date` → forma NOBRADE (`[1860?]`, `[18--]`) + ISO 8601 + original | `descricao/revisao/datas.tsv` | — | ✅ |
| 1.2 | Atribuir data aproximada aos `s.d.` a partir do fotógrafo | coluna em `datas.tsv` | 1.1, 1.4 | ✅ |
| 1.3 | Séries: 2.657 combinações de `dc.source` → séries canônicas por instituição | `descricao/revisao/series.tsv` | — | ✅ |
| 1.4 | Autoridades: fotógrafos (`dc.contributor`) e colecionadores, com variantes deduplicadas | `descricao/autoridades.jsonl` | — | ✅ |
| 1.5 | Suporte/espécie: `dc.type` → gênero + espécie + suporte | `descricao/revisao/tipos.tsv` | — | ✅ |
| 1.6 | Direitos: `dc.rights` → ~4 categorias, mantendo o original | `descricao/revisao/direitos.tsv` | — | ✅ |
| 1.7 | Data tópica: `dc.coverage` → cidade (UF) | campo 1.3 | — | ✅ |
| 1.8 | Assuntos: `dc.subject` separado em pt/en | campo 8.1 | — | ✅ |
| **2 — Montagem da hierarquia** |||||
| 2.1 | `nobrade`: níveis 0→5 com `id`, `parent_id`, `nivel`, `codigo` | `descricao/unidades.jsonl` | 0.3, 1.3 | ✅ |
| 2.2 | Preencher os elementos por nível a partir dos normalizadores | campos `"1.3"`, `"2.1"`… | 1.x, 2.1 | ✅ |
| 2.3 | Nível 5 para os handles com várias imagens | registros filhos | 2.1 | ✅ toda imagem é nível 5 |
| 2.4 | Somar dimensão e suporte nos níveis 1–3 | campo 1.5 agregado | 2.2 | ✅ |
| **3 — Textos dos níveis altos** |||||
| 3.1 | Nível 1: história arquivística, procedência, acesso, arranjo | `descricao/textos/nivel1.md` | — | ✅ |
| 3.2 | Nível 2: breve histórico das 15 instituições | `descricao/textos/secoes/` | — | ✅ fatos conferidos na web, fontes em cada arquivo (2026-09-30) |
| 3.3 | Nível 3: âmbito e conteúdo das séries principais | `descricao/textos/series/` | 1.3 | ✅ resumo automático |
| 3.4 | Biografias de fotógrafos a partir dos posts do WordPress | campo 2.2 das autoridades | 1.4 | ✅ fontes nas autoridades |
| **4 — Relações e fontes** |||||
| 4.1 | 5.1 Originais: instituição + handle + Sophia + objdigital | campo automático | 2.1 | ✅ |
| 4.2 | 5.2 Cópias: preservação, legado, miniaturas no S3 | campo automático | 2.1 | ✅ |
| 4.3 | 5.4 Publicações: posts do WordPress → handles citados | campo 5.4 | — | ✅ |
| **5 — Validação** |||||
| 5.1 | Validador dos 7 obrigatórios por nível | `descricao/pendencias.md` | 2.x | ✅ |
| 5.2 | Rodar, corrigir e repetir até zerar as pendências | relatório limpo | 5.1 | ✅ 0 pendências |
| **6 — Exportação e publicação** |||||
| 6.1 | Exportar EAD 3 | `descricao/ead/brasiliana.xml` | 5.2 | ✅ valida |
| 6.2 | Exportar EAC-CPF | `descricao/eac/*.xml` | 1.4 | ✅ valida |
| 6.3 | Validar contra os XSD | xmllint limpo | 6.1, 6.2 | ✅ |
| 6.4 | Subir `descricao/` para `s3://brasiliana/descricao/` e atualizar o MANIFEST | objetos no bucket | 6.3 | ✅ 487 objetos, 172 MB, com `manifest-sha256.txt`; MANIFEST final (2026-09-30) |
| 6.5 | (opcional) CSV de importação do AtoM | `descricao/atom.csv` | 5.2 | ✅ |
| **7 — Mural** |||||
| 7.1 | Cartão vira visor: painel à esquerda (desktop) ou folha abaixo da imagem (celular); palco com zoom real (roda, pinça, duplo clique, arrastar, + − encaixar, F = só a imagem); barra "← mural \| filtros (n)" no topo do painel para voltar ao Atlas 3D e abrir os filtros por cima do visor | `web/wall.html` | 2.2 | ✅ |
| 7.2 | Ficha arquivística recolhível com os obrigatórios NOBRADE: código (clique copia), nível, arranjo clicável (Brasiliana › instituição › série), data na forma da norma + "na origem", selo "atribuída" nas datas atribuídas pelo arquivista, produtor na forma autorizada ISAAR, dimensão e suporte, original (5.1), reprodução (4.2), citar (copia a referência com o código) | `web/wall.html`, `nobrade/mural.py` | 2.2 | ✅ |
| 7.2b | Filtros reagrupados pela NOBRADE: Arranjo (instituição → série em cascata), Data, Pontos de acesso (autor pela autoridade ISAAR, lugar, assunto, pessoa, coleção citada), Suporte e uso (espécie e suporte da 1.5, e condição de reprodução da 4.2, filtro novo); link direto `#BR RJXYZ…` abre a peça pelo código de referência | `web/wall.html` | 7.2 | ✅ |
| 7.2c | Correções de dados que saíram do visor: ano implausível na origem (ex. `20-12-1395`) vira data ilegível, com nota; datas atribuídas por série ou instituição usam o miolo p10–p90 (`[1933-1943?]` em vez de `[1800-1999?]`); o histograma do mural só usa datas com até 30 anos de amplitude | `nobrade/datas.py`, `montar.py`, `mural.py` | 1.1 | ✅ |
| 7.3 | Publicar em `xyz/brasiliana` | commit | 7.x | ✅ publicado em 30/09 (`index.html` + `atlas-map.json`), no ar em https://xn--2dk.xyz/brasiliana/ |
| 7.4 | Derivadas de acesso `medio/` (1.200 px) e `grande/` (2.000 px), tamanhos pela NARA 2004 e CONARQ Res. 31/2010 | `derivados/medio/`, `derivados/grande/` | 7.1 | ✅ 2026-10-01: 13.059 itens cada, gerados no finland por `pipeline/deriva-acesso.py`; o visor troca para `grande/` no zoom |