nia-etl-utils 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
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.
- nia_etl_utils/__init__.py +21 -1
- nia_etl_utils/exceptions.py +67 -0
- nia_etl_utils/ocr.py +401 -0
- nia_etl_utils-0.2.1.dist-info/METADATA +723 -0
- {nia_etl_utils-0.2.0.dist-info → nia_etl_utils-0.2.1.dist-info}/RECORD +7 -6
- {nia_etl_utils-0.2.0.dist-info → nia_etl_utils-0.2.1.dist-info}/WHEEL +1 -1
- nia_etl_utils-0.2.0.dist-info/METADATA +0 -615
- {nia_etl_utils-0.2.0.dist-info → nia_etl_utils-0.2.1.dist-info}/top_level.txt +0 -0
nia_etl_utils/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ Este pacote fornece funções reutilizáveis para:
|
|
|
11
11
|
- Processamento e exportação de CSV (processa_csv)
|
|
12
12
|
- Processamento paralelo de CSV grandes (processa_csv_paralelo)
|
|
13
13
|
- Manipulação de arquivos e diretórios (limpeza_pastas)
|
|
14
|
+
- Processamento de OCR via API IntelliDoc (ocr)
|
|
14
15
|
|
|
15
16
|
Exemplo de uso:
|
|
16
17
|
|
|
@@ -44,9 +45,21 @@ Exemplo de uso:
|
|
|
44
45
|
)
|
|
45
46
|
with conectar_postgresql(config) as conn:
|
|
46
47
|
# ...
|
|
48
|
+
|
|
49
|
+
Exemplo de OCR:
|
|
50
|
+
|
|
51
|
+
from nia_etl_utils import executar_ocr
|
|
52
|
+
from nia_etl_utils.ocr import OcrError
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
resultado = executar_ocr(blob_bytes, url_base="INTELLIDOC_URL")
|
|
56
|
+
texto = resultado["full_text"]
|
|
57
|
+
qualidade = resultado["overall_quality"]
|
|
58
|
+
except OcrError as e:
|
|
59
|
+
logger.error(f"Falha no OCR: {e}")
|
|
47
60
|
"""
|
|
48
61
|
|
|
49
|
-
__version__ = "0.2.
|
|
62
|
+
__version__ = "0.2.1"
|
|
50
63
|
__author__ = "Nícolas Galdino Esmael"
|
|
51
64
|
|
|
52
65
|
# =============================================================================
|
|
@@ -134,6 +147,11 @@ from .logger_config import (
|
|
|
134
147
|
remover_handlers_existentes,
|
|
135
148
|
)
|
|
136
149
|
|
|
150
|
+
# OCR
|
|
151
|
+
from .ocr import (
|
|
152
|
+
executar_ocr,
|
|
153
|
+
)
|
|
154
|
+
|
|
137
155
|
# Processamento CSV
|
|
138
156
|
from .processa_csv import (
|
|
139
157
|
exportar_multiplos_csv,
|
|
@@ -233,4 +251,6 @@ __all__ = [
|
|
|
233
251
|
"remover_pasta_recursivamente",
|
|
234
252
|
"criar_pasta_se_nao_existir",
|
|
235
253
|
"listar_arquivos",
|
|
254
|
+
# OCR
|
|
255
|
+
"executar_ocr",
|
|
236
256
|
]
|
nia_etl_utils/exceptions.py
CHANGED
|
@@ -18,6 +18,10 @@ Hierarquia:
|
|
|
18
18
|
├── EmailError
|
|
19
19
|
│ ├── DestinatarioError
|
|
20
20
|
│ └── SmtpError
|
|
21
|
+
├── OcrError
|
|
22
|
+
│ ├── OcrSubmissaoError
|
|
23
|
+
│ ├── OcrProcessamentoError
|
|
24
|
+
│ └── OcrTimeoutError
|
|
21
25
|
└── ValidacaoError
|
|
22
26
|
|
|
23
27
|
Examples:
|
|
@@ -306,6 +310,69 @@ class SmtpError(EmailError):
|
|
|
306
310
|
pass
|
|
307
311
|
|
|
308
312
|
|
|
313
|
+
# =============================================================================
|
|
314
|
+
# OCR
|
|
315
|
+
# =============================================================================
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class OcrError(NiaEtlError):
|
|
319
|
+
"""Erro base para operações de OCR.
|
|
320
|
+
|
|
321
|
+
Examples:
|
|
322
|
+
>>> raise OcrError("Falha no processamento OCR")
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class OcrSubmissaoError(OcrError):
|
|
329
|
+
"""Falha ao submeter documento para OCR.
|
|
330
|
+
|
|
331
|
+
Levantado quando não é possível enviar o documento para a API,
|
|
332
|
+
seja por problemas de rede, timeout ou resposta inválida.
|
|
333
|
+
|
|
334
|
+
Examples:
|
|
335
|
+
>>> raise OcrSubmissaoError(
|
|
336
|
+
... "Timeout ao submeter documento",
|
|
337
|
+
... details={"tentativas": 3, "status": 504}
|
|
338
|
+
... )
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class OcrProcessamentoError(OcrError):
|
|
345
|
+
"""Falha no processamento do documento pela API.
|
|
346
|
+
|
|
347
|
+
Levantado quando a API retorna status FAILURE ou REVOKED,
|
|
348
|
+
indicando que o documento não pôde ser processado.
|
|
349
|
+
|
|
350
|
+
Examples:
|
|
351
|
+
>>> raise OcrProcessamentoError(
|
|
352
|
+
... "Documento corrompido",
|
|
353
|
+
... details={"document_id": "abc-123", "erro_api": "invalid format"}
|
|
354
|
+
... )
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
pass
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class OcrTimeoutError(OcrError):
|
|
361
|
+
"""Timeout aguardando resultado do OCR.
|
|
362
|
+
|
|
363
|
+
Levantado quando o tempo máximo de polling é atingido
|
|
364
|
+
sem que a API retorne um resultado final.
|
|
365
|
+
|
|
366
|
+
Examples:
|
|
367
|
+
>>> raise OcrTimeoutError(
|
|
368
|
+
... "Timeout após 300s",
|
|
369
|
+
... details={"document_id": "abc-123", "ultimo_status": "PENDING"}
|
|
370
|
+
... )
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
pass
|
|
374
|
+
|
|
375
|
+
|
|
309
376
|
# =============================================================================
|
|
310
377
|
# VALIDAÇÃO
|
|
311
378
|
# =============================================================================
|
nia_etl_utils/ocr.py
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"""Módulo para processamento de OCR via API IntelliDoc.
|
|
2
|
+
|
|
3
|
+
Este módulo fornece funções para submeter documentos ao serviço de OCR
|
|
4
|
+
do MPRJ e aguardar o resultado do processamento.
|
|
5
|
+
|
|
6
|
+
A API IntelliDoc processa documentos de forma assíncrona:
|
|
7
|
+
1. POST /documents/files → submete documento, retorna document_id
|
|
8
|
+
2. GET /documents/{id} → consulta status/resultado
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
Uso básico com variável de ambiente:
|
|
12
|
+
|
|
13
|
+
>>> from nia_etl_utils.ocr import executar_ocr
|
|
14
|
+
>>>
|
|
15
|
+
>>> with open("documento.pdf", "rb") as f:
|
|
16
|
+
... resultado = executar_ocr(
|
|
17
|
+
... conteudo=f.read(),
|
|
18
|
+
... url_base="INTELLIDOC_URL", # env var
|
|
19
|
+
... )
|
|
20
|
+
>>> print(resultado["full_text"])
|
|
21
|
+
|
|
22
|
+
Uso com URL direta e configurações customizadas:
|
|
23
|
+
|
|
24
|
+
>>> resultado = executar_ocr(
|
|
25
|
+
... conteudo=blob_bytes,
|
|
26
|
+
... url_base="http://google.com",
|
|
27
|
+
... timeout_polling=600,
|
|
28
|
+
... max_tentativas=5,
|
|
29
|
+
... )
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import time
|
|
33
|
+
|
|
34
|
+
import requests
|
|
35
|
+
from loguru import logger
|
|
36
|
+
|
|
37
|
+
from .env_config import obter_variavel_env
|
|
38
|
+
from .exceptions import NiaEtlError
|
|
39
|
+
|
|
40
|
+
# =============================================================================
|
|
41
|
+
# EXCEÇÕES
|
|
42
|
+
# =============================================================================
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class OcrError(NiaEtlError):
|
|
46
|
+
"""Erro base para operações de OCR.
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
>>> raise OcrError("Falha no processamento OCR")
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class OcrSubmissaoError(OcrError):
|
|
56
|
+
"""Falha ao submeter documento para OCR.
|
|
57
|
+
|
|
58
|
+
Levantado quando não é possível enviar o documento para a API,
|
|
59
|
+
seja por problemas de rede, timeout ou resposta inválida.
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
>>> raise OcrSubmissaoError(
|
|
63
|
+
... "Timeout ao submeter documento",
|
|
64
|
+
... details={"tentativas": 3, "status": 504}
|
|
65
|
+
... )
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class OcrProcessamentoError(OcrError):
|
|
72
|
+
"""Falha no processamento do documento pela API.
|
|
73
|
+
|
|
74
|
+
Levantado quando a API retorna status FAILURE ou REVOKED,
|
|
75
|
+
indicando que o documento não pôde ser processado.
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
>>> raise OcrProcessamentoError(
|
|
79
|
+
... "Documento corrompido",
|
|
80
|
+
... details={"document_id": "abc-123", "erro_api": "invalid format"}
|
|
81
|
+
... )
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class OcrTimeoutError(OcrError):
|
|
88
|
+
"""Timeout aguardando resultado do OCR.
|
|
89
|
+
|
|
90
|
+
Levantado quando o tempo máximo de polling é atingido
|
|
91
|
+
sem que a API retorne um resultado final.
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
>>> raise OcrTimeoutError(
|
|
95
|
+
... "Timeout após 300s",
|
|
96
|
+
... details={"document_id": "abc-123", "ultimo_status": "PENDING"}
|
|
97
|
+
... )
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# =============================================================================
|
|
104
|
+
# CONSTANTES
|
|
105
|
+
# =============================================================================
|
|
106
|
+
|
|
107
|
+
MAGIC_BYTES: dict[bytes, str] = {
|
|
108
|
+
b"%PDF": ".pdf",
|
|
109
|
+
b"\xff\xd8\xff": ".jpg",
|
|
110
|
+
b"\x89PNG\r\n\x1a\n": ".png",
|
|
111
|
+
b"GIF87a": ".gif",
|
|
112
|
+
b"GIF89a": ".gif",
|
|
113
|
+
b"BM": ".bmp",
|
|
114
|
+
b"II*\x00": ".tiff",
|
|
115
|
+
b"MM\x00*": ".tiff",
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
PATH_ENVIO = "/documents/files"
|
|
119
|
+
PATH_CONSULTA = "/documents"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# =============================================================================
|
|
123
|
+
# FUNÇÕES AUXILIARES
|
|
124
|
+
# =============================================================================
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _detectar_extensao(conteudo: bytes) -> str:
|
|
128
|
+
"""Detecta a extensão do arquivo baseado nos magic bytes.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
conteudo: Bytes do arquivo.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Extensão detectada (ex: '.pdf', '.jpg') ou '.bin' se não reconhecido.
|
|
135
|
+
"""
|
|
136
|
+
conteudo_inicio = conteudo[:32].lstrip()
|
|
137
|
+
|
|
138
|
+
for magic, extensao in MAGIC_BYTES.items():
|
|
139
|
+
if conteudo_inicio.startswith(magic):
|
|
140
|
+
return extensao
|
|
141
|
+
|
|
142
|
+
return ".bin"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _normalizar_para_bytes(blob: object) -> bytes:
|
|
146
|
+
"""Converte diferentes tipos de BLOB para bytes.
|
|
147
|
+
|
|
148
|
+
Suporta LOBs do cx_Oracle/oracledb, memoryview e bytes/bytearray.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
blob: Objeto contendo dados binários.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Conteúdo em bytes.
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
TypeError: Se o tipo do blob não for suportado.
|
|
158
|
+
"""
|
|
159
|
+
if hasattr(blob, "read"):
|
|
160
|
+
return blob.read() # type: ignore[union-attr]
|
|
161
|
+
|
|
162
|
+
if isinstance(blob, memoryview):
|
|
163
|
+
return blob.tobytes()
|
|
164
|
+
|
|
165
|
+
if isinstance(blob, (bytes, bytearray)):
|
|
166
|
+
return bytes(blob)
|
|
167
|
+
|
|
168
|
+
raise TypeError(f"Tipo de blob não suportado: {type(blob)}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _resolver_url_base(url_base: str) -> str:
|
|
172
|
+
"""Resolve URL base a partir de valor direto ou nome de variável de ambiente.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
url_base: URL direta (começa com http) ou nome de variável de ambiente.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
URL base resolvida, sem barra final.
|
|
179
|
+
"""
|
|
180
|
+
if url_base.startswith(("http://", "https://")): # noqa
|
|
181
|
+
url = url_base
|
|
182
|
+
else:
|
|
183
|
+
url = obter_variavel_env(url_base)
|
|
184
|
+
|
|
185
|
+
return url.rstrip("/")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _submeter_documento(
|
|
189
|
+
url_base: str,
|
|
190
|
+
conteudo: bytes,
|
|
191
|
+
extensao: str,
|
|
192
|
+
max_tentativas: int,
|
|
193
|
+
intervalo_retry: int,
|
|
194
|
+
) -> str:
|
|
195
|
+
"""Submete documento para processamento OCR.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
url_base: URL base da API.
|
|
199
|
+
conteudo: Bytes do documento.
|
|
200
|
+
extensao: Extensão do arquivo.
|
|
201
|
+
max_tentativas: Número máximo de tentativas.
|
|
202
|
+
intervalo_retry: Segundos entre tentativas.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
document_id retornado pela API.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
OcrSubmissaoError: Se todas as tentativas falharem.
|
|
209
|
+
"""
|
|
210
|
+
url = f"{url_base}{PATH_ENVIO}"
|
|
211
|
+
nome_arquivo = f"documento{extensao}"
|
|
212
|
+
files = {"files": (nome_arquivo, conteudo)}
|
|
213
|
+
|
|
214
|
+
ultima_excecao: Exception | None = None
|
|
215
|
+
|
|
216
|
+
for tentativa in range(1, max_tentativas + 1):
|
|
217
|
+
try:
|
|
218
|
+
logger.debug(
|
|
219
|
+
f"Tentativa {tentativa}/{max_tentativas} - "
|
|
220
|
+
f"Submetendo OCR (extensao={extensao}, tamanho={len(conteudo)} bytes)"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
resp = requests.post(url, files=files, timeout=120)
|
|
224
|
+
|
|
225
|
+
if resp.ok:
|
|
226
|
+
resultado = resp.json()
|
|
227
|
+
document_id = resultado["documents"][0]["document_id"]
|
|
228
|
+
logger.info(f"Documento submetido com sucesso. document_id={document_id}")
|
|
229
|
+
return document_id
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
detalhe = resp.json()
|
|
233
|
+
except Exception:
|
|
234
|
+
detalhe = resp.text
|
|
235
|
+
|
|
236
|
+
ultima_excecao = OcrSubmissaoError(
|
|
237
|
+
f"API retornou status {resp.status_code}",
|
|
238
|
+
details={"status": resp.status_code, "detalhe": detalhe},
|
|
239
|
+
)
|
|
240
|
+
logger.warning(f"Tentativa {tentativa} falhou: status={resp.status_code}")
|
|
241
|
+
|
|
242
|
+
except requests.RequestException as e:
|
|
243
|
+
ultima_excecao = e
|
|
244
|
+
logger.warning(f"Tentativa {tentativa} falhou com exceção: {e}")
|
|
245
|
+
|
|
246
|
+
if tentativa < max_tentativas:
|
|
247
|
+
logger.info(f"Aguardando {intervalo_retry}s antes da próxima tentativa...")
|
|
248
|
+
time.sleep(intervalo_retry)
|
|
249
|
+
|
|
250
|
+
raise OcrSubmissaoError(
|
|
251
|
+
f"Submissão OCR falhou após {max_tentativas} tentativas",
|
|
252
|
+
details={"tentativas": max_tentativas, "ultimo_erro": str(ultima_excecao)},
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _aguardar_resultado(
|
|
257
|
+
url_base: str,
|
|
258
|
+
document_id: str,
|
|
259
|
+
timeout_polling: int,
|
|
260
|
+
intervalo_polling: int,
|
|
261
|
+
) -> dict:
|
|
262
|
+
"""Aguarda resultado do processamento OCR via polling.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
url_base: URL base da API.
|
|
266
|
+
document_id: ID do documento retornado na submissão.
|
|
267
|
+
timeout_polling: Tempo máximo de espera em segundos.
|
|
268
|
+
intervalo_polling: Intervalo entre consultas em segundos.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Dicionário com resultado completo da API (campo 'result').
|
|
272
|
+
|
|
273
|
+
Raises:
|
|
274
|
+
OcrProcessamentoError: Se a API retornar FAILURE ou REVOKED.
|
|
275
|
+
OcrTimeoutError: Se o timeout for atingido.
|
|
276
|
+
"""
|
|
277
|
+
url = f"{url_base}{PATH_CONSULTA}/{document_id}"
|
|
278
|
+
inicio = time.time()
|
|
279
|
+
ultimo_status = "UNKNOWN"
|
|
280
|
+
|
|
281
|
+
while True:
|
|
282
|
+
tempo_decorrido = time.time() - inicio
|
|
283
|
+
|
|
284
|
+
if tempo_decorrido >= timeout_polling:
|
|
285
|
+
raise OcrTimeoutError(
|
|
286
|
+
f"Timeout após {timeout_polling}s aguardando OCR",
|
|
287
|
+
details={"document_id": document_id, "ultimo_status": ultimo_status},
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
try:
|
|
291
|
+
resp = requests.get(url, timeout=30)
|
|
292
|
+
|
|
293
|
+
if resp.ok:
|
|
294
|
+
resultado = resp.json()
|
|
295
|
+
ultimo_status = resultado.get("status", "UNKNOWN")
|
|
296
|
+
|
|
297
|
+
if ultimo_status == "SUCCESS":
|
|
298
|
+
logger.info(f"OCR concluído para document_id={document_id}")
|
|
299
|
+
return resultado.get("result", {})
|
|
300
|
+
|
|
301
|
+
if ultimo_status in ("FAILURE", "REVOKED"):
|
|
302
|
+
erro = resultado.get("error") or resultado.get("message") or "Erro desconhecido"
|
|
303
|
+
raise OcrProcessamentoError(
|
|
304
|
+
f"OCR falhou: {erro}",
|
|
305
|
+
details={"document_id": document_id, "status": ultimo_status, "erro_api": erro},
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
logger.debug(f"document_id={document_id} status={ultimo_status}, aguardando...")
|
|
309
|
+
|
|
310
|
+
except requests.RequestException as e:
|
|
311
|
+
logger.warning(f"Erro ao consultar status: {e}")
|
|
312
|
+
|
|
313
|
+
time.sleep(intervalo_polling)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# =============================================================================
|
|
317
|
+
# FUNÇÃO PRINCIPAL
|
|
318
|
+
# =============================================================================
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def executar_ocr(
|
|
322
|
+
conteudo: bytes | object,
|
|
323
|
+
url_base: str = "INTELLIDOC_URL",
|
|
324
|
+
timeout_polling: int = 300,
|
|
325
|
+
max_tentativas: int = 3,
|
|
326
|
+
intervalo_retry: int = 5,
|
|
327
|
+
intervalo_polling: int = 1,
|
|
328
|
+
) -> dict:
|
|
329
|
+
"""Executa OCR em documento via API IntelliDoc.
|
|
330
|
+
|
|
331
|
+
Submete o documento para processamento e aguarda o resultado.
|
|
332
|
+
A extensão do arquivo é detectada automaticamente pelos magic bytes.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
conteudo: Bytes do documento ou objeto com método read() (LOB Oracle).
|
|
336
|
+
url_base: URL da API ou nome da variável de ambiente.
|
|
337
|
+
Se começar com 'http://' ou 'https://', usa como URL direta.
|
|
338
|
+
Caso contrário, busca no .env. Default: "INTELLIDOC_URL".
|
|
339
|
+
timeout_polling: Tempo máximo em segundos para aguardar resultado.
|
|
340
|
+
Default: 300 (5 minutos).
|
|
341
|
+
max_tentativas: Número de tentativas para submissão. Default: 3.
|
|
342
|
+
intervalo_retry: Segundos entre tentativas de submissão. Default: 5.
|
|
343
|
+
intervalo_polling: Segundos entre consultas de status. Default: 1.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Dicionário com resultado completo da API, contendo:
|
|
347
|
+
- document_id: ID do documento
|
|
348
|
+
- full_text: Texto extraído completo
|
|
349
|
+
- mime_type: Tipo MIME detectado
|
|
350
|
+
- overall_quality: Qualidade geral do OCR (0-1)
|
|
351
|
+
- total_pages: Número de páginas
|
|
352
|
+
- processing_time_ms: Tempo de processamento
|
|
353
|
+
- pages: Lista com detalhes de cada página
|
|
354
|
+
- metadata: Metadados adicionais
|
|
355
|
+
|
|
356
|
+
Raises:
|
|
357
|
+
OcrSubmissaoError: Se falhar ao submeter documento.
|
|
358
|
+
OcrProcessamentoError: Se a API retornar erro no processamento.
|
|
359
|
+
OcrTimeoutError: Se timeout for atingido aguardando resultado.
|
|
360
|
+
TypeError: Se o tipo do conteúdo não for suportado.
|
|
361
|
+
|
|
362
|
+
Examples:
|
|
363
|
+
Uso básico:
|
|
364
|
+
|
|
365
|
+
>>> resultado = executar_ocr(blob_bytes, url_base="INTELLIDOC_URL")
|
|
366
|
+
>>> print(resultado["full_text"])
|
|
367
|
+
|
|
368
|
+
Uso com URL direta:
|
|
369
|
+
|
|
370
|
+
>>> resultado = executar_ocr(
|
|
371
|
+
... conteudo=pdf_bytes,
|
|
372
|
+
... url_base="http://google.com",
|
|
373
|
+
... timeout_polling=600,
|
|
374
|
+
... )
|
|
375
|
+
>>> print(f"Qualidade: {resultado['overall_quality']}")
|
|
376
|
+
|
|
377
|
+
Acessando detalhes das páginas:
|
|
378
|
+
|
|
379
|
+
>>> for page in resultado["pages"]:
|
|
380
|
+
... print(f"Página {page['page_number']}: {page['extraction_method']}")
|
|
381
|
+
"""
|
|
382
|
+
conteudo_bytes = _normalizar_para_bytes(conteudo)
|
|
383
|
+
extensao = _detectar_extensao(conteudo_bytes)
|
|
384
|
+
url = _resolver_url_base(url_base)
|
|
385
|
+
|
|
386
|
+
logger.info(f"Iniciando OCR (tamanho={len(conteudo_bytes)} bytes, extensao={extensao})")
|
|
387
|
+
|
|
388
|
+
document_id = _submeter_documento(
|
|
389
|
+
url_base=url,
|
|
390
|
+
conteudo=conteudo_bytes,
|
|
391
|
+
extensao=extensao,
|
|
392
|
+
max_tentativas=max_tentativas,
|
|
393
|
+
intervalo_retry=intervalo_retry,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
return _aguardar_resultado(
|
|
397
|
+
url_base=url,
|
|
398
|
+
document_id=document_id,
|
|
399
|
+
timeout_polling=timeout_polling,
|
|
400
|
+
intervalo_polling=intervalo_polling,
|
|
401
|
+
)
|