nf-key-extractor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +427 -0
- package/benchmarks/README.md +53 -0
- package/dist/cli.cjs +1665 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1644 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +1546 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +141 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +1511 -0
- package/dist/index.js.map +1 -0
- package/package.json +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 devAlphaSystem
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# nf-key-extractor
|
|
2
|
+
|
|
3
|
+
Extração local, somente por CPU, de chaves de acesso validadas de NF-e e NFC-e
|
|
4
|
+
em arquivos PDF locais ou remotos. Primeiro, verifica o texto nativo do PDF;
|
|
5
|
+
depois, Code 128/QR; e usa OCR local somente quando o perfil selecionado permite.
|
|
6
|
+
Os arquivos remotos são baixados para o processo Node.js; a análise do PDF, o
|
|
7
|
+
reconhecimento de códigos de barras e o OCR permanecem locais.
|
|
8
|
+
|
|
9
|
+
O pacote aceita tanto as chaves numéricas legadas de 44 dígitos quanto o formato
|
|
10
|
+
atual de 44 caracteres, introduzido para CNPJs alfanuméricos em julho de 2026.
|
|
11
|
+
|
|
12
|
+
## Instalação
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install nf-key-extractor
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
É necessário usar Node.js 20 ou mais recente. Não é preciso instalar o Tesseract
|
|
19
|
+
no sistema, usar GPU ou serviço externo de extração, baixar modelos durante a
|
|
20
|
+
execução nem fornecer credenciais de API ao pacote. A aplicação que chama o
|
|
21
|
+
pacote pode fornecer credenciais quando a URL do PDF exigir autenticação.
|
|
22
|
+
|
|
23
|
+
## Uma chamada de função
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { extractNFeAccessKeys } from "nf-key-extractor";
|
|
27
|
+
|
|
28
|
+
const result = await extractNFeAccessKeys("/local/path/danfe.pdf", {
|
|
29
|
+
performance: "balanced",
|
|
30
|
+
passes: 2,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
console.log(JSON.stringify(result, null, 2));
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Também são aceitos `Buffer`, `Uint8Array` e `ArrayBuffer`:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const result = await extractNFeAccessKeys(pdfBuffer);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
URLs públicas HTTP/HTTPS de PDFs podem ser informadas diretamente:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const result = await extractNFeAccessKeys("https://documents.example.com/public/danfe.pdf");
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Para uma URL autenticada, use a opção `requestHeaders`, disponível somente na API:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const result = await extractNFeAccessKeys("https://documents.example.com/private/danfe.pdf", {
|
|
52
|
+
requestHeaders: {
|
|
53
|
+
Authorization: "Bearer <token>",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
O pacote não executa fluxos de login nem mantém um repositório de cookies. A
|
|
59
|
+
aplicação chamadora é responsável pelas credenciais fornecidas e pelo estado da
|
|
60
|
+
sessão. Prefira HTTPS sempre que enviar credenciais; cabeçalhos enviados a uma
|
|
61
|
+
URL HTTP inicial não são protegidos por TLS. `requestHeaders` aceita somente
|
|
62
|
+
valores do tipo string e não pode ser usada com um caminho local ou uma entrada
|
|
63
|
+
em memória. Cabeçalhos de roteamento, enquadramento, intervalo e negociação de
|
|
64
|
+
compressão são reservados pelo componente de download e não podem ser
|
|
65
|
+
sobrescritos.
|
|
66
|
+
|
|
67
|
+
### Download de PDFs remotos
|
|
68
|
+
|
|
69
|
+
Somente URLs `http://` e `https://` são aceitas. O componente de download segue
|
|
70
|
+
respostas HTTP 301, 302, 303, 307 e 308, com no máximo cinco redirecionamentos.
|
|
71
|
+
Ele remove `Authorization`, `Cookie` e `Proxy-Authorization` quando um
|
|
72
|
+
redirecionamento muda a origem ou rebaixa HTTPS para HTTP. Os demais cabeçalhos
|
|
73
|
+
fornecidos pela aplicação chamadora são mantidos na solicitação redirecionada.
|
|
74
|
+
|
|
75
|
+
O limite `maxFileSizeBytes` (30 MiB por padrão) é aplicado tanto ao
|
|
76
|
+
`Content-Length` declarado quanto aos bytes recebidos durante a transferência.
|
|
77
|
+
`timeoutMs` abrange o download e a extração em conjunto, enquanto `signal` pode
|
|
78
|
+
cancelar qualquer uma das fases. É a assinatura do PDF, e não a extensão da URL
|
|
79
|
+
ou o tipo de mídia da resposta, que determina se o conteúdo baixado é aceito.
|
|
80
|
+
Falhas de download usam o código de erro `DOWNLOAD_ERROR`. Erros estruturados
|
|
81
|
+
não reproduzem a URL, a string de consulta nem os valores dos cabeçalhos da
|
|
82
|
+
solicitação.
|
|
83
|
+
|
|
84
|
+
Trate URLs remotas como entradas confiáveis. O pacote não é um filtro de SSRF:
|
|
85
|
+
uma URL e seus redirecionamentos podem alcançar qualquer local da rede acessível
|
|
86
|
+
ao processo Node.js. Aplicações que aceitam URLs fornecidas por usuários devem
|
|
87
|
+
aplicar sua própria política de confiança. Quando forem necessárias listas de
|
|
88
|
+
permissão de host, DNS/IP ou redirecionamento, faça o download com um cliente
|
|
89
|
+
controlado pela aplicação e envie os bytes resultantes ao extrator.
|
|
90
|
+
|
|
91
|
+
## Resultado
|
|
92
|
+
|
|
93
|
+
A função retorna um objeto compatível com JSON para sucesso, ausência de
|
|
94
|
+
correspondência, entrada inválida, tempo limite e erros esperados de PDF:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"status": "success",
|
|
99
|
+
"success": true,
|
|
100
|
+
"precisionScore": 0.99,
|
|
101
|
+
"bestMatch": {
|
|
102
|
+
"accessKey": "35260712345678000195550010000000011123456784",
|
|
103
|
+
"documentType": "NFe",
|
|
104
|
+
"model": "55",
|
|
105
|
+
"format": "numeric",
|
|
106
|
+
"isValid": true,
|
|
107
|
+
"precisionScore": 0.99,
|
|
108
|
+
"pages": [1],
|
|
109
|
+
"sources": ["pdf-text"],
|
|
110
|
+
"occurrences": 1,
|
|
111
|
+
"components": {
|
|
112
|
+
"accessKey": "35260712345678000195550010000000011123456784",
|
|
113
|
+
"format": "numeric",
|
|
114
|
+
"stateCode": "35",
|
|
115
|
+
"yearMonth": "2607",
|
|
116
|
+
"year": "26",
|
|
117
|
+
"month": "07",
|
|
118
|
+
"issuerId": "12345678000195",
|
|
119
|
+
"model": "55",
|
|
120
|
+
"documentType": "NFe",
|
|
121
|
+
"series": "001",
|
|
122
|
+
"invoiceNumber": "000000001",
|
|
123
|
+
"emissionType": "1",
|
|
124
|
+
"numericCode": "12345678",
|
|
125
|
+
"checkDigit": 4
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"results": [
|
|
129
|
+
{
|
|
130
|
+
"accessKey": "35260712345678000195550010000000011123456784",
|
|
131
|
+
"documentType": "NFe",
|
|
132
|
+
"model": "55",
|
|
133
|
+
"format": "numeric",
|
|
134
|
+
"isValid": true,
|
|
135
|
+
"precisionScore": 0.99,
|
|
136
|
+
"pages": [1],
|
|
137
|
+
"sources": ["pdf-text"],
|
|
138
|
+
"occurrences": 1,
|
|
139
|
+
"components": {
|
|
140
|
+
"accessKey": "35260712345678000195550010000000011123456784",
|
|
141
|
+
"format": "numeric",
|
|
142
|
+
"stateCode": "35",
|
|
143
|
+
"yearMonth": "2607",
|
|
144
|
+
"year": "26",
|
|
145
|
+
"month": "07",
|
|
146
|
+
"issuerId": "12345678000195",
|
|
147
|
+
"model": "55",
|
|
148
|
+
"documentType": "NFe",
|
|
149
|
+
"series": "001",
|
|
150
|
+
"invoiceNumber": "000000001",
|
|
151
|
+
"emissionType": "1",
|
|
152
|
+
"numericCode": "12345678",
|
|
153
|
+
"checkDigit": 4
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"metadata": {
|
|
158
|
+
"performance": "balanced",
|
|
159
|
+
"ocrMode": "fallback",
|
|
160
|
+
"passesRequested": 2,
|
|
161
|
+
"passesUsed": 0,
|
|
162
|
+
"pagesTotal": 1,
|
|
163
|
+
"pagesProcessed": 1,
|
|
164
|
+
"pagesRendered": 0,
|
|
165
|
+
"ocrPages": 0,
|
|
166
|
+
"fileSizeBytes": 8421,
|
|
167
|
+
"maxPixelsPerPage": 12000000,
|
|
168
|
+
"maxSourceImagePixels": 60000000,
|
|
169
|
+
"durationMs": 12.34,
|
|
170
|
+
"complete": true,
|
|
171
|
+
"confidenceVersion": "1.0.0"
|
|
172
|
+
},
|
|
173
|
+
"warnings": [],
|
|
174
|
+
"error": null
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`results` contém todas as chaves únicas e validadas de NF-e/NFC-e detectadas
|
|
179
|
+
pelas etapas executadas nas páginas processadas. Os perfis `fast` e `balanced`
|
|
180
|
+
podem ignorar etapas de maior custo depois que uma evidência válida mais barata
|
|
181
|
+
é encontrada; use `accurate` ou `ocr: 'always'` quando quiser verificações
|
|
182
|
+
cruzadas adicionais. `bestMatch` é o resultado com a maior pontuação. O
|
|
183
|
+
`precisionScore` do nível superior é conservador: quando várias chaves são
|
|
184
|
+
retornadas, ele corresponde à menor pontuação entre os resultados.
|
|
185
|
+
|
|
186
|
+
Todas as chaves permanecem como strings, portanto zeros à esquerda nunca são
|
|
187
|
+
perdidos.
|
|
188
|
+
|
|
189
|
+
### O que a pontuação significa
|
|
190
|
+
|
|
191
|
+
`precisionScore` é uma pontuação determinística de confiança nas evidências,
|
|
192
|
+
versionada por `metadata.confidenceVersion`. Código de barras/QR, texto nativo
|
|
193
|
+
exato, texto reconstruído e OCR têm pontuações-base diferentes; fontes
|
|
194
|
+
independentes que concordam entre si aumentam a pontuação. Toda chave retornada
|
|
195
|
+
já passou pelas validações de formato, UF, mês, modelo 55/65, número da nota
|
|
196
|
+
diferente de zero, CNPJ/CPF do emitente e dígito verificador oficial por módulo 11.
|
|
197
|
+
|
|
198
|
+
A pontuação não é uma probabilidade calibrada estatisticamente. Meça precisão,
|
|
199
|
+
recall e falsos positivos em um corpus privado representativo antes de usar um
|
|
200
|
+
limiar em automações fiscais.
|
|
201
|
+
|
|
202
|
+
## Opções
|
|
203
|
+
|
|
204
|
+
| Opção | Valores | Padrão | Significado |
|
|
205
|
+
| ---------------------- | ------------------------------ | -------------------- | ----------------------------------------------------------- |
|
|
206
|
+
| `performance` | `fast`, `balanced`, `accurate` | `balanced` | Seleciona escala de renderização, limites e política de OCR |
|
|
207
|
+
| `passes` | inteiro `1..5` | específico do perfil | Número de tentativas visuais distintas de renderização |
|
|
208
|
+
| `ocr` | `never`, `fallback`, `always` | específico do perfil | Controla o OCR local do Tesseract |
|
|
209
|
+
| `maxPages` | inteiro positivo | `10`, `30` ou `50` | Máximo de páginas processadas |
|
|
210
|
+
| `maxFileSizeBytes` | inteiro positivo | 30 MiB | Limite de tamanho da entrada |
|
|
211
|
+
| `maxPixelsPerPage` | inteiro positivo | específico do perfil | Limite de pixels da renderização e de imagens incorporadas |
|
|
212
|
+
| `maxSourceImagePixels` | inteiro positivo | específico do perfil | Limite de pixels da imagem de origem decodificada |
|
|
213
|
+
| `timeoutMs` | `0..3600000` | específico do perfil | Prazo do download/extração; `0` o desabilita |
|
|
214
|
+
| `stopAfterFirst` | booleano | `false` | Interrompe após a primeira chave validada |
|
|
215
|
+
| `requestHeaders` | registro de strings | nenhum | Cabeçalhos exclusivos da API para um download HTTP(S) |
|
|
216
|
+
| `signal` | `AbortSignal` | nenhum | Cancela o download ou a extração local |
|
|
217
|
+
|
|
218
|
+
A etapa de texto nativo sempre é executada e não conta como uma passagem visual.
|
|
219
|
+
Cada passagem usa uma escala ou rotação diferente; o extrator nunca repete uma
|
|
220
|
+
operação idêntica apenas para aumentar a confiança.
|
|
221
|
+
|
|
222
|
+
Perfis:
|
|
223
|
+
|
|
224
|
+
- `fast`: texto nativo e Code 128/QR; OCR desabilitado por padrão.
|
|
225
|
+
- `balanced`: duas estratégias visuais e OCR somente quando o texto/código de
|
|
226
|
+
barras não encontra uma chave válida.
|
|
227
|
+
- `accurate`: resoluções maiores, passagens opcionais com rotação e OCR local
|
|
228
|
+
como alternativa.
|
|
229
|
+
|
|
230
|
+
Os perfis `fast` e `balanced` ignoram o trabalho visual nas páginas que já
|
|
231
|
+
produziram texto válido. O perfil `accurate` ainda executa as passagens de código
|
|
232
|
+
de barras configuradas para fazer uma verificação cruzada independente. O OCR no
|
|
233
|
+
modo `fallback` só é executado nas páginas não resolvidas pelas etapas
|
|
234
|
+
anteriores; `ocr: 'always'` o executa de qualquer forma.
|
|
235
|
+
|
|
236
|
+
## CLI
|
|
237
|
+
|
|
238
|
+
Após a instalação:
|
|
239
|
+
|
|
240
|
+
```text
|
|
241
|
+
nf-key-extractor <file-or-url> [options]
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Por exemplo:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
nf-key-extractor ./nota.pdf --performance balanced --passes 2 --pretty
|
|
248
|
+
nf-key-extractor https://documents.example.com/public/nota.pdf --pretty
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
O argumento posicional pode ser o caminho de um arquivo local ou uma URL pública
|
|
252
|
+
HTTP/HTTPS. Intencionalmente, a CLI não oferece uma opção para credenciais ou
|
|
253
|
+
cabeçalhos de solicitação personalizados; use a API da biblioteca com
|
|
254
|
+
`requestHeaders` para downloads autenticados. Evite tokens de consulta assinados
|
|
255
|
+
em URLs usadas na CLI, pois os argumentos do comando podem ficar visíveis no
|
|
256
|
+
histórico do terminal ou na lista de processos do sistema operacional.
|
|
257
|
+
|
|
258
|
+
Opções disponíveis:
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
--performance fast|balanced|accurate
|
|
262
|
+
--passes 1..5
|
|
263
|
+
--ocr never|fallback|always
|
|
264
|
+
--max-pages N
|
|
265
|
+
--max-file-size BYTES
|
|
266
|
+
--max-pixels N
|
|
267
|
+
--max-source-pixels N
|
|
268
|
+
--timeout-ms N
|
|
269
|
+
--first
|
|
270
|
+
--pretty
|
|
271
|
+
--help
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
O fluxo de saída padrão contém somente JSON. O código de saída `0` significa que
|
|
275
|
+
uma ou mais chaves foram encontradas (ou que a ajuda foi solicitada), `2`
|
|
276
|
+
significa que uma varredura completa não encontrou nenhuma, e `1` indica um erro
|
|
277
|
+
de entrada/processamento ou uma varredura incompleta sem chave.
|
|
278
|
+
|
|
279
|
+
## Chaves de acesso atuais e legadas
|
|
280
|
+
|
|
281
|
+
As chaves numéricas legadas continuam sendo aceitas. A expressão oficial atual é:
|
|
282
|
+
|
|
283
|
+
```text
|
|
284
|
+
^[0-9]{6}[A-Z0-9]{12}[0-9]{26}$
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Somente as primeiras 12 posições do identificador do emitente dentro da chave
|
|
288
|
+
podem ser alfanuméricas; as demais posições continuam numéricas. O cálculo do
|
|
289
|
+
dígito verificador mapeia cada um dos primeiros 43 caracteres para `ASCII - 48`,
|
|
290
|
+
aplica pesos repetidos de 2 a 9, da direita para a esquerda, e depois módulo 11.
|
|
291
|
+
As chaves numéricas são um subconjunto compatível do mesmo algoritmo.
|
|
292
|
+
|
|
293
|
+
O leitor de código de barras usa decodificação genérica de Code 128, portanto
|
|
294
|
+
aceita tanto o Code Set C legado quanto a representação híbrida atual em Code
|
|
295
|
+
Set C/A.
|
|
296
|
+
|
|
297
|
+
Referências oficiais:
|
|
298
|
+
|
|
299
|
+
- [MOC 7.0 - Visão Geral](https://www.confaz.fazenda.gov.br/legislacao/arquivo-manuais/moc7-visao-geral.pdf)
|
|
300
|
+
- [Nota Técnica Conjunta DFe 2025.001 - CNPJ alfanumérico](https://www.nfe.fazenda.gov.br/Portal/exibirArquivo.aspx?conteudo=5ZkvIZt10mQ%3D)
|
|
301
|
+
- [NT 2026.004 v1.01 - NF-e/NFC-e schemas](https://www.nfe.fazenda.gov.br/POrtal/exibirArquivo.aspx?AspxAutoDetectCookieSupport=1&conteudo=BTZQzgsO9Ws%3D)
|
|
302
|
+
|
|
303
|
+
## Validação independente
|
|
304
|
+
|
|
305
|
+
O analisador e o validador fiscais são públicos e não carregam dependências de
|
|
306
|
+
PDF/OCR:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
import { calculateAccessKeyCheckDigit, parseAccessKey, validateAccessKey, validateIssuerIdentifier } from "nf-key-extractor";
|
|
310
|
+
|
|
311
|
+
const validation = validateAccessKey(accessKey);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Comportamento de recursos e segurança
|
|
315
|
+
|
|
316
|
+
- Execução somente por CPU; nenhum backend de GPU é usado.
|
|
317
|
+
- O acesso à rede é usado apenas para baixar uma URL de entrada HTTP/HTTPS. Após
|
|
318
|
+
o download limitado, a extração é local; os bytes do PDF não são enviados a
|
|
319
|
+
um serviço de OCR nem a qualquer outro processador externo.
|
|
320
|
+
- Os dados de idioma do OCR são instalados localmente com o pacote e carregados
|
|
321
|
+
explicitamente do disco, evitando o uso padrão da CDN do Tesseract.
|
|
322
|
+
- Tamanho do arquivo, quantidade de páginas, pixels de imagens incorporadas e de
|
|
323
|
+
renderização, itens de texto, tamanho do texto, prazo e cancelamento têm
|
|
324
|
+
limites definidos.
|
|
325
|
+
- A execução de JavaScript em PDFs é desabilitada.
|
|
326
|
+
- As falhas esperadas são retornadas como JSON estruturado e nunca expõem
|
|
327
|
+
buffers nem objetos internos do PDF.
|
|
328
|
+
|
|
329
|
+
PDFs criptografados que exigem senha são informados como `PASSWORD_REQUIRED`.
|
|
330
|
+
PDFs ou digitalizações gravemente danificados ainda podem produzir `not_found`;
|
|
331
|
+
o pacote não inventa uma chave que não passe pela validação fiscal determinística.
|
|
332
|
+
|
|
333
|
+
Antes da renderização, o PDF.js ignora imagens incorporadas que excedam
|
|
334
|
+
`maxSourceImagePixels`. Os valores padrão dos perfis permitem digitalizações em
|
|
335
|
+
alta resolução sem deixar a decodificação da origem sem limites; aumente o
|
|
336
|
+
limite explicitamente apenas para digitalizações confiáveis e excepcionalmente
|
|
337
|
+
grandes.
|
|
338
|
+
|
|
339
|
+
## Resultados de validação e benchmarks
|
|
340
|
+
|
|
341
|
+
As medições a seguir foram coletadas em 22/07/2026 com Node.js 24.11.0, Windows
|
|
342
|
+
x64 e um AMD Ryzen 5 5600G (12 processadores lógicos). Os tempos absolutos variam
|
|
343
|
+
de uma máquina para outra; use os executores para comparar alterações no mesmo
|
|
344
|
+
ambiente.
|
|
345
|
+
|
|
346
|
+
### Benchmark sintético de correspondência exata
|
|
347
|
+
|
|
348
|
+
Cada cenário usa um PDF gerado com uma chave esperada conhecida. Uma execução de
|
|
349
|
+
aquecimento é excluída das iterações medidas.
|
|
350
|
+
|
|
351
|
+
| Cenário | Perfil / passagens | Iterações | Média | P50 | P95 | Correspondência exata |
|
|
352
|
+
| ------------------- | ------------------ | --------: | --------: | --------: | --------: | --------------------: |
|
|
353
|
+
| Texto nativo do PDF | `fast` / 1 | 20 | 3,09 ms | 3,00 ms | 3,78 ms | 100% |
|
|
354
|
+
| Code 128 | `balanced` / 2 | 10 | 119,44 ms | 117,72 ms | 131,64 ms | 100% |
|
|
355
|
+
| QR de NFC-e | `balanced` / 2 | 10 | 182,75 ms | 174,46 ms | 215,82 ms | 100% |
|
|
356
|
+
| OCR local | `balanced` / 1 | 3 | 724,59 ms | 723,14 ms | 762,74 ms | 100% |
|
|
357
|
+
|
|
358
|
+
### Corpus privado de documentos reais
|
|
359
|
+
|
|
360
|
+
O extrator também foi executado localmente em 12 PDFs privados de notas fiscais
|
|
361
|
+
de compra (223.074 bytes no total). O diretório local `invoices/` é ignorado pelo
|
|
362
|
+
Git e excluído da lista de arquivos permitidos no pacote npm. O executor emite
|
|
363
|
+
somente métricas agregadas; nunca imprime nomes de arquivos, chaves de acesso ou
|
|
364
|
+
conteúdo dos documentos.
|
|
365
|
+
|
|
366
|
+
| Perfil | Execuções medidas por PDF | PDFs com chave | Erros / avisos | Média | P50 | P95 |
|
|
367
|
+
| ----------------------- | ------------------------: | -------------: | -------------: | ----------: | ----------: | ----------: |
|
|
368
|
+
| `fast`, 1 passagem | 3 | 12/12 | 0 / 0 | 8,29 ms | 8,18 ms | 9,65 ms |
|
|
369
|
+
| `balanced`, 2 passagens | 3 | 12/12 | 0 / 0 | 8,28 ms | 7,66 ms | 13,35 ms |
|
|
370
|
+
| `accurate`, 3 passagens | 1 | 12/12 | 0 / 0 | 850,43 ms | 829,81 ms | 921,52 ms |
|
|
371
|
+
| `accurate`, OCR forçado | 1 | 12/12 | 0 / 0 | 1.934,50 ms | 1.907,04 ms | 2.036,73 ms |
|
|
372
|
+
|
|
373
|
+
Os perfis `fast`, `balanced` e `accurate` retornaram o mesmo conjunto de chaves
|
|
374
|
+
validadas nos 12 documentos, com exatamente uma chave por PDF e nenhum resultado
|
|
375
|
+
incompleto. Todas as evidências finais desse corpus vieram do texto nativo
|
|
376
|
+
reconstruído do PDF. O OCR forçado processou as 12 páginas, mas não reproduziu a
|
|
377
|
+
chave completa de forma independente e, portanto, não aumentou a pontuação das
|
|
378
|
+
evidências.
|
|
379
|
+
|
|
380
|
+
Esses PDFs privados não têm uma referência correta rotulada manualmente e
|
|
381
|
+
verificada de forma independente. Portanto, o resultado demonstra processamento
|
|
382
|
+
bem-sucedido, validação fiscal determinística e concordância entre os perfis;
|
|
383
|
+
ele não estabelece precisão/recall estatísticos nem prova que nenhuma chave foi
|
|
384
|
+
ignorada. O benchmark sintético acima é o teste de correspondência exata com
|
|
385
|
+
chaves esperadas conhecidas.
|
|
386
|
+
|
|
387
|
+
O tarball empacotado da versão `0.1.0` também foi instalado em um projeto
|
|
388
|
+
consumidor temporário e limpo com npm 11.6.1. A entrada ESM, a entrada CommonJS,
|
|
389
|
+
o executável da CLI e as dependências locais de OCR e execução instalados foram
|
|
390
|
+
resolvidos corretamente; ESM, CommonJS e CLI processaram com sucesso um PDF real
|
|
391
|
+
privado. O projeto consumidor temporário foi removido após a verificação.
|
|
392
|
+
|
|
393
|
+
O tarball empacotado da versão `0.2.0` foi gerado e instalado em outro projeto
|
|
394
|
+
consumidor temporário e limpo com pnpm 10.34.5 e Node.js 24.11.0. As entradas ESM
|
|
395
|
+
e CommonJS, `requestHeaders`, a extração de URLs HTTP/HTTPS, as declarações
|
|
396
|
+
TypeScript e o executável da CLI instalados foram verificados com um PDF gerado
|
|
397
|
+
e respostas de rede simuladas. Nenhum endpoint ativo nem credencial de produção
|
|
398
|
+
foi usado.
|
|
399
|
+
|
|
400
|
+
## Desenvolvimento
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
pnpm install
|
|
404
|
+
pnpm typecheck
|
|
405
|
+
pnpm lint
|
|
406
|
+
pnpm test
|
|
407
|
+
pnpm test:real
|
|
408
|
+
pnpm test:coverage
|
|
409
|
+
pnpm security:audit
|
|
410
|
+
pnpm build
|
|
411
|
+
pnpm check
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
A compilação gera ESM, CommonJS, declarações, mapas de código-fonte e a CLI em
|
|
415
|
+
`dist/`. Os fixtures de integração são sintéticos e não contêm dados reais de
|
|
416
|
+
contribuintes.
|
|
417
|
+
|
|
418
|
+
Execute um benchmark em JSON com:
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
pnpm benchmark --scenario text --iterations 20 --performance fast --passes 1
|
|
422
|
+
pnpm benchmark:real
|
|
423
|
+
pnpm benchmark:real --include-accurate --include-ocr
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Consulte [`benchmarks/README.md`](benchmarks/README.md) para conhecer os cenários
|
|
427
|
+
e as métricas.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Testes de desempenho (benchmarks)
|
|
2
|
+
|
|
3
|
+
O executor de benchmarks cria PDFs sintéticos em memória, verifica a
|
|
4
|
+
correspondência exata da chave de acesso em cada iteração e grava um documento
|
|
5
|
+
JSON na saída padrão.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm benchmark --scenario text --iterations 20 --performance fast --passes 1
|
|
9
|
+
pnpm benchmark --scenario barcode --iterations 10 --performance balanced --passes 2
|
|
10
|
+
pnpm benchmark --scenario qr --iterations 10 --performance balanced --passes 2
|
|
11
|
+
pnpm benchmark --scenario ocr --iterations 3 --performance accurate --passes 3
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Os valores informados incluem tempo decorrido mínimo/médio/p50/p95/máximo, tempo
|
|
15
|
+
de CPU, variação de RSS, informações do ambiente de execução, tamanho da entrada
|
|
16
|
+
e taxa de correspondência exata. A primeira extração serve como aquecimento e é
|
|
17
|
+
excluída das amostras.
|
|
18
|
+
|
|
19
|
+
Não use os tempos absolutos de uma única máquina como critério de CI. Em vez
|
|
20
|
+
disso, compare execuções repetidas no mesmo hardware e monitore regressões
|
|
21
|
+
relativas.
|
|
22
|
+
|
|
23
|
+
## Corpus privado de notas fiscais locais
|
|
24
|
+
|
|
25
|
+
`real-corpus.ts` localiza recursivamente os PDFs em um diretório local, executa o
|
|
26
|
+
aquecimento de cada perfil, mede a latência por documento e compara os conjuntos
|
|
27
|
+
de chaves validadas retornados por todos os perfis. Sua saída JSON contém somente
|
|
28
|
+
dados agregados: nunca inclui chaves de acesso, conteúdo dos documentos, caminhos
|
|
29
|
+
ou nomes de arquivos.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pnpm exec tsx benchmarks/real-corpus.ts
|
|
33
|
+
pnpm exec tsx benchmarks/real-corpus.ts --input-dir invoices --iterations 3 --warmup-iterations 1
|
|
34
|
+
pnpm exec tsx benchmarks/real-corpus.ts --include-accurate
|
|
35
|
+
pnpm exec tsx benchmarks/real-corpus.ts --include-accurate --include-ocr
|
|
36
|
+
pnpm exec tsx benchmarks/real-corpus.ts --require-key-per-document
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Os perfis `fast` e `balanced` sempre são executados. `--include-accurate`
|
|
40
|
+
adiciona o modo `accurate` com OCR como alternativa, enquanto `--include-ocr`
|
|
41
|
+
adiciona um perfil `accurate` que força o mecanismo de OCR local incluído no
|
|
42
|
+
pacote. O OCR pode ser consideravelmente mais lento, por isso é opcional.
|
|
43
|
+
|
|
44
|
+
O relatório inclui o tamanho do corpus, dados agregados de tempo de execução e
|
|
45
|
+
memória, contagens de sucesso por perfil, latência média/p50/p95, contagens de
|
|
46
|
+
documentos por categoria de origem não exclusiva, estabilidade entre iterações
|
|
47
|
+
e concordância exata dos conjuntos de chaves. O executor termina com status
|
|
48
|
+
diferente de zero em caso de erros de extração, processamento incompleto,
|
|
49
|
+
resultados não determinísticos ou divergências entre perfis. Um resultado
|
|
50
|
+
`not_found` válido é incluído nas contagens de sucesso, mas não é tratado como
|
|
51
|
+
erro de processamento, a menos que `--require-key-per-document` esteja definido.
|
|
52
|
+
A concordância entre perfis mede a consistência dos modos de extração; sem uma
|
|
53
|
+
referência correta verificada de forma independente, ela não mede a acurácia.
|