Intermediário 11 minFinanças

Contabilidade: extração de factures

Resposta direta

Para extrair faturas localmente, disponibilize um modelo de visão (Qwen 3.5 9B, 6,6 GB, ou Gemma 4) com Ollama, imponha um esquema JSON à resposta e depois valide por código: valor sem impostos (HT) mais IVA (TVA) igual ao valor com impostos (TTC), SIRET, datas. As faturas que não passam nas verificações são encaminhadas para revisão humana. Desde setembro de 2026, as faturas eletrônicas estruturadas chegam sem necessidade de IA: esse pipeline serve para PDFs simples e documentos digitalizados.

Digitar recibos à mão é lento e propenso a erros, mas entregar documentos contábeis a um serviço online gera problemas de confidencialidade. Um modelo de visão local lê a página, um esquema define a estrutura do resultado e controles determinísticos filtram os erros. Este guia monta o pipeline completo, do tri de PDFs até a importação contábil, e lembra o que a factura eletrônica mudará a partir de setembro de 2026.

Por Mohamed Meguedmi·Atualização 2026-09-30·Testado no Windows, macOS e Linux

#O que automatizamos e em que nível de confiabilidade

A partir de um PDF de fatura de fornecedor, queremos obter um JSON estruturado (fornecedor, número, data, valores sem impostos, IVA e valores com impostos, itens) que possa ser importado em lote no software contábil. Um modelo de visão servido pelo Ollama lê diretamente a imagem da página, sem passar por um OCR separado. A regra que torna esse sistema confiável cabe em uma frase: o modelo lê, o código verifica. Uma extração nunca é importada sem passar por verificações aritméticas e de identificadores, e tudo que falha vai para uma fila de revisão humana.

Este guia aborda um caso concreto: um escritório ou uma pequena ou média empresa que recebe algumas centenas de faturas por mês, usando um computador ou um pequeno servidor local. Nenhuma taxa de precisão é prometida aqui: a precisão depende dos seus fornecedores, da qualidade das digitalizações e do modelo. O método de medição aparece mais abaixo, usando uma amostra das suas próprias faturas.

#Fatura eletrônica: o que muda em 2026 e 2027

O kit IA Local

Seu ChatGPT privado e gratuito na sua máquina em 1 hora — LM Studio, Ollama, Open WebUI, seus documentos, sem nuvem.

  • Espaço online vitalício
  • PDF + arquivos
  • Atualizações vitalícias

Antes de montar um pipeline de leitura, é necessário verificar como a reforma afeta as faturas recebidas. Desde 1º de setembro de 2026, segundo impots.gouv.fr, as grandes empresas e as empresas de porte intermediário devem emitir suas faturas por meio de uma plataforma autorizada, e todas as empresas devem estar aptas a receber faturas eletrônicas. Para PME, TPE e microempresas, a obrigatoriedade de emissão se aplica a partir de 1º de setembro de 2027, segundo o guia prático da administração fiscal.

A consequência prática é dupla. Por um lado, uma parcela crescente das faturas que você recebe já chegará em formato estruturado, sem necessidade de qualquer leitura por IA. Por outro lado, o formato Factur-X, padrão franco-alemão de fatura híbrida, inclui em um mesmo arquivo um PDF legível e dados XML para processamento automatizado, com vários perfis de dados. Quando um PDF contém esse XML, lê-lo diretamente é mais seguro do que fazer um modelo inferir seu conteúdo. O pipeline abaixo segue, portanto, uma ordem de prioridade: dados estruturados, se existirem, depois o texto nativo do PDF e, como último recurso, visão.

i
O que este guia não aborda
O recebimento por meio de uma plataforma autorizada, o e-reporting e as obrigações de emissão dependem da sua escolha de plataforma e do fornecedor do seu software contábil. Esse pipeline trata das faturas que ainda chegam em PDF simples ou como imagens digitalizadas: fornecedores estrangeiros, pequenos prestadores de serviços, cupons de caixa, documentos em papel digitalizados.

#Escolher o modelo: o que a visão exige em memória

Para a leitura de faturas, duas famílias são adequadas na biblioteca Ollama. Qwen 3.5 com 9 bilhões de parâmetros pesa 6,6 GB, aceita texto e imagem e anuncia uma janela de 256 000 tokens; sua versão 27B pesa 17 GB. Gemma 4, com sua variante e4b, ocupa entre 6,6 e 9,5 GB de acordo com a ficha Ollama e anuncia 128 000 tokens de contexto, texto e imagem. Uma placa de 12 GB é suficiente para a variante 9B ou e4b, com margem para imagens de páginas.

Qual ferramenta para qual tipo de PDF
Tipo de arquivoFerramentaPor quê
PDF Factur-X ou XML embarcadoLeitura direta do XMLDados exatos, sem risco de erro de leitura
PDF nativo com texto selecionávelExtração de texto, depois LLM de textoRápido, sem imagem para processar
PDF escaneado ou foto nítidaModelo de visão (Qwen 3.5 9B, Gemma 4)Lê o layout e as tabelas
Scan de baixa qualidadeOCR Tesseract, seguido de revisão humanaA visão se perde no ruído; uma pessoa decidirá.

Referência de memória do site: um modelo de 9 bilhões de parâmetros em Q4 ocupa aproximadamente 5 a 6 GB, aos quais se somam o cache de contexto e as imagens da página. As páginas de faturas com várias páginas têm um custo maior do que as demais: um lote de dez páginas em uma requisição pode ultrapassar a janela padrão do Ollama, que continua bem abaixo dos 256.000 tokens anunciados pelo modelo enquanto você não a aumentar.

#Classificar os PDFs antes de ler: nativos, escaneados, estruturados

Detectar o tipo de arquivo evita enviar desnecessariamente uma imagem para um modelo de visão. Com a biblioteca PyMuPDF, um PDF cujo texto extraído tem mais de algumas centenas de caracteres é nativo; um PDF com quase nenhum texto é uma digitalização. Os arquivos Factur-X contêm um anexo XML que pode ser listado antes de qualquer outro processamento.

Rotear conforme o tipo de PDF
import fitz  # PyMuPDF

def type_pdf(chemin):
    doc = fitz.open(chemin)
    pieces = doc.embfile_names()  # pièces jointes intégrées
    if any(n.lower().endswith('.xml') for n in pieces):
        return 'structure'
    texte = ''.join(page.get_text() for page in doc)
    return 'natif' if len(texte.strip()) > 300 else 'scan'

Para digitalizações de baixa qualidade, o Tesseract continua sendo uma alternativa de segurança gratuita e offline: nesse caso, é necessário instalar o arquivo do idioma francês e digitalizar a 300 pontos por polegada. O guia sobre Tesseract detalha as configurações. Um texto resultante de um OCR de baixa qualidade nunca deve passar sem verificação: a fila de revisão humana assume essa tarefa.

Instalar o Tesseract com suporte ao francês
# macOS
brew install tesseract tesseract-lang

# Ubuntu / Debian
sudo apt install tesseract-ocr tesseract-ocr-fra

#Extrair com um modelo de visão e um esquema obrigatório

O Ollama permite restringir a resposta do modelo a um esquema JSON: de acordo com sua documentação, você fornece um esquema no campo format, e é recomendado repeti-lo também no prompt para orientar a resposta. Isso é muito mais robusto do que pedir “um JSON” em texto livre, pois as chaves e os tipos são impostos. Para imagens, a API REST espera imagens codificadas em base64 no campo images da mensagem.

Extração estruturada de uma fatura (Ollama, visão)
import base64, json, requests
from io import BytesIO
from pdf2image import convert_from_path

SCHEMA = {
  'type': 'object',
  'properties': {
    'fournisseur': {'type': 'object', 'properties': {
      'nom': {'type': 'string'},
      'siret': {'type': ['string', 'null']},
      'tva_intra': {'type': ['string', 'null']}}},
    'facture': {'type': 'object', 'properties': {
      'numero': {'type': 'string'},
      'date': {'type': 'string'},
      'echeance': {'type': ['string', 'null']}}},
    'montants': {'type': 'object', 'properties': {
      'ht': {'type': 'number'}, 'tva': {'type': 'number'},
      'ttc': {'type': 'number'}, 'devise': {'type': 'string'}}},
    'lignes': {'type': 'array', 'items': {'type': 'object', 'properties': {
      'description': {'type': 'string'}, 'quantite': {'type': 'number'},
      'pu_ht': {'type': 'number'}, 'total_ht': {'type': 'number'}}}}
  },
  'required': ['fournisseur', 'facture', 'montants']
}

PROMPT = ("Tu extrais les données d'une facture française. "
  "Réponds uniquement par un JSON conforme à ce schéma : " + json.dumps(SCHEMA) +
  ". Si une donnée est absente ou illisible, mets null. Les montants sont des nombres (1234.56), "
  "les dates au format AAAA-MM-JJ. N'invente rien.")

def pages_b64(pdf, dpi=200):
    sortie = []
    for img in convert_from_path(pdf, dpi=dpi):
        buf = BytesIO(); img.save(buf, format='PNG')
        sortie.append(base64.b64encode(buf.getvalue()).decode())
    return sortie

def extraire(pdf):
    r = requests.post('http://localhost:11434/api/chat', json={
      'model': 'qwen3.5:9b', 'stream': False, 'format': SCHEMA,
      'messages': [{'role': 'user', 'content': PROMPT, 'images': pages_b64(pdf)}],
      'options': {'temperature': 0, 'num_ctx': 16384}})
    return json.loads(r.json()['message']['content'])

Três opções merecem explicação. A temperatura zero torna a saída reprodutível. O parâmetro num_ctx amplia a janela, pois as imagens de várias páginas consomem rapidamente a pequena janela padrão do Ollama; o guia sobre a janela de contexto detalha esse mecanismo. Por fim, a instrução «Não invente nada», permitindo o valor null, reduz o risco mais grave: um modelo que preenche um campo ilegível com um valor plausível. Um esquema rígido força a forma da resposta, não a precisão do seu conteúdo.

#Expandir o esquema para seus casos reais

O esquema básico cobre a maioria das faturas de fornecedores comuns. As extensões a prever dependem da sua atividade. Adicione-as uma por uma e meça o efeito de cada uma na sua amostra: um esquema excessivamente carregado prejudica a leitura dos campos essenciais.

Adiantamento e saldo
Adicione um campo acompte_paye. Sem ele, o total com impostos incluídos não corresponde ao valor restante a pagar.
Referências de pedidos
Um campo bon_commande_ref permite a conciliação com seus pedidos de compra.
Taxa de frete e descontos
Uma linha dedicada ou o campo port_ht; caso contrário, a soma das linhas não corresponde ao total sem impostos.
Discriminação do IVA
Uma lista com alíquota, base e valor. Essencial sempre que a fatura combinar várias alíquotas.
Classificação contábil
Não peça ao modelo para adivinhar a conta contábil: produza uma proposta, identificada como tal, que sua regra de negócio ou uma pessoa confirme.

#Validar antes de importar: as verificações que detectam erros

Essa é a etapa que determina a qualidade da solução. Cada verificação é determinística, portanto mais confiável que o modelo. A primeira é aritmética: o valor sem impostos (HT) mais o IVA (TVA) deve ser igual ao total com impostos (TTC), com uma tolerância de alguns centavos para arredondamentos. A segunda verifica os itens: a soma deles deve corresponder ao valor sem impostos (HT). A terceira verifica as datas: elas não devem ser anteriores a um limite razoável nem estar no futuro. A quarta verifica o SIRET.

A validação do SIRET merece atenção especial. O número tem 14 dígitos e seu último dígito é um dígito verificador calculado com a fórmula de Luhn, segundo a Wikipédia. Existe uma exceção: os estabelecimentos da La Poste, cujo SIREN é 356000000, seguem outra regra, em que a soma dos 14 dígitos deve ser um múltiplo de 5. Uma validação ingênua por Luhn rejeitaria, portanto, indevidamente as faturas da La Poste. O código a seguir trata dos dois casos.

Controles de coerência de uma fatura extraída
from datetime import date

def luhn_ok(s):
    total = 0
    for i, c in enumerate(reversed(s)):
        d = int(c)
        if i % 2 == 1:
            d *= 2
            if d > 9:
                d -= 9
        total += d
    return total % 10 == 0

def siret_valide(s):
    if not (s.isdigit() and len(s) == 14):
        return False
    if s.startswith('356000000'):  # La Poste : somme multiple de 5
        return sum(int(c) for c in s) % 5 == 0
    return luhn_ok(s)

def valider(f):
    erreurs = []
    m = f['montants']
    if abs(m['ht'] + m['tva'] - m['ttc']) > 0.02:
        erreurs.append('HT + TVA différent du TTC')
    lignes = f.get('lignes') or []
    if lignes and abs(sum(l['total_ht'] for l in lignes) - m['ht']) > 0.05:
        erreurs.append('somme des lignes différente du HT')
    siret = (f['fournisseur'].get('siret') or '').replace(' ', '')
    if siret and not siret_valide(siret):
        erreurs.append('SIRET invalide : ' + siret)
    try:
        d = date.fromisoformat(f['facture']['date'])
        if d.year < 2000 or d > date.today():
            erreurs.append('date suspecte : ' + str(d))
    except ValueError:
        erreurs.append('date illisible')
    return erreurs
!
Nunca importar uma incoerência
Uma fatura cujo total não bate deve ser enviada para a fila “a verificar”. Uma extração que passa por todas as verificações não tem garantia de estar correta, mas as que falham certamente precisam ser revisadas: é nelas que se concentra a triagem humana.

#Medir a precisão em suas próprias faturas

Nenhum número de precisão publicado substitui uma medição feita em seu corpus, pois um escritório que processa faturas de atacadistas não tem os mesmos documentos que uma associação. Crie uma amostra de cerca de cinquenta faturas representativas, preencha manualmente os campos essenciais e depois compare campo a campo.

  1. 01
    Montar a amostra
    Use faturas reais e variadas: digitalizações, PDFs nativos, documentos com várias páginas e de vários fornecedores. Anonimize se você compartilhar os resultados.
  2. 02
    Inserir os dados de referência
    Anote manualmente os campos a automatizar: número, data, valor sem impostos (HT), IVA (TVA), valor com todos os impostos incluídos (TTC), SIRET.
  3. 03
    Comparar campo por campo
    Calcule a taxa de acerto por campo, não globalmente: um modelo pode ler as datas perfeitamente e errar nos dados de IVA.
  4. 04
    Definir um limite de automação
    Decida quais campos podem ser importados sem revisão. Os valores, se passarem pela verificação aritmética, são bons candidatos; a classificação contábil, nunca.
  5. 05
    Executar novamente a cada mudança
    Se você mudar de modelo, de resolução ou de prompt, processe a amostra novamente antes de passar para produção.

#Processar uma pasta de faturas em lote

O processamento em lote executa em sequência a classificação, a extração e a validação, depois organiza cada fatura conforme o resultado. Mantenha sempre dois arquivos lado a lado: o PDF original e o JSON extraído.

Processamento em lote com fila de revisão
from pathlib import Path
import json

def traiter(dossier_in, dossier_ok, dossier_revue):
    for pdf in Path(dossier_in).glob('*.pdf'):
        try:
            f = extraire(str(pdf))
            erreurs = valider(f)
        except Exception as e:
            f, erreurs = {}, ['échec extraction : ' + str(e)]
        dest = Path(dossier_ok if not erreurs else dossier_revue)
        (dest / (pdf.stem + '.json')).write_text(
            json.dumps({'donnees': f, 'erreurs': erreurs}, ensure_ascii=False, indent=2))
        pdf.rename(dest / pdf.name)
        print(('OK ' if not erreurs else 'A VERIFIER ') + pdf.name)

#Transferir os dados para o software contábil

Cada editor tem seu formato de importação e suas especificações evoluem: comece pela documentação do seu ferramenta, não de um modelo genérico. Os softwares de contabilidade oferecem geralmente importação por arquivo estruturado ou uma interface de programação. O mais seguro é gerar um arquivo de importação compatível, carregá-lo em uma pasta de teste e comparar as gravações geradas com as que você teria digitado.

Mantenha a trilha de auditoria: o PDF original, o JSON extraído, a versão do modelo e a data de processamento. Em caso de fiscalização, você deve conseguir rastrear o lançamento contábil até o documento comprobatório. As faturas contêm dados pessoais e comerciais: manter o processamento local evita entregar esses dados a terceiros, mas o armazenamento dos arquivos continua sujeito às suas regras de retenção e segurança.

FAQ
Um LLM local pode ler uma fatura digitalizada?+
Sim, um modelo de visão como Qwen 3.5 9B ou Gemma 4 lê a imagem de uma página e extrai os campos. A confiabilidade depende da qualidade da digitalização e do layout. Portanto, é necessário validar o resultado com verificações aritméticas e encaminhar para uma pessoa qualquer fatura que não passe nessas verificações.
É necessário um OCR além do modelo de visão?+
Não necessariamente. Um modelo de visão lê diretamente a imagem, o que evita a perda de informações de um OCR separado. O Tesseract continua sendo útil como recurso de apoio para digitalizações muito degradadas e para PDFs nativos, nos quais é possível simplesmente extrair o texto. Teste os dois na sua amostra em vez de presumir.
Como garantir um JSON válido na saída?+
O Ollama permite passar um esquema JSON no campo format da API: a resposta fica então restrita a essa estrutura. Isso garante o formato, não a exatidão dos valores. Portanto, adicione sempre verificações de consistência no código: valor sem impostos (HT) mais IVA (TVA), SIRET, datas e total das linhas.
O que muda com a obrigatoriedade da fatura eletrônica para esse tipo de ferramenta?+
Na França, desde 1º de setembro de 2026, todas as empresas devem poder receber faturas eletrônicas, e a emissão passa a ser obrigatória para pequenas e médias empresas em setembro de 2027. Faturas estruturadas, como Factur-X, podem ser lidas sem IA. O pipeline serve sobretudo para fornecedores que ainda enviam PDFs simples ou faturas em papel.
Qual placa gráfica é necessária para extrair faturas?+
Um modelo com 9 bilhões de parâmetros em Q4 ocupa aproximadamente 6 GB, além da memória para as imagens das páginas e o contexto. Uma placa de 12 GB é adequada, e uma de 8 GB basta para faturas de uma página com contexto reduzido. Sem GPU, o processamento funciona, mas fica lento: planeje executá-lo à noite.
É possível confiar na classificação contábil proposta pelo modelo?+
Não, não sem validação. O modelo pode sugerir uma conta com base na descrição do fornecedor, mas é uma proposta que precisa ser confirmada por uma regra de negócio ou pelo contador. O erro de classificação contábil é silencioso: ele passa por todas as verificações aritméticas. Trate essa classificação como a única área que sempre exige revisão humana.
Este guia ajudou você?

Um comentário, um erro ou uma observação? Avise-nos; isso ajuda a melhorar o guia para todos.