DGO Foresight & Intel Inteligência de mercado e pesquisa aplicada
Contato

Método · Extrator de balanços

O que é o extrator de balanços, e como ele foi feito.

Empresa de capital aberto é obrigada a publicar o balanço em jornal. Sai em PDF, cada uma com um layout, muitas escaneadas. O extrator lê esses arquivos e devolve uma planilha com uma linha por empresa. A parte difícil não é ler o número: é saber, com precisão, quando não se conseguiu ler.

O que entra e o que sai

Entra um PDF de balanço publicado. Sai uma linha com razão social, CNPJ, exercício, escala, escopo, valor e status. O status é o campo que faz a base valer alguma coisa, e vou chegar nele.

Foi construído e calibrado contra 15 balanços reais de empresas capixabas, não contra documento sintético. Cada regra descrita abaixo existe porque um arquivo específico quebrou sem ela.

Como funciona, em quatro etapas

  1. Localizar. Achar, dentro do PDF, o retângulo onde está a demonstração de resultado. Usa a camada de texto quando ela existe e presta; quando não presta, faz OCR da página. Um jornal traz várias demonstrações lado a lado, então a busca é por palavra e não por linha.
  2. Recortar. Renderizar só aquele retângulo em imagem de boa resolução. Recorte pequeno é barato de processar e não carrega o resto da página junto.
  3. Extrair. Onde a camada de texto é confiável, a leitura é determinística e custa zero. Só o que sobra vai para um modelo de visão. No corpus de teste, a maioria dos arquivos se resolve sem gastar um token.
  4. Validar. Conferir o número contra a aritmética do próprio documento, e atribuir o status conforme o resultado.

Duas regras de roteamento cortaram um lote de 30 arquivos de cerca de 20 minutos para 4m53s, sem mudar um valor sequer: a varredura da camada de texto passou a rodar primeiro no documento inteiro, em vez de alternar com OCR página a página, e a barra para dispensar o OCR virou um escore mínimo em vez de qualquer escore positivo.

Por que não é RAG, e por que não é fine-tuning

RAG serve para achar informação num corpus grande. Aqui o problema é o inverso: dado um documento, achar uma seção conhecida. Embedding e banco vetorial seriam custo sem retorno.

Fine-tuning não entra primeiro porque nenhum dos erros observados neste corpus foi erro de ler número. Foram erros de escala, de coluna, de escopo e de layout. Isso é regra de negócio, e regra de negócio precisa morar em código que alguém pode auditar, não em pesos que ninguém inspeciona.

O status, que é o ponto inteiro

Se "não consegui extrair" e "a empresa faturou zero" virarem o mesmo campo vazio, quem usar a base vai somar os dois e concluir por cima da mistura, sem nunca saber. Por isso cada registro carrega o próprio estado:

Os estados possíveis de um registro
StatusSignificado
okextraído e validado
receita_zero0,00 impresso: dado válido
sem_movimentotraço explícito à direita do rótulo
dre_ausentenão há demonstração no documento
receita_nao_localizadaa demonstração existe, o valor não foi localizado
revisarextraiu, mas a validação não fecha
errofalha técnica

Erro de leitura sai como erro, nunca como zero.

Três desses são resposta e entram na base como fato: receita_zero, sem_movimento e dre_ausente. Os outros dizem, com precisão, o que falta.

Um hífen que apagou R$ 202,7 milhões

Vale um exemplo de por que essas distinções custam trabalho. sem_movimento exige um traço explícito à direita do rótulo. Só que hífen não é traço, e a diferença é tipográfica. Um hospital publica assim:

RECEITA DE SERVIÇOS DE SAÚDE -
19  180.969.410  170.503.540
CONTRATO DE GESTÃO

O - ali não é ausência de valor: é o começo de "- CONTRATO DE GESTÃO", que quebrou para a linha seguinte. O registro saiu como empresa sem faturamento, quando a receita era R$ 202,7 milhões.

O que separa os dois é o espaçamento, comparado com o da própria linha. Traço de valor fica na coluna, longe do texto. Hífen de rótulo fica colado na palavra anterior. Medido em todos os sem_movimento de um lote de 150 arquivos:

Distância até a palavra anterior, em múltiplos do espaço da própria linha
CasoMultiplicador
Hífen de rótulo1,0×
Traços de valor legítimos4,2× a 122,5×

O limiar de 3× cai no meio dessa folga, e a medida se calibra sozinha pela mediana dos espaços da linha, então não depende de corpo de fonte nem de resolução de OCR.

Como a validação funciona

A confiabilidade não vem do modelo, vem da aritmética. Demonstração contábil tem redundância embutida, e ela pega quase tudo:

O que cada conferência pega
ConferênciaErro que ela detecta
bruta − deduções = líquidalinha ou coluna errada
líquida ≤ brutainversão de campos
o resultado consta na DMPLcoluna ou ano errados
ativo / receita fora de faixaerro de escala
valor em reais ≤ R$ 20 biescala declarada errada no documento
o trecho literal existe no textonúmero inventado pelo modelo

A quinta linha existe por um motivo específico. As outras comparam dois números do mesmo documento, então não enxergam rótulo de escala errado: se o documento inteiro está mil vezes fora, a razão entre ativo e receita continua perfeita. Uma construtora declara "(Em milhares de Reais)" num quadro cujos valores estão em reais, e passava como ok com R$ 145 bilhões de receita. O teto é alto de propósito: empresa brasileira acima de R$ 20 bi se conta nos dedos, e nenhuma publica balanço em jornal do interior.

O limite que nenhuma conferência atravessa

Aqui está a parte que interessa a quem vai construir algo parecido. O cache de OCR era indexado pelo endereço de memória do documento. Quando um documento era liberado e o próximo alocado no mesmo endereço, a chave colidia e o cache devolvia as palavras do documento anterior. Medido: o mesmo valor, 29.766.643,66, em quatro empresas diferentes numa rodada de 30.

Nenhuma conferência pega isso. Bruta menos deduções dá líquida, o resultado aparece na DMPL, a magnitude é plausível. Tudo fecha, porque o documento lido é internamente coerente. Ele só não é o documento certo.

A validação verifica consistência interna, não identidade. São coisas diferentes, e a segunda não se deduz da primeira. Todo pipeline desse tipo tem esse limite; o que varia é se ele está escrito em algum lugar.

A correção passou a usar o nome do documento como chave. Isso também colide, e o teste do caminho de imagem pegou na primeira execução: um PDF aberto a partir de bytes tem sempre o mesmo nome. Em produção nunca mordeu, porque os arquivos vêm do disco com caminho próprio, mas o buraco estava exatamente dentro da guarda contra o bug mais caro do projeto. Hoje a chave é um contador gravado no próprio documento: não recicla e não colide.

O placar, e por que ele mede pouco

No corpus onde as regras foram encontradas, o resultado é 20 de 20 corretos, com zero erros silenciosos: nenhum arquivo entrou na base com número errado se dizendo ok. Esse é o único critério que importa numa base que vai alimentar decisão.

E ele não diz quase nada sobre o próximo arquivo. Medido nesta mesma base: 20 de 20 no corpus conhecido, 0 valores em 9 arquivos cegos, e 10 de 10 depois de consertar. Cada rodada de consertos sobe o número do corpus conhecido e não prevê o desempenho fora dele.

Por isso o número que vale reportar é sempre o primeiro, medido em arquivo nunca visto, antes de qualquer conserto. Quem só mostra o placar depois das correções está mostrando o quanto ajustou ao corpus, não o quanto o sistema funciona.

O que perguntar a uma base extraída de documento

  1. Como o vazio é representado? Se há um único campo vazio para tudo, "não li" e "é zero" já se misturaram, e não há como separá-los depois.
  2. Qual foi o placar no primeiro contato com arquivo novo? Antes de qualquer conserto.
  3. O que a validação não consegue ver? Consistência interna não é identidade.

Onde isso está

O extrator é motor proprietário e o repositório é privado, então esta página é a documentação pública dele. Os números vieram do próprio corpus e das rodadas registradas. Se algum não bater, é bug, e eu quero saber.