Intermedio 11 minFinanza

Contabilità: estrazione di factures

Risposta diretta

Per estrarre i dati dalle fatture in locale, rendi disponibile un modello di visione (Qwen 3.5 9B, 6,6 GB, o Gemma 4) con Ollama, imponi uno schema JSON alla sua risposta, poi valida i risultati tramite codice: imponibile più IVA uguale totale IVA inclusa, SIRET, date. Le fatture che non superano i controlli vengono sottoposte a revisione umana. Da settembre 2026, le fatture elettroniche strutturate arrivano senza bisogno di IA: questa pipeline serve per i PDF semplici e le scansioni.

Inserire fatture a mano è lento e soggetto a errori, ma affidare documenti contabili a un servizio online solleva problemi di riservatezza. Un modello di visione locale legge la pagina, uno schema impone la forma del risultato e controlli deterministici filtrano gli errori. Questa guida costruisce l'intero pipeline, dal tri delle PDF all'importo contabile, e ricorda cosa cambia la fattura elettronica a partire settembre 2026.

Di Mohamed Meguedmi·Agg. 2026-09-30·Testato su Windows, macOS e Linux

#Ciò che si automatizza e con quale livello di affidabilità

A partire dal PDF di una fattura di un fornitore, si vuole ottenere un JSON strutturato (fornitore, numero, data, importi al netto dell'IVA, IVA e importi IVA inclusa, righe) che si possa importare in batch nel software contabile. Un modello di visione reso disponibile tramite Ollama legge direttamente l'immagine della pagina, senza passare da un OCR separato. La regola che rende affidabile il sistema si riassume in una frase: il modello legge, il codice controlla. Un'estrazione non viene mai importata senza aver superato controlli aritmetici e sugli identificativi, e tutto ciò che non supera i controlli viene messo in coda per una revisione umana.

Questa guida riguarda un caso concreto: uno studio professionale o una PMI che riceve qualche centinaio di fatture al mese, con elaborazione su un computer o un piccolo server locale. Qui non viene promessa alcuna percentuale di accuratezza: l'accuratezza dipende dai tuoi fornitori, dalla qualità delle scansioni e dal modello. Il metodo di misurazione è descritto più avanti e si applica a un campione delle tue fatture.

#Fattura elettronica: cosa cambia nel 2026 e nel 2027

Il kit IA Locale

Il tuo ChatGPT privato e gratuito sulla tua macchina in 1 ora — LM Studio, Ollama, Open WebUI, i tuoi documenti, senza cloud.

  • Spazio online a vita
  • PDF + file
  • Aggiornamenti a vita

Prima di costruire un pipeline di lettura, bisogna vedere cosa la riforma fa alle fatture in ingresso. A partire dal 1 settembre 2026, secondo impots.gouv.fr, le grandi imprese e le imprese di dimensione media devono emettere le fatture attraverso una piattaforma autorizzata, e tutte le imprese devono essere in grado di ricevere fatture elettroniche. Per le PMI, le TPE e le microimprese, l'obbligo di emissione si applica a partire dal 1 settembre 2027, secondo il manuale pratico dell'amministrazione fiscale.

La conseguenza pratica è duplice. Da un lato, una parte sempre maggiore delle tue fatture in entrata arriverà già in forma strutturata, senza che sia necessaria alcuna lettura da parte dell'IA. Dall'altro, il formato Factur-X, standard franco-tedesco per le fatture ibride, include nello stesso file un PDF leggibile e dati XML per l'elaborazione automatizzata, con diversi profili di dati. Quando un PDF contiene questo XML, leggerlo direttamente è più sicuro che farlo indovinare a un modello. La pipeline qui sotto applica quindi un ordine di priorità: dati strutturati se presenti, poi testo nativo del PDF, visione come ultima risorsa.

i
Cosa non copre questa guida
La ricezione tramite una piattaforma autorizzata, l'e-reporting e gli obblighi di emissione dipendono dalla piattaforma che scegli e dal fornitore del tuo software contabile. Questa pipeline riguarda le fatture che arrivano ancora come semplici PDF o scansioni: fornitori esteri, piccoli prestatori di servizi, scontrini, documenti cartacei digitalizzati.

#Scegliere il modello: quanta memoria richiede la visione

Per la lettura delle fatture, nella libreria Ollama sono adatte due famiglie. Qwen 3.5 con 9 miliardi di parametri pesa 6,6 GB, accetta testo e immagini e dichiara una finestra di 256.000 token; la sua versione 27B pesa 17 GB. Gemma 4, nella variante e4b, occupa tra 6,6 e 9,5 GB secondo la scheda Ollama e dichiara 128.000 token di contesto, con supporto per testo e immagini. Una scheda da 12 GB basta quindi per la variante 9B o e4b, con margine per le immagini delle pagine.

Quale strumento per quale tipo di PDF
Tipo di fileStrumentoPerché
PDF Factur-X o XML integratoLettura diretta dell'XMLDati esatti, nessun rischio di errore di lettura
PDF nativo con testo selezionabileEstrazione del testo, poi LLM testualeRapido, nessuna immagine da elaborare
PDF scansionato o foto nitidaModello di visione (Qwen 3.5 9B, Gemma 4)Legge l'impaginazione e le tabelle
Scansione di scarsa qualitàOCR Tesseract, poi revisione umanaLa visione si lascia sviare dal rumore; una persona deciderà

Stima di riferimento del sito per la memoria: un modello da 9 miliardi di parametri in Q4 pesa circa 5–6 GB, a cui si aggiungono la cache del contesto e le immagini della pagina. Le pagine delle fatture multipagina richiedono più risorse delle altre: un gruppo di dieci pagine in un'unica richiesta può superare la finestra predefinita di Ollama, che resta ben al di sotto dei 256.000 token annunciati per il modello finché non la aumenti.

#Classificare i PDF prima di leggerli: nativi, scansionati, strutturati

Rilevare il tipo di file evita di inviare inutilmente un'immagine a un modello di visione. Con la libreria PyMuPDF, un PDF il cui testo estratto supera qualche centinaio di caratteri è nativo; un PDF quasi privo di testo è una scansione. I file Factur-X contengono un allegato XML che si può elencare prima di qualsiasi altra elaborazione.

Instradare in base al tipo di 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'

Per le scansioni di scarsa qualità, Tesseract resta una soluzione di riserva gratuita e offline: occorre allora installare il file per la lingua francese ed eseguire la scansione a 300 punti per pollice. La guida su Tesseract descrive in dettaglio le impostazioni. Un testo ottenuto da un OCR di scarsa qualità non deve mai passare senza controllo: subentra la coda di revisione umana.

Installare Tesseract con il supporto per il francese
# macOS
brew install tesseract tesseract-lang

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

#Estrarre con un modello di visione e uno schema imposto

Ollama può vincolare la risposta del modello a uno schema JSON: secondo la sua documentazione, si fornisce uno schema nel campo format ed è consigliabile ripeterlo anche nel prompt per guidare la risposta. È nettamente più robusto che chiedere «un JSON» in testo libero, perché le chiavi e i tipi sono imposti. Per le immagini, l'API REST si aspetta immagini codificate in base64 nel campo images del messaggio.

Estrazione strutturata di una fattura (Ollama, vision)
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'])

Tre scelte meritano una spiegazione. La temperatura a zero rende l'output riproducibile. Il parametro num_ctx amplia la finestra, perché le immagini di più pagine esauriscono rapidamente la finestra di contesto predefinita di Ollama, che è ridotta; la guida sulla finestra di contesto spiega in dettaglio questo meccanismo. Infine, l'istruzione «Non inventare nulla», con la possibilità di usare il valore null, riduce il rischio più grave: un modello che riempie un campo illeggibile con un valore plausibile. Uno schema rigoroso impone la forma della risposta, non la correttezza del suo contenuto.

#Estendere lo schema ai tuoi casi reali

Lo schema di base copre la maggior parte delle fatture dei fornitori comuni. Le estensioni da prevedere dipendono dalla tua attività. Aggiungile una alla volta e misura l'effetto di ciascuna sul tuo campione: uno schema troppo carico peggiora la lettura dei campi essenziali.

Acconto e saldo
Aggiungi un campo acompte_paye. Senza di esso, il totale IVA inclusa non corrisponde all'importo ancora da pagare.
Riferimenti degli ordini
Un campo bon_commande_ref consente l'abbinamento con i tuoi ordini.
Spese di spedizione e sconti
Riga dedicata o campo port_ht, altrimenti la somma delle righe non coincide con il totale al netto dell'IVA.
Ripartizione dell'IVA
Un elenco di aliquote, basi imponibili e importi. Indispensabile quando la fattura comprende più aliquote.
Imputazione contabile
Non chiedere al modello di indovinare il conto: produci una proposta, contrassegnata come tale, che una tua regola di gestione o una persona confermi.

#Validare prima di importare: i controlli che individuano gli errori

È la fase che determina la qualità del sistema assemblato. Ogni controllo è deterministico, quindi più affidabile del modello. Il primo è aritmetico: l'imponibile (HT) più l'IVA (TVA) deve essere uguale al totale IVA inclusa (TTC), con una tolleranza di pochi centesimi per gli arrotondamenti. Il secondo riguarda le righe: la loro somma deve coincidere con l'imponibile. Il terzo riguarda le date: non devono essere anteriori a un limite temporale ragionevole né essere nel futuro. Il quarto riguarda il SIRET.

Il controllo del SIRET merita particolare attenzione. Il numero è composto da 14 cifre e l'ultima è una cifra di controllo calcolata con la formula di Luhn, precisa Wikipedia. Esiste un'eccezione: le sedi di La Poste, il cui SIREN è 356000000, seguono un'altra regola secondo cui la somma delle 14 cifre deve essere un multiplo di 5. Un controllo ingenuo con Luhn respingerebbe quindi erroneamente delle fatture di La Poste. Il codice seguente gestisce entrambi i casi.

Controlli di coerenza di una fattura estratta
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
!
Mai importare un'incoerenza
Una fattura il cui totale non corrisponde deve finire nella coda «da verificare». Non è garantito che un'estrazione che supera tutti i controlli sia corretta, ma quelle che non li superano sono sicuramente da rivedere: la selezione effettuata da una persona si concentra su queste.

#Misurare l'accuratezza sulle tue fatture

Nessun valore di precisione pubblicato sostituisce una misurazione sul tuo corpus, perché uno studio che gestisce fatture di grossisti non ha gli stessi documenti di un'associazione. Crea un campione di circa cinquanta fatture rappresentative, inserisci a mano i valori dei campi essenziali, poi confronta campo per campo.

  1. 01
    Costituire il campione
    Usa fatture reali e di vario tipo: scansioni, PDF nativi, fatture di più pagine e di diversi fornitori. Anonimizza se condividi i risultati.
  2. 02
    Inserire i dati di riferimento
    Annota a mano i campi da automatizzare: numero, data, importo al netto delle imposte (HT), IVA (TVA), importo comprensivo delle imposte (TTC), SIRET.
  3. 03
    Confrontare campo per campo
    Calcola il tasso di accuratezza per ciascun campo, non complessivamente: un modello può leggere perfettamente le date e sbagliare i valori dell'IVA.
  4. 04
    Impostare una soglia di automazione
    Decidi quali campi possono essere importati senza revisione. Gli importi, se superano il controllo aritmetico, sono buoni candidati; l'imputazione contabile, mai.
  5. 05
    Rieseguire a ogni modifica
    Cambia modello, risoluzione o prompt: elabora di nuovo il campione prima di passare in produzione.

#Elaborare un dossier di fatture in batch

L'elaborazione in batch esegue in sequenza lo smistamento, l'estrazione e la validazione, poi archivia ogni fattura in base al risultato. Conserva sempre due file affiancati: il PDF originale e il JSON estratto.

Elaborazione in batch con coda di revisione
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)

#Inserire i dati nel software contabile

Ogni editor ha il suo formato di importo e le sue specifiche evolvono: partire dalla documentazione del tuo strumento, non da un modello generico. I software di contabilità offrono generalmente un importo tramite file strutturato o un'interfaccia di programmazione. Il più sicuro è creare un file di importo conforme, caricarlo in una cartella di prova, poi confrontare le scritture generate con quelle che avresti inserito manualmente.

Conserva la traccia di audit: il PDF originale, il JSON estratto, la versione del modello e la data di elaborazione. In caso di controllo, devi poter risalire dalla registrazione contabile al documento giustificativo. Le fatture contengono dati personali e commerciali: lavorare in locale evita di affidare questi dati a terzi, ma l'archiviazione dei file resta comunque soggetta alle tue regole di conservazione e sicurezza.

FAQ
Un LLM locale può leggere una fattura scansionata?+
Sì, un modello di visione come Qwen 3.5 9B o Gemma 4 legge l'immagine di una pagina e ne estrae i campi. L'affidabilità dipende dalla qualità della scansione e dall'impaginazione. È quindi necessario convalidare il risultato con controlli aritmetici e sottoporre a una persona ogni fattura che non supera tali controlli.
Serve un OCR in più rispetto al modello di visione?+
Non sempre. Un modello di visione legge direttamente l'immagine, evitando la perdita di informazioni dovuta a un OCR separato. Tesseract resta utile come rete di sicurezza per scansioni molto degradate e per PDF nativi in cui si può semplicemente estrarre il testo. Prova entrambi sul tuo campione anziché basarti su supposizioni.
Come garantire un JSON valido in uscita?+
Ollama permette di passare uno schema JSON nel campo format dell'API: la risposta viene così vincolata a questa struttura. Questo garantisce la forma, non la correttezza dei valori. Aggiungi quindi sempre controlli di coerenza nel codice: importo al netto dell'IVA più IVA, SIRET, date, totale delle righe.
Che cosa cambia con l'obbligo di fattura elettronica per questo tipo di strumento?+
Dal 1 settembre 2026, tutte le imprese devono poter ricevere fatture elettroniche, e l'emissione diventa obbligatoria per le PMI a settembre 2027. Le fatture strutturate, come Factur-X, si leggono senza IA. Il pipeline serve soprattutto ai fornitori che ancora inviano PDF semplici o carta.
Quale scheda grafica serve per estrarre le fatture?+
Un modello con 9 miliardi di parametri in Q4 pesa circa 6 GB, a cui si aggiungono le immagini delle pagine e il contesto. Una scheda da 12 GB è adatta, una da 8 GB basta per fatture di una pagina con un contesto ridotto. Senza GPU, l'elaborazione funziona ma diventa lenta: prevedi un'elaborazione notturna.
È possibile fidarsi dell'imputazione contabile proposta dal modello?+
No, non senza una validazione. Il modello può suggerire un conto a partire dalla denominazione del fornitore, ma è una proposta da confermare tramite una regola aziendale o da parte del contabile. L'errore di imputazione è silenzioso: supera tutti i controlli aritmetici. Consideralo l'unico punto che richiede sempre un controllo umano.
Questa guida ti è stata utile?

Un feedback, un errore, una precisazione? Facci sapere, così la guida migliora per tutti.