Extração de documento com IA: nota fiscal, contrato e o que dá errado
Tirar campos estruturados de PDF e imagem é uma das aplicações de IA com retorno mais direto. Também é onde a diferença entre 95% e 99,9% de acerto muda todo o desenho.
Equipe EasyOps Cloud · · 8 min de leitura
Transformar uma pilha de PDFs em linhas de banco de dados é uma das aplicações de IA com retorno mais evidente. O trabalho é repetitivo, caro em hora humana, e o resultado é verificável.
Também é onde uma diferença aparentemente pequena de precisão muda todo o desenho. Um sistema com 95% de acerto por campo parece ótimo — e num documento com vinte campos, significa que menos de 36% dos documentos saem completamente corretos.
Essa conta é o ponto de partida de qualquer projeto sério nessa área.
O que muda conforme o documento
Nem todo documento tem a mesma dificuldade, e tratar todos igual é o primeiro erro:
| Tipo | Dificuldade | Abordagem |
|---|---|---|
| PDF nativo, layout fixo | Baixa | Extração por posição, sem IA |
| PDF nativo, layout variável | Média | Texto extraído + modelo |
| Digitalizado com boa qualidade | Média | OCR + modelo |
| Foto de celular, torta | Alta | Modelo de visão |
| Manuscrito | Muito alta | Revisão humana obrigatória |
A primeira linha merece destaque porque é frequentemente ignorada. Documento com layout fixo e texto nativo — boleto padronizado, nota fiscal eletrônica, extrato de um único banco — se resolve com biblioteca de extração e regra de posição, com 100% de acerto e custo zero. Usar modelo aí é gastar mais para acertar menos.
Verifique primeiro se o texto já está lá:
pdftotext -layout documento.pdf - | head -40Se sair texto legível, você não precisa de OCR. Se sair vazio ou embaralhado, é imagem.
E vale lembrar do caso ideal: documentos fiscais brasileiros costumam ter uma versão estruturada em XML. Se ela existir, extrair dela é sempre melhor que interpretar o PDF.
Saída estruturada, sempre
Pedir "extraia os dados" e receber texto livre é o caminho para retrabalho. Defina o esquema e exija conformidade:
{
"type": "object",
"properties": {
"numero_nota": {"type": "string"},
"data_emissao": {"type": "string", "format": "date"},
"cnpj_emitente": {"type": "string", "pattern": "^[0-9]{14}$"},
"valor_total": {"type": "number"},
"itens": {
"type": "array",
"items": {
"type": "object",
"properties": {
"descricao": {"type": "string"},
"quantidade": {"type": "number"},
"valor_unitario": {"type": "number"}
},
"required": ["descricao", "quantidade", "valor_unitario"]
}
}
},
"required": ["numero_nota", "data_emissao", "cnpj_emitente", "valor_total"]
}Dois detalhes que evitam problema depois. O CNPJ como string de 14 dígitos, e não número — zeros à esquerda somem em campo numérico, e é o erro clássico. E valores monetários chegando como número, com a conversão de "1.234,56" feita explicitamente, porque o formato brasileiro confunde qualquer analisador que espere ponto decimal.
Peça também que o modelo indique o que não encontrou, em vez de preencher:
Se um campo não estiver presente no documento, retorne null.
Nunca infira, calcule ou complete valor ausente.Campo inventado é pior que campo vazio, porque não há como detectá-lo depois.
Validar com regra de negócio
A validação de esquema garante formato. A de negócio é que detecta erro de leitura, e é onde está o maior ganho:
def validar(nota):
problemas = []
if not cnpj_valido(nota["cnpj_emitente"]):
problemas.append("cnpj com dígito verificador inválido")
soma = sum(i["quantidade"] * i["valor_unitario"] for i in nota["itens"])
if abs(soma - nota["valor_total"]) > 0.02:
problemas.append(f"soma dos itens ({soma:.2f}) difere do total")
emissao = date.fromisoformat(nota["data_emissao"])
if emissao > date.today() or emissao < date.today() - timedelta(days=1825):
problemas.append("data fora do intervalo plausível")
return problemasA verificação de soma é especialmente valiosa: ela cruza campos independentes, e um erro de OCR em qualquer um deles quebra a igualdade. Um dígito lido errado no valor unitário aparece na conta.
Dígito verificador de CNPJ e CPF é validação gratuita e pega erro de leitura de imediato.
Sempre que existir uma relação aritmética entre campos, ela é o melhor detector disponível — melhor que qualquer estimativa de confiança do modelo.
Confiança e o limite da revisão
O modelo pode indicar quão seguro está de cada campo, e isso permite rotear:
Para cada campo extraído, informe também uma confiança de 0 a 1,
baseada na legibilidade do trecho correspondente no documento.Vale saber que essa autoavaliação é aproximada — modelos tendem a ser otimistas. Ela serve para priorizar revisão, não para dispensá-la.
O desenho que funciona combina os dois sinais:
if problemas_de_validacao:
fila = "revisao_obrigatoria"
elif min(confiancas.values()) < 0.85:
fila = "revisao_por_amostragem"
else:
fila = "aprovado"E, mesmo na fila aprovada, vale revisar uma amostra aleatória — 2% a 5%. É o que mede a taxa de erro real e detecta degradação quando o fornecedor atualiza o modelo, ou quando começa a chegar um tipo de documento diferente.
Revisão humana desenhada para ser rápida
Se a revisão for necessária, o tempo dela define o retorno do projeto. Uma interface ruim consome o ganho inteiro.
O que faz diferença:
- Documento e campos lado a lado, sem alternar janela.
- Destaque no trecho de origem de cada valor, para conferir sem procurar.
- Foco automático no campo suspeito, em vez de exigir leitura de tudo.
- Aprovar com uma tecla quando estiver certo, que é a maioria dos casos.
A meta razoável é revisar um documento em menos de vinte segundos. Acima de um minuto, vale comparar honestamente com a digitação manual — pode não estar compensando.
O custo, que é modesto
Extração é uma das aplicações mais baratas de IA. Um documento de duas páginas consome poucos milhares de tokens, e o processamento é assíncrono — sem exigência de latência baixa.
Isso torna o modelo local uma opção real quando o dado é sensível, cenário do artigo sobre API ou modelo próprio. Contrato, prontuário e documento de RH costumam ter restrição contratual sobre sair do ambiente — e aqui a exigência de qualidade permite modelos menores.
Duas reduções valem sempre: enviar apenas as páginas relevantes em vez do documento inteiro, e converter imagem para resolução suficiente em vez de máxima. Página em alta resolução consome muito e não melhora o resultado.
A arquitetura completa
Recebe → guarda original no bucket → detecta tipo
→ tem versão estruturada? usa ela
→ texto nativo com layout fixo? extrai por regra
→ senão: OCR ou modelo de visão
→ saída estruturada por esquema
→ valida formato e regra de negócio
→ roteia: aprovado | amostragem | revisão
→ grava resultado + confiança + versão do modeloGuardar o original é obrigatório. Ele é a única forma de reprocessar quando você melhorar o fluxo, e a evidência quando alguém contestar um valor — e bucket é o lugar certo, não o disco da VM.
Registrar a versão do modelo junto com o resultado permite responder, meses depois, por que um lote saiu diferente do outro.
O erro de projeto mais comum
Prometer automação total. Documento real inclui foto torta, carimbo por cima do número, página faltando e formato que ninguém previu.
O projeto que se sustenta assume revisão desde o início e mede o ganho corretamente: não é "eliminamos a digitação", é "reduzimos de três minutos por documento para vinte segundos de conferência". Esse ganho é grande, é real, e é sustentável — enquanto a promessa de 100% automático leva à decepção quando o primeiro caso difícil aparece, que é sempre na primeira semana.
Melhorar sem trocar de modelo
Quando a taxa de acerto não satisfaz, o instinto é buscar um modelo melhor. Quatro ajustes costumam render mais, e são bem mais baratos.
Melhorar a imagem de entrada. Endireitar página torta, aumentar contraste e converter para escala de cinza resolve uma parte relevante dos erros de leitura:
convert entrada.jpg -deskew 40% -normalize -colorspace Gray saida.pngRecortar a região relevante. Se o campo está sempre no mesmo quadrante, enviar só aquele pedaço melhora o resultado e reduz o custo.
Dar um exemplo no prompt. Um documento do mesmo tipo com a extração correta ao lado calibra o modelo para o seu formato específico — costuma ser o ajuste de maior retorno.
Separar em duas passagens. Uma para os campos do cabeçalho, outra para a tabela de itens. Pedir tudo de uma vez em documento denso produz mais erro que duas chamadas focadas.
Vale testar esses quatro antes de qualquer troca. Além de mais baratos, eles continuam valendo se você trocar de modelo depois — enquanto o inverso não é verdade.
Reprocessar é parte do desenho
Uma consequência de guardar os originais que vale explicitar: o fluxo de extração vai melhorar, e você vai querer aplicar as melhorias ao que já passou.
Isso exige que o reprocessamento seja uma operação normal, não um esforço excepcional. Na prática, três decisões viabilizam:
- Resultado versionado, com a extração antiga preservada em vez de sobrescrita. Assim dá para comparar e medir se a nova versão é de fato melhor.
- Identificação estável do documento, para que o reprocessamento saiba o que atualizar.
- Processamento em lote assíncrono, com controle de velocidade — reprocessar cinquenta mil documentos de uma vez satura API e disco.
O ganho aparece de duas formas. Você consegue afirmar objetivamente que a versão nova é melhor, comparando as duas sobre o mesmo conjunto. E o histórico de documentos antigos, extraído com uma versão pior, deixa de ser dívida permanente.
Sem isso, cada melhoria vale só para o que chegar daqui em diante — e a base fica com qualidade desigual, o que atrapalha qualquer análise que dependa dela.