Skip to content

10ge6/pdf-extractor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sistema de Extração Estruturada Adaptativa

Este repositório contém a implementação completa do Enter Extractor, uma solução de extração estruturada de PDFs desenvolvida para o desafio do Enter AI Fellowship. O sistema foi projetado para equilibrar acurácia (80%+), baixo custo por requisição e latência inferior a 10 s, mantendo um processo de aprendizagem incremental por documento.


TL;DR

O Enter Extractor combina heurística geométrica, validação algorítmica e aprendizado iterativo leve, atingindo o equilíbrio ideal entre custo, velocidade e precisão.

A analogia final é simples:

Cada campo começa como um “palpite” dentro de uma pirâmide de confiança. A cada documento, ele desce um degrau ou fica onde está, até se estabilizar como uma regra fixa. Quando todos os campos de um label amadurecem, o sistema se torna praticamente determinístico, além de dispor de caches modulares que permitem responder a combinações de chaves maduras com custo marginal zero, mesmo em configurações inéditas.


Objetivo

Extrair informações estruturadas de documentos PDF (com OCR embutido) a partir de um label, um schema parcial e o próprio arquivo, retornando JSONs consistentes, validados e cada vez mais rápidos à medida que a sessão evolui.

A arquitetura possui diferentes tipos de cache para operar sem reprocessar PDFs idênticos nem fazer chamadas redundantes ao LLM, além de aprender progressivamente a partir dos resultados anteriores — um processo que chamamos de maturação de receitas.


Arquitetura Geral

O pipeline é composto por três camadas principais:

  1. Pré-processamento e OCR: Conversão do PDF em linhas e caixas delimitadoras (bboxes) com o pdfminer.
  2. Extração determinística: Identificação de padrões (regex), alinhamentos geométricos e ancoragens contextuais.
  3. Backfill via LLM: Chamadas únicas ao gpt-5-mini para campos ausentes, usadas como ground truth para treinar e ajustar a confiança do sistema.

Todo o fluxo é cacheado e autoajustável em tempo de execução.

A ideia central é ensinar o sistema a se autoexplicar: cada campo é tratado como uma hipótese sobre o tipo de dado que ele representa. A maturação dessas hipóteses segue uma hierarquia de especificidade.


Type Inference

A inferência de tipo é o primeiro passo sempre que um novo campo (key) aparece em um determinado label.

Pode-se imaginar uma pirâmide de confiança, onde cada nível representa o quão específico e confiável é a crença que o sistema possui no contexto de determinado valor:


Regexable — “Identidade com CPF”

Quando o campo é verificável por formato (CPF, CNPJ, Data, CEP, etc.), ele é considerado um tipo de identidade única. O sistema usa o mapa de sinônimos (enter_extractor/configs/synonyms_map.json) para identificar se o nome da chave sugere algum padrão validável e aplica os validadores formais (enter_extractor/regex/regex_validator_rules.txt. A depender da chave, checa-se apenas o formato. Para valores com checksum há essa validação mais rígida: CPF, CNPJ, PIS, Boleto, etc).

Critérios:

  • Apenas um match por documento (unicidade);

  • Busca em duas fases:

    • BBox-clean: bloco isolado contendo apenas o padrão;
    • Text-guarded: valor cercado por delimitadores (início/fim de linha ou palavra-chave);
  • Execução do validador de formato (checksum, data, moeda, UF etc.);

  • Se passar em todas as etapas, o campo é considerado regexable e seu padrão é armazenado como receita.


Aligned — “Relação pai-filho”

Caso o campo não seja verificável por regex, procuramos uma âncora textual próxima (por exemplo, “Nome:” à esquerda de “João da Silva”). Essa relação key → value é espacial, capturada por coordenadas (direção, distância e linha base).

Critérios:

  • O valor deve estar paralelo horizontal ou verticalmente ao rótulo (i.e. abaixo ou à direita do mesmo);

  • Não pode haver outro texto entre o rótulo e o valor;

  • A receita armazenada contém:

    • BBox da âncora
    • Direção (horizontal ou vertical)
    • Regras de continuidade multiline

  • Nas vezes seguintes que tratamos esse valor, procuramos pelo rótulo e percorremos o caminho reverso, esperando encontrar o valor.

Independent — “Localização confiável”

Se nem regex nem âncora textual forem confiáveis, o sistema grava apenas a região geométrica do valor encontrado pelo LLM (x, y, w, h). É como marcar no mapa: “da última vez que vi um CEP neste tipo de documento, ele estava aqui”. Um exemplo de valor Independent é o campo de nome na carteira da OAB fornecida como exemplo para este projeto.

Esses campos ainda são previsíveis — apenas não dependem de ancoragem textual.


LLM-only — “Zona cinzenta”

Quando a confiança de um campo cai abaixo do limite de maturação (75%), ele é rebaixado a LLM-only. A partir desse ponto, ele sempre será extraído pelo modelo, sem tentativa de heurística.

Esse estágio representa o fim da pirâmide — um campo puramente semântico, não determinístico.


O Loop de Maturação

O leitor atento deve ter percebido que apenas assinalar um desses tipos a um valor na primeira vez que o encontramos é um tanto arriscado.

Cada (label, key) passa por um ciclo de maturação — um mecanismo de aprendizado baseado em feedback loops de reforço, iterados uma quantia fixa de vezes:

  1. Predição: O sistema tenta extrair o valor com a receita atual (regex/aligned/independent).

  2. Validação: Em paralelo, o LLM extrai os mesmos campos (para valores missing + maturing).

  3. Comparação: Se o valor previsto coincide com o valor do LLM → acertos++.

  4. Cálculo de confiança: confiança = acertos / tentativas.

  5. Decisão:

    • Se confiança ≥ 0.8 → o campo matura (receita finalizada).
    • Se confiança < 0.8 após o mínimo de tentativas → o campo é rebaixado para o próximo tipo na hierarquia.

⚠️ Nunca há promoção. Um campo pode apenas descer de nível conforme falha, garantindo convergência e evitando loops infinitos.


Caching e Performance

Todos os caches são em memória (sessão):

Cache Chave Conteúdo Capacidade
Recipe cache (label, key) tipo, confiança, contadores 10 000
Doc cache hash(PDF + schema) JSON final 500
Parsed-page cache hash(PDF) linhas + bboxes 250

Esses mecanismos permitem que o sistema reutilize o conhecimento local e responda rapidamente a PDFs repetidos ou semelhantes.


Fluxo Operacional (por documento)

  1. Entrada: (label, extraction_schema, pdf)
  2. Normalização: limpa acentos, reduz a lower case e assimila sinônimos.
  3. OCR parsing: converte o PDF em blocos com texto e coordenadas.
  4. Busca de regexables: tenta validar padrões formais.
  5. LLM backfill: se necessário, executa uma única chamada com os campos ausentes.
  6. Fusão de resultados: combina matches regex + LLM.
  7. Resolução alinhada e independente: aplica regras geométricas Δy e regiões.
  8. Maturação: compara previsão × verdade do LLM, atualiza confiança.
  9. Demotion: campos com baixa confiança são rebaixados.
  10. Retorno: JSON final + métricas de custo/latência.

Métricas em Tempo de Execução

O sistema mantém contadores internos (em main.py) que acumulam:

  • Total de requisições
  • Custo médio em USD
  • Latência média em segundos

Esses valores são retornados opcionalmente com --metrics.


Estrutura de Arquivos

enter_extractor/
├── main.py                # CLI principal + maturação + métricas
├── core/
│   ├── cache.py           # LRU caches de sessão
│   ├── schema.py          # controle de maturidade por (label, key)
│   └── metrics.py         # rolling averages
├── io/
│   ├── pdf_loader.py
│   └── ocr_parse.py       # parser OCR (pdfminer)
├── regex/
│   ├── patterns.py        # regexes canônicos
│   ├── detector.py        # busca de padrões em texto
│   ├── validators.py      # validações formais (CPF, CNPJ, etc.)
│   └── synonyms_map.json  # sinônimos e expressões regulares
├── aligned/
│   └── resolve.py         # resolução key↔value alinhada
├── independent/
│   └── resolve.py         # captura por posição
└── pipeline/
    ├── llm.py             # integração GPT-5-mini
    └── context.py         # preparação de schema e overrides


Uso via CLI

Na root do repo, rodar:

pip install -r requirements.txt
python -m enter_extractor.main path/to/requests.json path/to/pdf_or_folder

Uso via Web UI

⚠️ Resposta recebida de vez. Não usar para medir tempo de resposta de submissões individuais; para este fim, usar o CLI. Na root do repo, rodar:

pip install -r requirements.txt
uvicorn server:app --reload --workers 1

Na pasta enter-ui/, rodar:

npm run dev

Exemplo de requests.json

Entrada

{
  "label": "carteira_oab",
  "pdf_path": "oab_1.pdf",
  "extraction_schema": {
    "nome": "Nome do profissional",
    "inscricao": "Número de inscrição",
    "seccional": "Seccional do profissional"
  }
}

Saída esperada

{
  "pdf_path": "oab_1.pdf",
  "label": "carteira_oab",
  "extracted": {
    "nome": "JOANA D'ARC",
    "inscricao": "101943",
    "seccional": "PR"
  },
  "metrics": {
    "requests": 3,
    "avg_cost_usd": 0.00053,
    "avg_latency_s": 1.92
  }
}

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors