Metadata-Version: 2.5
Name: portnoie
Version: 0.2.1
Summary: Reproducible PortNOIE interface with experimental pure-PyTorch and legacy backends
Project-URL: Homepage, https://github.com/FORMAS/PortNOIE
Project-URL: Repository, https://github.com/FORMAS/PortNOIE
Project-URL: Issues, https://github.com/FORMAS/PortNOIE/issues
Project-URL: PyPI, https://pypi.org/project/portnoie/
Author: Bruno Souza Cabral, Marlo Vieira dos Santos e Souza, Daniela Barreiro Claro
License-Expression: GPL-3.0-only
License-File: LICENSE
License-File: NOTICE
Keywords: nlp,open information extraction,portnoie,portuguese
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Natural Language :: Portuguese
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: <3.14,>=3.10
Requires-Dist: huggingface-hub<2,>=0.10.1
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: check-wheel-contents>=0.6; extra == 'dev'
Requires-Dist: pytest<9,>=7; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: twine>=7; extra == 'dev'
Provides-Extra: legacy
Requires-Dist: allennlp-models==2.10.1; (python_version == '3.10') and extra == 'legacy'
Requires-Dist: allennlp==2.10.1; (python_version == '3.10') and extra == 'legacy'
Requires-Dist: diskcache<6,>=5; (python_version == '3.10') and extra == 'legacy'
Requires-Dist: flair==0.12.2; (python_version == '3.10') and extra == 'legacy'
Requires-Dist: huggingface-hub==0.10.1; (python_version == '3.10') and extra == 'legacy'
Requires-Dist: numpy<1.24; (python_version == '3.10') and extra == 'legacy'
Requires-Dist: overrides<4,>=3.1; (python_version == '3.10') and extra == 'legacy'
Requires-Dist: spacy<3.4,>=3.3; (python_version == '3.10') and extra == 'legacy'
Requires-Dist: torch==1.12.1; (python_version == '3.10') and extra == 'legacy'
Requires-Dist: torchvision==0.13.1; (python_version == '3.10') and extra == 'legacy'
Requires-Dist: transformers==4.20.1; (python_version == '3.10') and extra == 'legacy'
Provides-Extra: modern
Requires-Dist: click<9,>=8.2.1; extra == 'modern'
Requires-Dist: huggingface-hub<2,>=0.34; extra == 'modern'
Requires-Dist: spacy<3.9,>=3.8.14; extra == 'modern'
Requires-Dist: torch<3,>=2.10; extra == 'modern'
Description-Content-Type: text/markdown

# PortNOIE

Interface reproduzível para o **PortNOIE**, sistema de Open Information
Extraction (Open IE) em português desenvolvido no doutorado de Bruno Souza
Cabral com Marlo Vieira dos Santos e Souza e Daniela Barreiro Claro.

Esta distribuição oferece dois caminhos de inferência:

- `modern` (padrão): migração **experimental** do núcleo neural em PyTorch, sem
  AllenNLP, Flair nem os dois arquivos externos de language model;
- `legacy`: implementação AllenNLP histórica, preservada somente para
  comparações de compatibilidade.

Ambos usam o checkpoint histórico de [`bratao/PortNOIE` no Hugging Face](https://huggingface.co/bratao/PortNOIE),
baixado automaticamente e mantido em cache na primeira extração. A API normaliza a saída em triplas
`ARG0`, `V` e `ARG1` e remove pontuação marginal dos três campos.

> O wheel instalado sem extras contém somente a API leve e as verificações de
> integridade; sozinho ele **não executa inferência**. Use o extra `modern` no
> comando de instalação da release abaixo para habilitar o backend padrão.

> **Não é o melhor BERT da tese.** O `model.th` de 74.832.847 bytes é um
> artefato histórico/de referência: BiLSTM de uma camada, hidden size 384 e
> representações Flair Diário bidirecionais de 1024. O melhor modelo BERT
> descrito na tese será distribuído separadamente quando sua validação terminar.

## Estado do backend moderno

O backend moderno reconstrói em PyTorch o grafo serializado no checkpoint. As
14 matrizes das duas LMs Flair presentes em `model.th` foram comparadas com os
assets históricos: **14/14 eram idênticas com `torch.equal`**. O checkpoint não
guarda o dicionário de 287 caracteres; por isso a distribuição inclui o sidecar
mínimo `modern_data/flair_metadata.json`, com proveniência e checksums dos
arquivos dos quais ele foi derivado. Seu próprio SHA-256 é verificado antes do
carregamento.

O caminho moderno usa `torch.load(..., weights_only=True)` e não abre
`model_params.pkl`. Por segurança, ele exige **Torch 2.10 ou mais novo**; versões
anteriores são bloqueadas por [GHSA-63cw-57p8-fm3p](https://github.com/advisories/GHSA-63cw-57p8-fm3p).
Ele executou uma frase anotada e também texto bruto com o modelo spaCy real, produzindo:

```text
Maria | escreveu | um livro
```

Isso demonstra execução reproduzível sem AllenNLP/Flair em runtime, mas **não
prova equivalência ao legado**. Ainda faltam comparação de logits e avaliação
em corpus entre os dois pipelines. Até lá, o nome retornado pela API é
`PortNOIE-modern-experimental`.

## Instalação moderna

O pacote declara suporte a Python 3.10–3.13. Para o backend padrão, recomenda-se
Python 3.11 ou mais novo:

```bash
python -m venv .venv
source .venv/bin/activate             # Windows: .venv\Scripts\activate
python -m pip install --upgrade pip
python -m pip install "portnoie[modern]"
portnoie download                    # opcional: antecipar o download do checkpoint
portnoie doctor --checkpoint-only
```

O wheel e o sdist não contêm os pesos. Não é necessário fornecer caminho de
modelo: a primeira extração baixa o checkpoint de cerca de **75 MB** para o cache
Hugging Face, na revisão fixada na biblioteca, e verifica os hashes oficiais.
`PortNOIE()` e a importação do pacote não fazem downloads.

Para texto bruto, se `pt_core_news_lg` estiver ausente, `auto_download=True`
(padrão) também instala a wheel oficial `pt_core_news_lg==3.8.0`, de
**568.207.147 bytes**, via `pip` ou `uv` no mesmo Python que executa PortNOIE.
A URL contém o SHA-256 fixado, verificado pelo instalador. Essa primeira chamada
pode demorar e precisa de `pip` no ambiente ou de `uv` no `PATH`.

Em produção offline/controlada, execute `portnoie download` e instale previamente
a [wheel spaCy fixada por SHA-256](https://github.com/explosion/spacy-models/releases/download/pt_core_news_lg-3.8.0/pt_core_news_lg-3.8.0-py3-none-any.whl#sha256=2561c9a72a938d37141e9694e1a36d25061a44ce7e4f3bad2d3fa3bb836191af).
Depois use `PortNOIE(local_files_only=True)`, `PortNOIE(auto_download=False)` ou
`portnoie extract --local-files-only`; todas essas opções impedem os downloads
automáticos tanto do checkpoint quanto do modelo spaCy.

Integrações que já possuem tokens, POS e dependências podem usar
`extract_annotated` e não precisam do modelo spaCy.

## API Python

```python
from portnoie import PortNOIE

extractor = PortNOIE()  # backend="modern"
result = extractor.extract("Maria escreveu um livro.")

for triple in result.triples:
    print(triple.as_dict())
# {'ARG0': 'Maria', 'V': 'escreveu', 'ARG1': 'um livro'}
```

O objeto carrega os pesos e o pipeline spaCy de forma preguiçosa e os reutiliza
nas chamadas seguintes. `result.raw` preserva tokens, anotações, tags BIO e o
identificador explícito do backend. Por padrão, frames incompletos são
descartados; use `PortNOIE(strict=False)` para mantê-los.

Para preparar o cache sem executar inferência:

```python
from portnoie import download_model

download_model()  # baixa só o checkpoint oficial e verifica seus hashes
extractor = PortNOIE(local_files_only=True)  # requer spaCy pré-instalado para texto bruto
```

`cache_dir` é opcional; por padrão vale o cache padrão do Hugging Face, configurável
por `HF_HOME`. `revision` permite selecionar outra revisão, mas não substitui os
hashes oficiais ancorados no pacote.

Para anotações já calculadas:

```python
result = extractor.extract_annotated(
    tokens=["Maria", "escreveu", "um", "livro", "."],
    pos_tags=["PROPN", "VERB", "DET", "NOUN", "PUNCT"],
    dependencies=["nsubj", "ROOT", "det", "obj", "punct"],
)
```

Como no leitor histórico, cada grupo contíguo de tokens `VERB`/`AUX` gera uma
variação de indicador verbal. Labels de dependência desconhecidos usam o label
de fallback `dep` do vocabulário preservado. Predicados multiword e frames com
predicados sobrepostos são consolidados pelo mesmo algoritmo do preditor
histórico. Dígitos viram `0` antes das LMs de caracteres, enquanto o word shape
continua usando o token original.

## Linha de comando e diagnóstico

```bash
portnoie download
portnoie doctor
portnoie extract "Maria escreveu um livro."
echo "Maria escreveu um livro." | portnoie extract --raw
```

`portnoie doctor` não carrega a rede neural nem acessa a rede para baixar arquivos. Ele verifica
faixa de Python, hashes oficiais do checkpoint ancorados no pacote, sidecar,
versão segura de Torch, spaCy e `pt_core_news_lg`. O relatório distingue:

- `modern.annotated_ready`: grafo pronto para anotações fornecidas;
- `modern.raw_text_ready`: também possui spaCy e `pt_core_news_lg`;
- `modern.raw_text_auto_download_available`: pode instalar o modelo na primeira extração;
- `legacy.ready`: ambiente histórico e assets externos completos.

Use `portnoie doctor --checkpoint-only` para validar apenas o checkpoint local ou
em cache. Em uma instalação nova do wheel, o resultado esperado antes do primeiro
download é `checkpoint.status="not-cached"`, com código de saída 1. Execute
`portnoie download` antes de repetir o diagnóstico. O comando completo também
retorna status diferente de zero quando o backend selecionado não está pronto.

Um `model_dir` customizado é comparado aos hashes oficiais por padrão; o
manifesto que estiver dentro dele não redefine essa raiz de confiança. Somente
artefatos deliberadamente diferentes podem usar `trust_custom_model=True`. Esse
opt-in confia no manifesto customizado para consistência, não para autenticidade,
e é obrigatório ao pular integridade antes de carregar o pickle legado.

## Backend legado, somente para comparação

O legado é explícito e limitado a Python 3.10 por causa de AllenNLP 2.10/Torch
1.12:

> **Não use em produção.** Essa pilha está fora de suporte e contém dependências
> com vulnerabilidades conhecidas. Execute-a somente em ambiente descartável,
> isolado e offline, com os artefatos oficiais previamente baixados/verificados e sem dados não
> confiáveis.

```bash
python3.10 -m pip install -e ".[legacy]"
portnoie download
python3.10 -m spacy download pt_core_news_lg
portnoie doctor --backend legacy
portnoie extract --backend legacy "Maria escreveu um livro."
```

Os extras `modern` e `legacy` fixam linhas incompatíveis de Torch e devem ser
instalados em ambientes virtuais separados.

Esse caminho ainda exige `forward-1024.pt` e `backward-1024.pt`. Informe cópias
locais verificadas:

```bash
export PORTNOIE_FLAIR_FORWARD=/modelos/flair/forward-1024.pt
export PORTNOIE_FLAIR_BACKWARD=/modelos/flair/backward-1024.pt
```

O fallback histórico usa endpoints HTTP e fica desativado por padrão. O opt-in
`--allow-historical-download` existe apenas para reprodução controlada; não é
necessário nem consultado pelo backend moderno.

## Docker

O container usa o backend moderno:

```bash
docker build -t portnoie .
docker run --rm portnoie doctor
docker run --rm portnoie extract "Maria escreveu um livro."
```

Também é possível executar `docker compose run --rm portnoie doctor`. O
Dockerfile não instala AllenNLP/Flair nem baixa os assets Flair externos. Durante o
build, baixa o checkpoint verificado para o cache do usuário não-root e instala
`pt_core_news_lg==3.8.0` pela URL com hash fixado; a imagem pronta pode executar
sem acesso à rede.

## Desenvolvimento

```bash
python -m pip install -e ".[modern,dev]"
portnoie download
ruff check src tests scripts
pytest
python -m build
```

Os testes leves não carregam a rede. Quando Torch está disponível, um teste
offline carrega o checkpoint real e valida a frase anotada; outro percorre o
fluxo de texto bruto com anotações spaCy simuladas. O teste com
`pt_core_news_lg` real é executado quando esse modelo está instalado. A CI roda
o backend moderno em Python 3.10, 3.11, 3.12 e 3.13.

```text
src/portnoie/                 API, CLI, diagnóstico e núcleo moderno
src/portnoie/official.py       revisão e hashes oficiais ancorados no pacote
src/portnoie/modern_data/      sidecar mínimo com proveniência
src/portnoie/model_data/       cópia local opcional; excluída do wheel/sdist
multioie/                     implementação AllenNLP histórica
tests/                        testes unitários e validação offline
scripts/                      verificações de procedência e E2E
```

O relatório e o script reproduzível da comparação Flair 14/14 estão em
[docs/MODERN_VALIDATION.md](https://github.com/FORMAS/PortNOIE/blob/main/docs/MODERN_VALIDATION.md).
O [guia de release](https://github.com/FORMAS/PortNOIE/blob/main/docs/RELEASING.md)
descreve a publicação no PyPI/GitHub e os testes exigidos antes de cada tag.

## Segurança, licença e citação

O backend moderno usa Torch ≥2.10 e carrega somente tensores com
`weights_only=True`. O legado pode abrir o `model_params.pkl`, que é um pickle
capaz de executar código; use somente a cópia oficial verificada contra os
hashes oficiais do pacote. Consulte
[SECURITY.md](https://github.com/FORMAS/PortNOIE/blob/main/SECURITY.md).

Para trabalhos acadêmicos, cite o artigo associado (a referência também está em
[CITATION.cff](https://github.com/FORMAS/PortNOIE/blob/main/CITATION.cff)):

```bibtex
@inproceedings{cabral2022portnoie,
  author = {Cabral, Bruno and Souza, Marlo and Claro, Daniela Barreiro},
  title = {PortNOIE: A Neural Framework for Open Information Extraction for the Portuguese Language},
  booktitle = {Computational Processing of the Portuguese Language (PROPOR 2022)},
  year = {2022},
  doi = {10.1007/978-3-030-98305-5_23}
}
```

A release usa `GPL-3.0-only`. O arquivo `LICENCE` original estava vazio e o metadata
antigo dizia apenas `GPL-3.`; a autorização explícita do mantenedor para publicar o
código, o checkpoint e as matrizes incorporadas foi registrada em 30/08/2026 no
[NOTICE](https://github.com/FORMAS/PortNOIE/blob/main/NOTICE).
