Metadata-Version: 2.4
Name: receitapdf
Version: 0.2.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Rust
Classifier: Topic :: Office/Business
License-File: LICENSE
License-File: LICENSE-DejaVu.txt
Summary: Geração de PDFs dos modelos eletrônicos de receituários da Anvisa
License-Expression: MIT
Requires-Python: >=3.13
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# receitapdf

Biblioteca Rust e CLI que recebem os dados já estruturados de uma prescrição e produzem os bytes do PDF de um modelo eletrônico publicado pela Anvisa. Este componente não solicita números ao SNCR, não decide conteúdo clínico e não assina documentos.

## Modelos e arquivos oficiais

Foram implementados os oito modelos disponíveis na seção de receituários eletrônicos da Anvisa:

- Notificação de Receita A, B e B2;
- Notificação de Receita Especial para retinoides sistêmicos;
- Notificação de Receita de Talidomida;
- Notificações A e B veterinárias;
- Receita de Controle Especial (RCE).

Os PDFs originais foram baixados da [página oficial dos modelos eletrônicos](https://www.gov.br/anvisa/pt-br/assuntos/medicamentos/controlados/sncr/receituario-eletronico/modelos-de-receituarios-eletronicos) e estão em `assets/anvisa-v2/`. São arquivos de uma página, com MediaBox de 595,5 × 842,25 pontos (aproximadamente 210 × 297 mm). A inspeção dos oito arquivos confirmou que não há AcroForms nem anotações/campos preenchíveis. Por isso, o gerador preserva cada PDF oficial como fundo vetorial e acrescenta uma camada de texto e QR. Não tenta editar um PDF existente por meio do `printpdf`: a composição é feita com `printpdf` para criar a camada e `lopdf` para incorporá-la ao modelo original. Para as Notificações, somente o marcador de QR da arte original é coberto pelo QR oficial.

O layout, as caixas e os rótulos impressos permanecem os do arquivo Anvisa correspondente; não há logotipo, cor ou identidade visual adicionada pelo componente. A camada de texto usa DejaVu Sans incorporada ao PDF para manter caracteres acentuados; o link de verificação usa DejaVu Sans Bold Oblique, em negrito itálico como as URLs impressas no rodapé oficial. O código-fonte do projeto está sob MIT ([LICENSE](LICENSE)); os modelos oficiais da Anvisa e a fonte DejaVu mantêm seus próprios termos e avisos. Consulte os termos na página de origem antes de redistribuir os modelos.

## QR Code

Conforme a [orientação oficial da Anvisa para o receituário eletrônico](https://www.gov.br/anvisa/pt-br/assuntos/medicamentos/controlados/sncr/receituario-eletronico), as Notificações de Receita recebem um QR com esta URL, usando o número SNCR já recebido na entrada e validado no formato `0000.0-00.0000000`:

```text
https://sncr.anvisa.gov.br/receita/consultar?numero={numero}
```

Não é criado QR para a Receita de Controle Especial, pois a orientação consultada não publica conteúdo de consulta para esse documento. O campo de número da RCE é opcional e, quando fornecido, não é tratado como número SNCR.

## API Rust

```rust
use receitapdf::{gerar_pdf, Receita};

fn gerar(receita: &Receita) -> Result<Vec<u8>, receitapdf::Erro> {
    gerar_pdf(receita)
}
```

`gerar_pdf` valida os dados requeridos pelo modelo, o formato do número das Notificações, os telefones, a capacidade dos campos e a geração do QR, e retorna erro em vez de serializar campos que excedam o espaço configurado. `url_qr` expõe a URL usada pela Notificação e retorna `None` para RCE.

Telefones (`prescritor.telefone`, `paciente.telefone` e `telefone` dos responsáveis) devem estar em E.164, sem espaços ou pontuação (`+5571930000000`); qualquer outro formato retorna `Erro::TelefoneInvalido` (`ValueError` em Python). No PDF, números do Brasil aparecem no formato brasileiro, como `(71) 93000-0000` ou `(71) 3000-0000`; números de outros países permanecem em E.164. `prescritor.telefone` é obrigatório, e os demais só são validados quando preenchidos.

## Extensão Python

O pacote Python exige CPython 3.13 ou superior. A extensão usa a ABI estável `abi3` com mínimo 3.13, então um wheel CPython pode ser usado também em versões futuras compatíveis. É necessário ter Rust/Cargo disponível ao compilar a partir do código-fonte; o `maturin` é instalado automaticamente pelo backend PEP 517:

```sh
python3.13 -m pip install .
```

Uso a partir de um dicionário Python:

```python
import json
from pathlib import Path

from receitapdf import gerar_pdf, url_qr

prescricao = json.loads(Path("prescricao.json").read_text(encoding="utf-8"))
pdf: bytes = gerar_pdf(prescricao)
Path("saida.pdf").write_bytes(pdf)

# Retorna None para a Receita de Controle Especial.
url: str | None = url_qr(prescricao)
```

`gerar_pdf` retorna `bytes`; entradas ausentes, inválidas ou que excedam o espaço disponível levantam `ValueError`. Falhas internas de renderização levantam `RuntimeError`. A dependência PyO3 é opcional para consumidores Rust e é ativada pelo backend de build Python. Para compilar wheel localmente, instale `maturin` e execute `maturin build --release`; os wheels usam a tag `abi3` a partir do CPython 3.13.

### Publicar no PyPI

O workflow [publish-pypi.yml](.github/workflows/publish-pypi.yml) cria o sdist e o wheel Linux x86_64, executa os testes e publica via Trusted Publishing. Para habilitar a publicação, cadastre um publisher pendente nas configurações do PyPI com estes valores:

- Projeto PyPI: `receitapdf`
- Owner do GitHub: `ramon`
- Repositório: `receitapdf`
- Workflow: `.github/workflows/publish-pypi.yml`
- Environment do GitHub: `pypi`

Depois desse cadastro, a publicação pode ser iniciada manualmente em `master` ou ao publicar uma release do GitHub com tag `vX.Y.Z` igual à versão em `pyproject.toml`. O job usa OIDC; não é necessário guardar token do PyPI como secret.

Nos modelos comuns, a posologia começa abaixo do rótulo, pode ocupar várias linhas e respeita margens internas da caixa. Nos boxes de emitente e paciente, cada valor é posicionado com uma distância uniforme abaixo do rótulo correspondente. Os campos da Talidomida seguem as posições e a estrutura próprias desse formulário.

## CLI

Rust está declarado no `mise.toml`. Com `mise` instalado:

```sh
mise install
mise exec rust -- cargo run --release -- examples/notificacao-a.json saida.pdf
```

Também é possível ler o JSON pela entrada padrão:

```sh
cat examples/notificacao-a.json | mise exec rust -- cargo run --release -- - saida.pdf
```

O executável recebe `<entrada.json> <saida.pdf>`; use `-` como entrada para stdin. Os nomes JSON do enum `modelo` são `notificacao_a`, `notificacao_b`, `notificacao_b2`, `retinoides`, `talidomida`, `notificacao_a_veterinaria`, `notificacao_b_veterinaria` e `receita_controle_especial`. Veja [notificacao-a.json](examples/notificacao-a.json) para um exemplo totalmente fictício.

## Conferência visual e testes

As entradas fictícias de cada tipo estão em `examples/visual-review/`; as imagens PNG em `examples/visual-review/png/` foram geradas a partir dos PDFs finais em resolução de 144 dpi. O [contato visual](examples/visual-review/contato.png) mostra as oito páginas. Os PDFs originais correspondentes usados para comparar estão em `assets/anvisa-v2/`.

Os testes cobrem a geração de cada modelo, a URL e os bytes QR usados pela renderização, a exigência/formato do número SNCR, os dados obrigatórios e a rejeição de texto que não cabe. Execute:

```sh
mise exec rust -- cargo test --offline
```

## Integrações externas

O número SNCR deve ser fornecido pela integração chamadora para Notificações. A assinatura eletrônica exigida pelo fluxo da plataforma (incluindo certificado e validações aplicáveis) também é externa: este módulo não cria uma assinatura criptográfica nem registra a dispensação/uso no SNCR.

No rodapé, os rótulos "IDENTIFICAÇÃO DA PLATAFORMA" e "REGISTRO DA ASSINATURA DIGITAL E DATA" são espaços a preencher, não texto fixo, e o PDF os substitui pelos valores: `plataforma` e o registro da assinatura. Pela RDC 1.000/2025 (art. 12), a data da assinatura eletrônica é a data de emissão, então, sem `assinatura`, o registro é `Assinado digitalmente por {prescritor.nome} em {data}`, apenas com a data. Para incluir hora, CPF ou outro dado, informe o texto pronto em `assinatura` (opcional, uma linha; se não couber, `Erro::TextoNaoCabe`). O rótulo "ESTA PRESCRIÇÃO PODE SER VERIFICADA EM:" permanece, e `link_verificacao` (obrigatório) é escrito ao lado dele, em negrito itálico; como o campo tem uma linha, um link que não caiba no espaço gera `Erro::TextoNaoCabe`.

