Metadata-Version: 2.5
Name: nomin-connectors
Version: 0.3.0
Summary: Conectores auditáveis de bancas para o registro canônico do Nomin
Classifier: Development Status :: 3 - Alpha
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
Requires-Python: >=3.12
Requires-Dist: boto3<2,>=1.40
Requires-Dist: httpx<1,>=0.27
Requires-Dist: psycopg[binary]<4,>=3.2
Requires-Dist: pydantic<3,>=2.10
Requires-Dist: pymupdf<2,>=1.25
Requires-Dist: selectolax<1,>=0.3
Provides-Extra: dev
Requires-Dist: pyright>=1.1.390; extra == 'dev'
Requires-Dist: pytest<10,>=8.3; extra == 'dev'
Requires-Dist: respx<1,>=0.22; extra == 'dev'
Requires-Dist: ruff<1,>=0.9; extra == 'dev'
Description-Content-Type: text/markdown

# Conectores de bancas do Nomin

Fatias verticais do pipeline de ingestão do Nomin. O pacote compartilha contratos, portas e
runtime entre bancas, mantendo descoberta, classificação e extração específicas para cada
família de fonte.

A primeira integração executável com Windmill está documentada em
[`docs/operations/windmill.md`](docs/operations/windmill.md). A arquitetura de produção e a
separação entre Postgres, S3 e projeções ficam em
[`docs/architecture/pipeline-producao.md`](docs/architecture/pipeline-producao.md).
A geração e a publicação do pacote público estão documentadas em
[`docs/operations/package-publishing.md`](docs/operations/package-publishing.md).

## Escopo implementado

- descoberta completa da tabela legada `Arquivos` e dos blocos Drupal atuais;
- expansão de páginas agregadoras para editais filhos numerados;
- distinção entre PDF público, consulta autenticada e ação temporária;
- captura condicional com `ETag` e `Last-Modified`;
- resultados finais de ampla concorrência, negros, PcD, indígenas, quilombolas e
  hipossuficientes;
- leitura adaptativa de quantidades variáveis de notas e de múltiplas oportunidades no
  mesmo PDF, sem atribuir nomes inventados a componentes desconhecidos;
- catálogo seguro para qualquer outro PDF público: bytes, versão, ato e evidência são
  preservados sem inferir sua semântica específica;
- homologação do resultado;
- resultado retificado de candidato `sub judice`, sem inferir os deslocamentos coletivos;
- prorrogação da validade;
- evidência por linha ou documento, com página, trecho e coordenadas;
- chaves de trabalho, lote e observação determinísticas;
- runtime local com ledger append-only, receipts transacionais e retomada de ciclos;
- artefatos content-addressed em disco e metadados/checkpoints em SQLite.

O conector não grava banco de dados, não acessa consultas individuais e não transforma
ausência em eliminação. Entradas sem perfil conhecido continuam presentes no lote de
descoberta. Perfis legados do Senado permanecem congelados; os perfis adaptativos usam o
contexto descoberto na página e não carregam identificadores fixos de outro concurso.

### Cebraspe

- descoberta do feed JSON público usado pela página de cada certame;
- preservação separada das rendições PDF e HTML/VLibras de um mesmo ato;
- catálogo completo de editais, comunicados, informações, provas e gabaritos;
- descoberta, sem captura automática, de consultas e ações individuais;
- captura condicional restrita aos hosts oficiais `*.cebraspe.org.br`;
- lote canônico de publicação, rendição e evidência documental;
- suporte ao alias histórico `Cespe/UnB` na identificação externa.

O primeiro perfil do Cebraspe cataloga e captura os documentos, mas deliberadamente não
interpreta linhas de resultado ou efeitos jurídicos. Esses layouts serão adicionados como
perfis versionados depois de congelados em corpus próprio.

### Instituto AOCP

- descoberta pela API pública usada pela página de cada certame;
- catálogo separado dos grupos `links` e `publicacoes`;
- remoção de parâmetros de rastreamento sem alterar a identidade lógica do documento;
- captura condicional de PDFs nos hosts oficiais e no bucket público da banca;
- catálogo, sem acesso automático, da Área do Candidato e de ações individuais;
- detecção explícita do desafio anti-bot como `SOURCE_BLOCKED`, nunca como feed vazio;
- perfil documental auditável que preserva o ato e sua evidência sem inventar fatos de
  layouts ainda não congelados.

### IBFC

- descoberta das abas semânticas da página pública de cada evento;
- preservação das datas e seções de cada entrada publicada;
- captura de PDFs e listas HTML nos hosts oficiais do IBFC;
- catálogo, sem acesso automático, de ações autenticadas, temporárias e expiradas;
- lote canônico de processo seletivo, ato publicado, rendição e evidência documental.

O primeiro perfil do IBFC preserva e indexa as publicações sem interpretar linhas de
candidatos, classificações ou efeitos jurídicos de layouts ainda não congelados.

### Quadrix

- descoberta das publicações datadas da página pública de cada certame;
- preservação do edital, situação, cronograma e tabela de vagas como metadados;
- catálogo separado de ações autenticadas ou temporárias, sem acessá-las;
- captura condicional restrita aos hosts da Quadrix e aos hosts oficiais de anexos;
- lote canônico de processo seletivo, ato publicado, rendição e evidência documental.

O primeiro perfil da Quadrix é documental: listas nominais, classificações e efeitos
jurídicos permanecem sem interpretação até que seus layouts sejam congelados e validados.

### Instituto Consulplan

- descoberta completa da tabela datada de publicações de cada certame;
- identificação do concurso pelo caminho estável dos documentos no CDN oficial;
- preservação do visualizador acessível como metadado da mesma rendição PDF;
- catálogo, sem acesso automático, de consultas individuais e recursos temporários;
- captura condicional restrita aos hosts oficiais do Instituto Consulplan;
- lote canônico de processo seletivo, ato publicado, rendição e evidência documental.

O primeiro perfil da Consulplan preserva e indexa as publicações sem interpretar listas
nominais, classificações ou efeitos jurídicos antes de congelar seus layouts em corpus.

### IADES

- descoberta da seção pública `Documentos` de cada processo seletivo;
- identidade estável pelo parâmetro opaco `id` da página `ProcessoSeletivo.aspx`;
- separação da data publicada e do título preservando também o texto original do link;
- catálogo completo de PDFs e outros arquivos públicos, com captura automática dos PDFs;
- captura condicional restrita a `/inscricao/upload/` nos hosts oficiais do IADES;
- lote canônico de processo seletivo, ato publicado, rendição e evidência documental.

O primeiro perfil do IADES preserva e indexa os PDFs sem acessar o Ambiente do Candidato
nem interpretar resultados, classificações ou efeitos jurídicos de layouts ainda não
congelados em corpus próprio.

### Fundação Carlos Chagas (FCC)

- descoberta completa do bloco `Links e Arquivos` em páginas de certame;
- leitura correta do HTML legado em ISO-8859-1 e do horário de atualização de Brasília;
- conversão do visualizador Rybena para a URL canônica do PDF público;
- catálogo, sem acesso automático, do Portal do Candidato e das consultas com captcha;
- captura condicional restrita a `*.concursosfcc.com.br`;
- extração de inscrição alfanumérica, nome, nota final e classificação explicitamente
  publicada por cargo e modalidade (ampla, PcD e lista conjunta NIQ);
- conferência dos totais `Candidato(s) nesta opção` antes de declarar snapshot completo.

As datas de emissão dos relatórios são preservadas como data documental. A data de
atualização da página não é promovida a data de publicação de cada link, pois a FCC não
faz essa associação em todas as entradas.

### Fundação Vunesp

- descoberta da página pública por código estável de projeto, como `PCSP2304`;
- leitura do feed `Editais e Documentos` no subdomínio oficial `documento.vunesp.com.br`;
- sessão HTTP preservada entre a página do projeto e o feed entre subdomínios;
- captura de PDFs servidos por identificadores opacos em `/documento/stream/{token}`;
- catálogo das ações da Área do Candidato sem acessá-las automaticamente;
- captura condicional pelo `ETag` e `Last-Modified` do feed documental;
- lote canônico de processo, ato publicado, rendição e evidência documental;
- bloqueios da CDN registrados como `SOURCE_BLOCKED`, nunca como feed vazio.

O primeiro perfil da Vunesp preserva o catálogo e o texto dos documentos. Resultados e
efeitos jurídicos permanecem sem interpretação até que seus layouts sejam congelados em
um corpus versionado.

### Cesgranrio

- descoberta pelos endpoints públicos de evento e conteúdos usados pelo portal;
- identidade estável por evento, bloco e UUID do conteúdo;
- separação entre a URL lógica e os parâmetros temporários de assinatura;
- renovação automática de uma assinatura expirada antes de uma segunda tentativa;
- catálogo de ações autenticadas sem acesso automático;
- perfil conservador que preserva o PDF como ato publicado sem inventar fatos de layout.

Uso programático:

```python
from nomin_connectors import CesgranrioConnector
from nomin_connectors.contracts import SourceCheckpoint, SourceEndpoint

connector = CesgranrioConnector()
endpoint = SourceEndpoint(
    id="cesgranrio:event:8",
    url="https://concursos.cesgranrio.org.br/portal/avaliacoes/8",
    publisher="Fundação Cesgranrio",
    publisher_role="EXAM_BOARD",
    declared_scope={"campaign_ref": "ipea-01-2023"},
)
batch = connector.discover(endpoint, SourceCheckpoint())
```

Pela linha de comando:

```bash
uv run nomin-cesgranrio discover \
  --endpoint-id cesgranrio:event:8 \
  https://concursos.cesgranrio.org.br/portal/avaliacoes/8
```

### Instituto Avalia

- descoberta pela API JSON pública usada pelo portal moderno de cada concurso;
- suporte às URLs atuais `/concursos/{id}` e às URLs legadas `/concurso.jsp?id={id}`;
- identidade estável pelo ID público da publicação e pelo identificador das ações;
- extração da data que antecede o título de cada publicação;
- captura condicional restrita ao repositório oficial de PDFs;
- catálogo de consultas, boletins e recursos sem abrir ações do candidato;
- lote canônico de processo seletivo, ato publicado, rendição e evidência documental.

O primeiro perfil do Avalia preserva o catálogo e o texto dos PDFs. Resultados nominais,
classificações e efeitos jurídicos permanecem sem interpretação até que seus layouts sejam
congelados e validados em corpus próprio.

```python
from nomin_connectors import AvaliaConnector
from nomin_connectors.contracts import SourceCheckpoint, SourceEndpoint

connector = AvaliaConnector()
endpoint = SourceEndpoint(
    id="avalia:contest:597",
    url="https://www.avalia.org.br/concursos/597",
    publisher="Instituto Avalia de Inovação em Avaliação e Seleção",
    publisher_role="EXAM_BOARD",
    declared_scope={},
)
batch = connector.discover(endpoint, SourceCheckpoint())
```

### IDECAN

- descoberta das publicações em `idecan.selecao.net.br/informacoes/{id}`;
- suporte ao portal novo `portal.concursos.idecan.org.br/edital/ver/{id}`, inclusive quando
  ele declara explicitamente que ainda não há arquivos;
- compatibilidade conservadora com o acervo `concurso.idecan.org.br/Concurso.aspx?ID={id}`;
- preservação das seções, títulos e datas de cada publicação;
- catálogo de ações autenticadas e temporárias sem acessá-las automaticamente;
- captura restrita aos hosts oficiais e às CDNs usadas pelas plataformas;
- desafios anti-bot do acervo histórico registrados como `SOURCE_BLOCKED`;
- lote canônico de processo seletivo, ato publicado, rendição e evidência documental.

O primeiro perfil do IDECAN é documental. Resultados nominais, classificações e efeitos
jurídicos permanecem sem interpretação até que cada layout seja congelado e validado em
corpus próprio.

```python
from nomin_connectors import IdecanConnector
from nomin_connectors.contracts import SourceCheckpoint, SourceEndpoint

connector = IdecanConnector()
endpoint = SourceEndpoint(
    id="idecan:event:31",
    url="https://idecan.selecao.net.br/informacoes/31/",
    publisher="IDECAN",
    publisher_role="EXAM_BOARD",
    declared_scope={},
)
batch = connector.discover(endpoint, SourceCheckpoint())
```

### Fundatec

- descoberta da lista completa em `publicacoes_v2.php`, a partir do identificador numérico
  do concurso;
- extração dos destinos reais encapsulados pelo wrapper legado `janelagrande(...)`;
- preservação da data, do título e do identificador `idpub` de cada entrada;
- captura condicional restrita ao bucket oficial
  `concursos-publicacoes.s3.amazonaws.com`;
- catálogo de consultas individuais e formulários temporários sem acessá-los;
- desafios do AWS WAF registrados como `SOURCE_BLOCKED`, nunca como feed vazio;
- lote canônico de processo seletivo, ato publicado, rendição e evidência documental.

O primeiro perfil da Fundatec preserva e indexa os PDFs sem interpretar resultados,
classificações ou efeitos jurídicos de layouts ainda não congelados em corpus próprio.

## Ambiente

```bash
uv sync --extra dev
uv run pytest
uv run ruff check .
uv run pyright
```

## Uso

Descobrir as publicações atuais:

```bash
uv run nomin-fgv discover \
  --endpoint-id fgv:senado22:notice-3 \
  https://conhecimento.fgv.br/concursos/senado22/3
```

Uma página agregadora também pode ser usada diretamente; os editais filhos são percorridos
no mesmo snapshot:

```bash
uv run nomin-fgv discover \
  --endpoint-id fgv:tjrjservidores25 \
  https://conhecimento.fgv.br/concursos/tjrjservidores25
```

Para um certame do Cebraspe:

```bash
uv run nomin-cebraspe discover \
  --endpoint-id cebraspe:cd_25_ns \
  https://www.cebraspe.org.br/concursos/cd_25_ns/
```

Para um certame do Instituto AOCP:

```bash
uv run nomin-aocp discover \
  --endpoint-id aocp:tjpr2025 \
  https://www.institutoaocp.org.br/concursos/tjpr2025
```

Para um evento do IBFC:

```bash
uv run nomin-ibfc discover \
  --endpoint-id ibfc:event:496 \
  https://concursos.ibfc.org.br/informacoes/496/
```

Para um concurso do Instituto Avalia:

```bash
uv run nomin-avalia discover \
  --endpoint-id avalia:contest:597 \
  https://www.avalia.org.br/concursos/597
```

Para um certame da Quadrix:

```bash
uv run nomin-quadrix discover \
  --endpoint-id quadrix:event:3056 \
  https://quadrix.org.br/informacoes/3056/
```

Para um certame do Instituto Consulplan:

```bash
uv run nomin-consulplan discover \
  --endpoint-id consulplan:contest:1322 \
  'https://www.institutoconsulplan.org.br/kbSimboloMaIs3yAJUu7kDvuACKUlASw=='
```

Para um processo seletivo do IADES:

```bash
uv run nomin-iades discover \
  --endpoint-id iades:process:b1a690968e \
  'https://iades.com.br/inscricao/ProcessoSeletivo.aspx?id=b1a690968e'
```

Para um certame da Fundação Carlos Chagas:

```bash
uv run nomin-fcc discover \
  --endpoint-id fcc:mpeal125 \
  https://www.concursosfcc.com.br/concursos/mpeal125/index.html
```

Para um projeto da Fundação Vunesp:

```bash
uv run nomin-vunesp discover \
  --endpoint-id vunesp:pcsp2304 \
  https://www.vunesp.com.br/PCSP2304
```

Para um concurso da Fundatec:

```bash
uv run nomin-fundatec discover \
  --endpoint-id fundatec:contest:1075 \
  'https://www.fundatec.org.br/portal/concursos/index_concursos.php?concurso=1075'
```

Para um evento do IDECAN:

```bash
uv run nomin-idecan discover \
  --endpoint-id idecan:event:31 \
  https://idecan.selecao.net.br/informacoes/31/
```

Executar um ciclo completo e confirmar os lotes:

```bash
uv run nomin-fgv crawl \
  --endpoint-id fgv:senado22:notice-3 \
  --database ./var/nomin.sqlite3 \
  --artifacts ./var/artifacts \
  --cycle-id fgv-senado22-2026-08-19T1200Z \
  https://conhecimento.fgv.br/concursos/senado22/3
```

O Cebraspe usa o mesmo runtime:

```bash
uv run nomin-cebraspe crawl \
  --endpoint-id cebraspe:cd_25_ns \
  --database ./var/nomin.sqlite3 \
  --artifacts ./var/artifacts \
  --cycle-id cebraspe-cd-25-ns-2026-08-19T1200Z \
  https://www.cebraspe.org.br/concursos/cd_25_ns/
```

A FCC também usa o runtime compartilhado:

```bash
uv run nomin-fcc crawl \
  --endpoint-id fcc:mpeal125 \
  --database ./var/nomin.sqlite3 \
  --artifacts ./var/artifacts \
  --cycle-id fcc-mpeal125-2026-08-19T1200Z \
  https://www.concursosfcc.com.br/concursos/mpeal125/index.html
```

O Instituto AOCP usa o mesmo runtime:

```bash
uv run nomin-aocp crawl \
  --endpoint-id aocp:tjpr2025 \
  --database ./var/nomin.sqlite3 \
  --artifacts ./var/artifacts \
  --cycle-id aocp-tjpr2025-2026-08-19T1200Z \
  https://www.institutoaocp.org.br/concursos/tjpr2025
```

A Vunesp usa o mesmo runtime:

```bash
uv run nomin-vunesp crawl \
  --endpoint-id vunesp:pcsp2304 \
  --database ./var/nomin.sqlite3 \
  --artifacts ./var/artifacts \
  --cycle-id vunesp-pcsp2304-2026-08-19T1200Z \
  https://www.vunesp.com.br/PCSP2304
```

Reutilize o mesmo `--cycle-id` para retomar uma execução interrompida. Um ciclo já
confirmado retorna seu estado sem fazer novas chamadas de rede. Em um novo ciclo, o
checkpoint envia `ETag`/`Last-Modified`; uma resposta `304` registra saúde da fonte sem
recriar artefatos ou lotes.

Extrair um artefato já capturado:

```bash
uv run nomin-fgv extract resultado.pdf \
  --endpoint-id fgv:tjrjservidores25:02 \
  --source-url https://conhecimento.fgv.br/sites/default/files/concursos/arquivo.pdf \
  --source-page-url https://conhecimento.fgv.br/concursos/tjrjservidores25/02 \
  --campaign-ref tjrjservidores25 \
  --notice-number 02 \
  --title "Resultado Final de Aprovados - Ampla Concorrência" \
  --listed-at 2026-06-12 \
  --observed-at 2026-08-19T12:00:00Z
```

Os comandos escrevem JSON em `stdout`. Bytes capturados não fazem parte da serialização do
contrato. A orquestração, persistência dos artefatos e confirmação do lote pertencem ao
runtime de ingestão, fora do módulo do conector.

## Aplicações web

O monorepo contém três unidades independentes em `apps/`:

- `apps/backend`: API Hono em Cloudflare Workers, organizada em
  `domain/application/infrastructure`, com InferDI, OpenAPI e um seam pronto para
  PostgreSQL/Drizzle;
- `apps/portal`: SPA React/Vite com React Router, TanStack Query, Tailwind CSS 4,
  componentes locais shadcn/Base UI e cliente gerado do OpenAPI.
- `apps/admin`: plano de controle local Bun/Hono + React/Vite para catálogo de endpoints,
  fan-out manual e administração de schedules no Windmill. O Admin acessa `ingestion` com
  papel próprio e nunca compartilha suas credenciais com o browser ou o backend público.

A primeira fatia vertical é a busca de correspondências nominais e o radar de filas. O
adapter padrão contém dados demonstrativos e não grava estado; a projeção PostgreSQL deve
ser ligada por request quando o índice nominal de produção estiver disponível.

```bash
bun install
bun run api:generate
bun run dev:all
bun run check
```

Portal local: `http://localhost:5173`. API e documentação: `http://localhost:8787/docs`.
Admin local: `http://127.0.0.1:5174` em desenvolvimento ou `:8788` após o build.
As decisões estão em
[`docs/adr/0010-aplicacoes-web-e-api-em-workers.md`](docs/adr/0010-aplicacoes-web-e-api-em-workers.md).

## Publicar o conector e ativar o Windmill

O Windmill fixa explicitamente a versão de `nomin-connectors`, portanto a ordem operacional
é: validar e publicar o pacote, aguardar sua disponibilidade no PyPI, sincronizar o workspace
e, por último, aplicar as migrações.

Para publicar a versão declarada em `pyproject.toml`, integre as mudanças em `main` e envie
uma tag anotada com a mesma versão:

```bash
uv run ruff check .
uv run pyright
uv run pytest
git push origin main
git tag -a v0.3.0 -m "nomin-connectors 0.3.0"
git push origin v0.3.0
```

A tag dispara `.github/workflows/package.yml`. Aguarde o job **Publish wheel to PyPI** concluir
e confirme que `nomin-connectors==0.3.0` está disponível antes de atualizar o Windmill.

Com o perfil `nomin-cloud` ativo, sincronize primeiro em modo de inspeção e depois aplique:

```bash
cd windmill
wmill workspace switch nomin-cloud
wmill sync push --dry-run --locks-required
wmill sync push --yes --locks-required
```

O sync não envia resources nem secrets, conforme `windmill/wmill.yaml`. Com o resource
`f/nomin/postgres` já configurado no workspace, execute a migração idempotente:

```bash
wmill script run f/nomin/migrate_database \
  --data '{"database":"$res:f/nomin/postgres"}'
```

O resultado esperado informa `status: "migrated"` e lista as versões recém-aplicadas; uma
segunda execução retorna `status: "up_to_date"`. Detalhes de publicação e operação ficam em
[`docs/operations/package-publishing.md`](docs/operations/package-publishing.md) e
[`docs/operations/windmill.md`](docs/operations/windmill.md).

## Decisões

As decisões arquiteturais e os perfis congelados da primeira fatia estão registrados em
[`docs/adr/0003-stack-e-fatia-vertical-do-conector-fgv.md`](docs/adr/0003-stack-e-fatia-vertical-do-conector-fgv.md).
O runtime local e sua semântica transacional estão registrados no
[`docs/adr/0004-runtime-local-append-only.md`](docs/adr/0004-runtime-local-append-only.md).
O desenho de produção com Windmill, Postgres, S3 e projeções de leitura está descrito em
[`docs/architecture/pipeline-producao.md`](docs/architecture/pipeline-producao.md) e registrado
no [`docs/adr/0009-windmill-postgres-s3-e-projecoes-de-leitura.md`](docs/adr/0009-windmill-postgres-s3-e-projecoes-de-leitura.md).
O perfil adaptativo e a descoberta multiformato da FGV estão registrados no
[`docs/adr/0006-fgv-descoberta-multiformato-e-resultados-adaptativos.md`](docs/adr/0006-fgv-descoberta-multiformato-e-resultados-adaptativos.md).
O recorte e a estratégia de descoberta do Cebraspe estão registrados no
[`docs/adr/0005-conector-cebraspe-usa-feed-publico-e-perfil-de-catalogo.md`](docs/adr/0005-conector-cebraspe-usa-feed-publico-e-perfil-de-catalogo.md).
O recorte documental e o tratamento de bloqueios do Instituto AOCP estão registrados no
[`docs/adr/0008-conector-aocp-usa-api-publica-e-perfil-documental.md`](docs/adr/0008-conector-aocp-usa-api-publica-e-perfil-documental.md).
# nomin
