DEV Community

Cláudio Filipe Lima Rapôso
Cláudio Filipe Lima Rapôso

Posted on

Processamento de Documentos em Larga Escala: Pipeline de OCR Assíncrono com Azure AI Document Intelligence, Azure Functions e PostgreSQL

A digitalização de documentos corporativos (faturas, contratos e relatórios) impõe um desafio computacional severo quando realizada de forma síncrona. Tentar extrair texto de arquivos PDF densos utilizando bibliotecas locais dentro de funções sem servidor (serverless) esbarra inevitavelmente nos limites rígidos de tempo de execução (timeouts) e alocação de memória. Simultaneamente, bloquear o cliente aguardando o término do processamento degrada a experiência da aplicação. A implementação de um pipeline de OCR assíncrono fundamentado no Azure AI Document Intelligence, Azure Functions e PostgreSQL resolve essa deficiência estrutural. Ao delegar o escaneamento pesado para o serviço cognitivo gerenciado e estruturar o fluxo de retorno via webhook com persistência otimizada, a arquitetura assegura escalabilidade elástica, tolerância a falhas e rendimento massivo para fluxos de trabalho orientados a documentos em ecossistemas corporativos.

Pré-requisitos

A orquestração desta topologia exige proficiência em processamento de arquivos em nuvem, manipulação de streams binários e modelagem relacional de alta performance. A infraestrutura deve ser provisionada utilizando o Terraform versão 1.7.0 ou superior acoplado ao provedor HashiCorp AzureRM versão 3.90.0 ou superior. A camada computacional utiliza o Python 3.12, integrando o modelo de programação v2 do azure-functions, o SDK oficial azure-ai-documentintelligence e o driver psycopg para persistência relacional assíncrona. Privilégios administrativos na assinatura do Azure são mandatórios para provisionar os recursos cognitivos de inteligência artificial e configurar a string de conexão segura com o PostgreSQL.

Passo a Passo

Provisionamento da Infraestrutura Cognitiva e de Armazenamento

A fundação do pipeline inicia-se com o provisionamento do Azure AI Document Intelligence e do banco de dados relacional PostgreSQL. A justificativa técnica para priorizar o serviço cognitivo gerenciado em detrimento de motores auto-hospedados baseados em Tesseract reside na acurácia e na capacidade de extração estruturada de tabelas e pares chave-valor em múltiplos idiomas. Provisionamos o recurso cognitivo atrelado a uma camada padronizada, enquanto o PostgreSQL gerencia a persistência transacional dos dados estruturados resultantes da extração. Configuramos o Azure Storage Account para atuar como a zona de pouso (Landing Zone) primária, onde os usuários finais depositam os arquivos brutos. O Terraform abaixo consolida essa topologia isolada, garantindo que as chaves de acesso sejam geridas de forma centralizada.

resource "azurerm_cognitive_account" "document_intelligence" {
  name                  = "cog-enterprise-ocr"
  location              = var.location
  resource_group_name   = var.resource_group_name
  kind                  = "FormRecognizer"
  sku_name              = "S0"
  local_authentication_enabled = true
}

resource "azurerm_postgresql_flexible_server" "pg_core" {
  name                   = "psql-enterprise-ocr-core"
  resource_group_name    = var.resource_group_name
  location               = var.location
  version                = "15"
  administrator_login    = var.db_admin
  administrator_password = var.db_password
  storage_mb             = 32768
  sku_name               = "GP_Standard_D2s_v3"
}

resource "azurerm_storage_account" "ocr_landing" {
  name                     = "stocrlandingzone01"
  resource_group_name      = var.resource_group_name
  location                 = var.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

Enter fullscreen mode Exit fullscreen mode

Como acionamos o motor cognitivo assim que um documento é depositado no armazenamento sem bloquear a thread de execução da aplicação principal?

Iniciação Assíncrona via Azure Blob Trigger

Disparamos o processo de reconhecimento óptico de caracteres de forma puramente assíncrona utilizando um gatilho de Blob Storage (Blob Trigger) acoplado a uma Azure Function. O adaptador de entrada intercepta a criação do arquivo PDF, extrai a URL autenticada do objeto e a submete ao Azure AI Document Intelligence utilizando o método de análise em segundo plano (begin_analyze_document_from_url). O raciocínio arquitetônico vital nesta etapa é o desacoplamento de tempo (Time Decoupling). O serviço cognitivo recebe a tarefa, devolve um identificador de operação imediato (Operation ID) e processa as centenas de páginas do documento nos bastidores da infraestrutura da Microsoft. A função Python conclui sua execução em poucos milissegundos, liberando o container sem estourar o limite de timeout e sem consumir recursos computacionais ociosos enquanto o documento é escaneado.

sequenceDiagram
    participant User as Cliente / App
    participant Blob as Azure Blob Storage
    participant FuncInit as Azure Function (Iniciadora)
    participant Cog as Azure AI Document Intelligence
    participant FuncWebhook as Azure Function (Webhook)
    participant DB as PostgreSQL

    User->>Blob: Upload de PDF Bruto (Fatura.pdf)
    Blob->>FuncInit: Dispara Blob Trigger
    FuncInit->>Cog: Inicia Análise Assíncrona (begin_analyze_document)
    Cog-->>FuncInit: Retorna Operation ID (202 Accepted)
    Note over Cog: Processamento pesado em segundo plano
    Cog->>FuncWebhook: Notifica Conclusão via Webhook (Callback)
    FuncWebhook->>DB: Persiste Dados Estruturados Extraídos
import os
import logging
import azure.functions as func
from azure.ai.documentintelligence import DocumentIntelligenceClient
from azure.core.credentials import AzureKeyCredential

app = func.FunctionApp()

class OcrInitiatorAdapter:
    def __init__(self):
        endpoint = os.environ["DOCUMENT_INTELLIGENCE_ENDPOINT"]
        key = os.environ["DOCUMENT_INTELLIGENCE_KEY"]
        self.client = DocumentIntelligenceClient(endpoint=endpoint, credential=AzureKeyCredential(key))

    def trigger_analysis(self, file_url: str, document_id: str) -> None:
        logging.info(f"Disparando análise assíncrona para o documento ID: {document_id}")

        # O modelo prebuilt-invoice ou prebuilt-document extrai o texto de forma estruturada
        poller = self.client.begin_analyze_document_from_url(
            model_id="prebuilt-document",
            analyze_request={"urlSource": file_url},
            # O webhook notificará o término do processo em segundo plano
            # notification_url=os.environ["OCR_WEBHOOK_CALLBACK_URL"]
        )
        logging.info(f"Operação iniciada com poller ID: {poller.id}")

initiator = OcrInitiatorAdapter()

@app.blob_trigger(
    arg_name="pdfinput",
    path="incoming-documents/{name}",
    connection="AZURE_STORAGE_CONNECTION_STRING"
)
def initiate_ocr_process(pdfinput: func.InputStream):
    file_name = pdfinput.name
    logging.info(f"Novo documento detectado na zona de pouso: {file_name}")

    # URL temporária assinada (SAS) gerada para o serviço cognitivo acessar o blob com segurança
    # Simplificado aqui para fins estruturais
    file_url = f"https://stocrlandingzone01.blob.core.windows.net/incoming-documents/{os.path.basename(file_name)}"

    initiator.trigger_analysis(file_url, document_id=file_name)

Enter fullscreen mode Exit fullscreen mode

Uma vez que o Azure AI Document Intelligence conclui a leitura de milhares de linhas de texto, tabelas e metadados, como processamos esse payload estruturado volumoso e o persistimos no PostgreSQL de forma otimizada?

Persistência Relacional Otimizada com PostgreSQL

Persistimos o resultado estruturado capturando o payload consolidado através de um endpoint HTTP (Webhook Adapter) e gravando-o no PostgreSQL utilizando comandos parametrizados de alta performance. O modelo relacional exige que dados semi-estruturados extraídos por IA (como pares chave-valor e listas de itens de faturas) sejam mapeados para o tipo de dado nativo JSONB do PostgreSQL. O raciocínio técnico para priorizar o JSONB em detrimento de tabelas normalizadas rígidas é a variabilidade intrínseca de documentos corporativos: cada fatura possui um layout ligeiramente diferente. Armazenar o documento bruto extraído em formato JSONB binário indexado permite consultas analíticas futuras extremamente rápidas através de operadores de contenção do PostgreSQL (@>), sem exigir migrações de esquema constantes para acomodar novas colunas de metadados.

import json
import logging
import azure.functions as func
import psycopg

app = func.FunctionApp()

class PostgresPersistenceAdapter:
    def __init__(self):
        self.conn_string = os.environ["POSTGRESQL_CONNECTION_STRING"]

    def save_extracted_document(self, document_id: str, extracted_data: dict) -> None:
        logging.info(f"Persistindo dados extraídos do documento {document_id} no PostgreSQL...")

        query = """
            INSERT INTO extracted_documents (document_id, raw_payload, status, created_at)
            VALUES (%s, %s, %s, NOW())
            ON CONFLICT (document_id) 
            DO UPDATE SET raw_payload = EXCLUDED.raw_payload, status = EXCLUDED.status;
        """

        with psycopg.connect(self.conn_string) as conn:
            with conn.cursor() as cur:
                cur.execute(query, (document_id, json.dumps(extracted_data), "COMPLETED"))
                conn.commit()

        logging.info("Persistência relacional concluída com sucesso.")

persistence_adapter = PostgresPersistenceAdapter()

@app.route(route="ocr-webhook", auth_level=func.AuthLevel.FUNCTION)
def ocr_completion_webhook(req: func.HttpRequest) -> func.HttpResponse:
    logging.info("Webhook de conclusão de OCR acionado pelo Azure AI Service.")
    try:
        body = req.get_json()
        document_id = body.get("documentId")
        analysis_result = body.get("analyzeResult", {})

        persistence_adapter.save_extracted_document(document_id, analysis_result)

        return func.HttpResponse(json.dumps({"status": "Success"}), status_code=200, mimetype="application/json")
    except Exception as e:
        logging.error(f"Erro ao processar webhook de OCR: {str(e)}")
        return func.HttpResponse(json.dumps({"error": str(e)}), status_code=500, mimetype="application/json")

Enter fullscreen mode Exit fullscreen mode

Solução de Problemas Comuns

Uma adversidade frequente em pipelines de OCR baseados em documentos volumosos é a ocorrência de falhas silenciosas onde o Azure AI Document Intelligence reporta status de erro no processamento das páginas (InvalidRequest ou UnsupportedDocument). Isso ocorre habitualmente quando o arquivo PDF enviado para a zona de pouso está corrompido, protegido por senha ou vetorizado incorretamente como imagem bitmap de baixa resolução sem camadas de texto limpas. O diagnóstico exige a inspeção dos logs gerados pelo objeto AnalyzeResult na função de retorno. Para mitigar esse problema, implemente um adaptador de validação prévia no Blob Trigger que rejeite arquivos com menos de uma página ou aplique normalizações de imagem utilizando bibliotecas de processamento antes de despachar o payload para o serviço cognitivo.

Outro gargalo comum manifesta-se através de erros de esgotamento de conexões no PostgreSQL (Too many clients) durante picos vertiginosos de processamento em lote (Batch OCR). Como as funções serverless do Azure escalam horizontalmente de forma instantânea, centenas de instâncias Python podem tentar abrir conexões TCP simultâneas com o banco de dados relacional, ultrapassando o limite de conexões simultâneas suportado pelo servidor Flexível do PostgreSQL. A solução arquitetônica exige a introdução de um pooler de conexões intermediário, como o PgBouncer, ou a configuração estrita de padrões Singleton e limites de reuso de conexões dentro do adaptador Python, garantindo que o banco de dados preserve sua integridade transacional sob carga extrema.

Conclusão

A integração do Azure AI Document Intelligence com Azure Functions e PostgreSQL estabelece uma fundação robusta para a automatização de processos baseados em documentos não estruturados. A separação assíncrona entre a ingestão, o escaneamento cognitivo e a persistência relacional blinda a arquitetura contra os limites restritivos de timeout de nuvem. Esta topologia transforma arquivos estáticos em dados transacionais consultáveis em tempo real. Como evolução natural desta plataforma, as equipes de engenharia podem conectar o payload JSONB persistido no PostgreSQL a motores de busca vetorial para habilitar consultas em linguagem natural baseadas em RAG sobre o histórico completo de faturas e contratos corporativos.

Referências

Esposito, D. (2018). Architecting for scale: How to maintain high availability and manage risk in the cloud. O'Reilly Media.

Microsoft Azure. (2024). What is Azure AI Document Intelligence?. Microsoft Learn. https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/overview

PostgreSQL Global Development Group. (2023). PostgreSQL 15 Documentation: JSON Types. PostgreSQL.org. https://www.postgresql.org/docs/15/datatype-json.html

Top comments (0)