La fattura sembra letta bene. Possiamo importarla?
Un piccolo ufficio riceve fatture da fornitori diversi. Copiare importi e righe nel gestionale richiede attenzione; un assistente AI promette dati già pronti. Ma una tabella ordinata non completa il lavoro: un totale plausibile può appartenere al documento sbagliato, una riga può sparire e il fornitore può essere confuso con il destinatario. Questo articolo aiuta chi gestisce amministrazione o automazione documentale a decidere quali verifiche mettere tra lettura automatica e registrazione. La domanda è concreta: che cosa dimostrano i controlli sui numeri, e che cosa non possono dimostrare?
La risposta anticipata è che la coerenza aritmetica intercetta una parte degli errori, ma non equivale alla fedeltà al documento. Lo dimostriamo con dodici registrazioni sintetiche, un validatore eseguito e una simulazione dei tempi di lavoro. Non abbiamo eseguito OCR, interrogato un modello, trattato fatture reali o misurato operatori. I risultati appartengono all' esempio dichiarato, non stimano l' affidabilità di un prodotto e non costituiscono validazione fiscale. Il contributo è un metodo verificabile per progettare il passaggio verso il gestionale.
Prima scelta: serve davvero leggere una pagina?
Se arriva un file strutturato utilizzabile, con campi identificati e formato supportato, confrontare un importatore deterministico con il percorso AI. Deterministico significa che regole esplicite trasformano gli stessi dati nello stesso modo, senza chiedere a un modello di indovinare dove sia il totale. Non significa che il file sia vero o privo di errori: restano identità, autorizzazioni e riconciliazione. Significa evitare una ricostruzione visiva quando l' informazione è già strutturata. Questa alternativa va valutata prima di comprare capacità di riconoscimento delle immagini.
Per scansioni e PDF non strutturati, distinguere tre lavori. L' OCR, riconoscimento ottico dei caratteri, prova a leggere segni e parole. L' estrazione assegna quelle parole a campi come fornitore e importo. La validazione controlla se i campi rispettano vincoli e corrispondono alla fonte. Un numero può essere trascritto correttamente e assegnato al campo sbagliato. Separare i passaggi rende gli errori diagnosticabili: cambiare modello non corregge automaticamente una regola che interpreta male la virgola decimale.
Un esempio ricostruibile dall'inizio alla fine
Il documento ideale contiene due righe nette da 120 e 80 euro, netto 200 euro, imposta dichiarata 40 euro e totale 240 euro, fornitore SUP-A e valuta EUR. Sono dati inventati per l' esperimento. Non ricaviamo l' imposta da un' aliquota e non suggeriamo un trattamento tributario. Escludiamo sconti e oneri a livello documento, acconti, arrotondamenti speciali, valute diverse e note di credito. Questo perimetro serve a capire la logica, non a ridurre un processo reale a quattro numeri. Un documento fuori perimetro va segnalato, non forzato nello schema.
Nel codice gli importi sono centesimi interi: 120 euro diventano 12000. Le somme sono così esatte, senza tolleranze arbitrarie per numeri binari in virgola mobile. Ciò non autorizza ad arrotondare silenziosamente ogni prezzo a due decimali: prezzi unitari, quantità e arrotondamenti possono richiedere regole diverse. Qui le righe sono già importi netti finali in centesimi. Un sistema completo conserva il valore originale e applica una politica decimale esplicita. La documentazione Python di Decimal distingue rappresentazione e arrotondamento.
Far tornare i conti: necessario ma non sufficiente
Quale domanda risolve la formula? Se le righe estratte coprono il documento e sono assegnate correttamente, la loro somma deve coincidere con il netto; netto e imposta devono ricostruire il totale. l_i è l' importo della riga i, N il netto, T l' imposta e G il totale. Tutti sono centesimi; m è il numero di righe, senza unità. Non chiediamo all' AI di giudicare la propria risposta: applichiamo vincoli esterni espliciti.
All' inizio 12000 + 8000 = 20000 e 20000 + 4000 = 24000: i residui sono zero. Se l' estrazione cambia soltanto G in 25000, il secondo residuo vale 1000 centesimi, dieci euro. Sappiamo che i campi non possono essere tutti corretti nelle ipotesi dichiarate; non sappiamo quale correggere. Cambiare automaticamente il totale per farlo coincidere con la somma cancella la traccia dell' errore e potrebbe alterare un totale letto bene mentre l' imposta è sbagliata. Il residuo richiede una verifica della fonte, non ordina di correggere una colonna particolare.
Peppol BR-CO-10 e BR-CO-15 documentano relazioni tra somme di riga e totali. Il nostro validatore usa la logica aritmetica in un sottoinsieme semplificato: non implementa lo standard, XML, tutte le regole o gli arrotondamenti. Il totale comprensivo d' imposta non è sempre il residuo da pagare. Un validatore reale richiede la definizione della famiglia di documenti e delle regole effettive: questo esempio non dimostra conformità a Peppol o alla fatturazione italiana.
Tutte le somme sono giuste, la registrazione è sbagliata
Costruiamo un errore che nessuna delle due equazioni scopre. L' estrattore restituisce righe da 132 e 88 euro, netto 220, imposta 44 e totale 264: ogni importo è moltiplicato per 1,1. Le somme tornano, ma non corrispondono alla fonte da 240 euro. È un controesempio sufficiente a confutare «se i totali tornano, possiamo importare». Non serve stimare quanto spesso accada per dimostrare che l' implicazione logica è falsa. La frequenza reale richiede invece dati rappresentativi ed etichette attendibili.
Un secondo caso lascia gli importi invariati e cambia SUP-A in SUP-B. Il controllo approva ancora. Numero documento, data e riferimento d' ordine hanno lo stesso limite: se non entrano nei vincoli, i vincoli non li proteggono. Un controllo sull' anagrafica scopre un codice inesistente, ma non distingue due fornitori esistenti. Per quello occorre riconciliare identità e documento, conservando pagina e ritaglio di provenienza del campo. Ogni controllo copre un errore preciso; nessuno diventa universale perché ha un nome rassicurante.
Dodici casi per controllare una politica d'importazione
Il dataset contiene sei copie corrette del record base, C1–C6, e sei record errati, E1–E6. E1 cambia soltanto il totale, E2 il netto ed E3 l' imposta: violano almeno una somma. E4 è la trasformazione coerente appena descritta; E5 ed E6 cambiano soltanto il fornitore. Sono casi progettati per mettere alla prova il controllo, non un campione casuale delle fatture aziendali. La metà di errori è una scelta didattica, non una stima del lavoro reale. Non c' è seed: il dataset è deterministico e incluso integralmente nel codice.
Assegniamo un punteggio sintetico a ciascun record. C1–C6 ricevono 0,99; 0,97; 0,94; 0,85; 0,98; 0,92. E1–E6 ricevono 0,99; 0,98; 0,96; 0,99; 0,97; 0,90. Il punteggio non proviene da un modello e non è una probabilità calibrata. Serve a verificare la pratica di accettare dati quando i controlli passano e il sistema sembra abbastanza sicuro. La soglia 0,95 è un parametro dell' esempio, non una raccomandazione di produzione.
Formalizziamo la decisione per contarla. V_i vale uno se il record i supera i controlli, zero altrimenti; s_i è il punteggio e tau la soglia. A_i indica l' accettazione simulata. Y_i vale uno soltanto se tutti i campi coincidono con il riferimento, informazione nota nell' esperimento ma non disponibile al sistema durante l' uso. V e Y sono diversi: V è osservabile senza sapere la risposta; Y richiede una fonte di verità indipendente. La formula successiva separa quanto automatizziamo da quanti errori lasciamo passare.
Con soglia 0,95 vengono accettati C1, C2, C5, E4 ed E5: cinque record. Sette vanno a revisione. Copertura 5/12, circa 41,7%; tra gli accettati due su cinque sono sbagliati, cioè 40%. La seconda percentuale non usa dodici al denominatore: misura il rischio nel sottoinsieme che evita la revisione. Quando non si accetta nulla, il rischio condizionale non è zero ma non definito, perché quel sottoinsieme non esiste: il codice registra None. Un sistema che non importa nulla non equivale a uno che importa correttamente.
| Soglia | Accettati | Revisione | Errori accettati | Rischio accettati |
|---|---|---|---|---|
| 0 | 9 | 3 | 3 | 33.3% |
| 0.9 | 8 | 4 | 3 | 37.5% |
| 0.95 | 5 | 7 | 2 | 40.0% |
| 0.98 | 3 | 9 | 1 | 33.3% |
| 1.01 | 0 | 12 | 0 | — |
La tabella confronta cinque soglie sullo stesso insieme. Alzarle non elimina necessariamente gli errori più gravi: E4, coerente ma diverso dalla fonte, ha punteggio 0,99. L' errore condizionale può salire mentre la copertura scende, perché vengono esclusi record corretti a punteggio inferiore. Ciò non dimostra che le soglie siano inutili: mostra perché bisogna verificarne il rapporto con gli errori nel proprio dominio. Un numero non diventa probabilità soltanto perché sta tra zero e uno.
Il risparmio si misura dopo le correzioni
Supponiamo due minuti per registrare manualmente un documento, dieci secondi di lavoro attivo per avviare ogni estrazione, un minuto per verificare ogni documento rinviato e cinque minuti per riparare ogni errore accettato. Non sono tempi misurati. Il minuto di revisione include correzione e inserimento dei casi rinviati; i cinque minuti sono lavoro successivo sui soli errori sfuggiti. Assumiamo che la revisione corregga tutto e che gli errori accettati siano poi tutti scoperti. Entrambe le ipotesi possono fallire: il modello dei tempi non garantisce qualità.
Per sapere se l' automazione riduce il lavoro, sommiamo attività prima nascoste. n è il numero di documenti, a quelli accettati automaticamente, e quelli errati fra questi. t_0, t_r, t_e e t_m sono secondi per avvio, revisione, riparazione e registrazione manuale. W è il lavoro attivo totale. Non calcoliamo attesa del cliente o durata del servizio: il parallelismo può ridurre il calendario senza ridurre i minuti di attenzione umana, e viceversa. L' ultima disuguaglianza ci dice quanto lavoro di riparazione possiamo sostenere prima di perdere il vantaggio.
Alla soglia 0,95 il lavoro iniziale è 12×10 + 7×60 = 540 secondi, nove minuti: sembra meno dei ventiquattro minuti manuali. Due errori aggiungono però 2×300 = 600 secondi: il totale diventa 1140 secondi, diciannove minuti. Restano cinque minuti di vantaggio ipotetico. Se ogni riparazione richiedesse oltre 450 secondi il vantaggio sparirebbe; a 450 i percorsi pareggiano. Sono conti sulle ipotesi, non promesse di risparmio. Mancano software, avviamento, conservazione, danni dell' errore e controllo successivo: una decisione economica completa deve aggiungerli senza contare due volte la stessa attività.

A sinistra osservare insieme accettazioni ed errori, non soltanto la barra più alta. A destra confrontare lavoro prima delle riparazioni, lavoro che le include e riferimento manuale. La soglia 1,01 esclude tutti i record: nel modello produce quattordici minuti, due di avvio e dodici di revisione ipotizzata più rapida della registrazione manuale. Questo vantaggio apparente non dimostra merito dell' AI: nasce dall' ipotesi di un minuto per verificare dati già proposti. Quella differenza va misurata prima di giustificare un investimento.
Che cosa misura la ricerca, e che cosa misurare in ufficio
DocILE, di Šimsa e colleghi, arXiv v2 del 3 maggio 2023, separa localizzazione e raggruppamento delle righe. Le metriche principali non equivalgono alla correttezza della registrazione. Include formati mai visti nell' addestramento. È un riferimento metodologico storico, non una classifica attuale. Non ne abbiamo riprodotto modelli o risultati.
Il proprio processo richiede una prova distinta. Bloccare prima documenti autorizzati, definire un riferimento verificabile e separare sviluppo e valutazione per fornitore, formato e periodo evita di provare soltanto sugli stessi moduli usati per scegliere le regole. Registrare esattezza dei campi, corrispondenza delle righe, documenti interamente corretti, copertura ed errori accettati risponde a domande diverse. Dieci campi corretti su undici possono rendere il documento inutilizzabile se l' undicesimo identifica il destinatario del pagamento. Non dedurre la correttezza dell' intero documento moltiplicando accuratezze dei campi senza giustificare l' indipendenza degli errori.
I tempi richiedono la stessa attenzione. Confrontare compiti equivalenti e registrare avvio, lettura della fonte, correzione, registrazione e rilavorazioni. Se l' operatore verifica solo i campi evidenziati, gli errori non evidenziati possono non entrare nel conteggio: occorre un controllo indipendente di una parte degli accettati. L' esperimento presume una verità nota; un progetto reale deve costruirla e finanziarla. Non abbiamo eseguito test con persone e non presentiamo i tempi ipotetici come osservazioni di utenti. Anche la revisione manuale può sbagliare: un confronto che la assume perfetta va dichiarato come tale, non usato per nascondere errori.
Dal validatore al gestionale: un percorso controllabile
La conseguenza progettuale è separare proposta e registrazione. Conservare originale, campi proposti, coordinate delle fonti, versione dell' estrattore, normalizzazioni ed esiti dei vincoli permette di ricostruire la decisione. La revisione deve correggere il dato senza riscrivere l' originale e mostrare perché un controllo fallisce. Preparando l' importazione, conservare identificatore di operazione e stato dell' esito: un timeout non dimostra che il gestionale non abbia già registrato. Qui non implementiamo l' integrazione né registriamo dati reali; individuiamo il confine che il test deve coprire.
Limitare inizialmente il perimetro a una famiglia di documenti e usare una coda di eccezioni esplicita rende il processo verificabile. «Serve revisione» non basta: mostrare quali righe non tornano, quale campo manca e quale pagina controllare. Un documento fuori schema non deve diventare un insieme di zeri per superare i controlli. L' approvazione resta una decisione tracciabile, non una certificazione automatica del modello. Questo non dimostra che EL-AI abbia già un prodotto di contabilizzazione: analizziamo una possibile architettura applicativa, senza annunciare servizi realizzati.
Leggere il codice senza perdere la domanda
make crea i record e conserva il confronto con il riferimento. coherent controlla interi, valuta e uguaglianze, non identità o normativa. policy applica la soglia, conta accettazioni ed errori con verità nota, poi somma tempi ipotetici. Le asserzioni verificano errori intercettati ed errori coerenti sfuggiti; fissano 5/7/2 e 540/1140 secondi alla soglia 0,95. Il controllo delle somme cresce linearmente col numero di righe. Questo non rappresenta la complessità o latenza di un modello visivo, non eseguito. Per riprodurre, eseguire experiment.py e poi plot.py: risultati e versioni sono nell' archivio.
Che cosa possiamo decidere, e che cosa resta da dimostrare
Nel perimetro dichiarato, i controlli aritmetici rinviano record incoerenti ma non autorizzano da soli l' importazione. Il controesempio da 264 euro lo dimostra senza dipendere da un modello. Inoltre il costo delle correzioni può cambiare la decisione: nove minuti iniziali diventano diciannove nelle nostre ipotesi; oltre sette minuti e mezzo di riparazione per errore cancellano il vantaggio rispetto al manuale. Da dodici casi costruiti non possiamo stimare quante fatture reali siano automatizzabili, scegliere una soglia universale o promettere un ritorno economico.
La scelta utile parte dal formato disponibile, separa estrazione e autorizzazione, misura la correttezza degli accettati e conta tutto il lavoro per renderli utilizzabili. Se un importatore strutturato soddisfa il compito, l' AI può non servire. Se occorre interpretare pagine variabili, il modello deve entrare in un processo verificabile e non sostituire le verifiche con un punteggio rassicurante. Il prossimo esperimento necessario è una valutazione indipendente sui documenti e le eccezioni reali dell' ufficio, con confronto manuale misurato e criteri di arresto espliciti, non una dimostrazione più brillante.
Fonti e materiali riproducibili
Peppol BIS Billing 3.0 — BR-CO-10, May 2026 release.
Peppol BIS Billing 3.0 — BR-CO-15, May 2026 release.
Šimsa et al. — DocILE, arXiv:2302.05658v2, 3 May 2023; sections 3–5.
Python — decimal; documentation and runtime versions may differ.
import json,sys,copy
from pathlib import Path
BASE=dict(lines=[12000,8000],net=20000,tax=4000,total=24000,supplier="SUP-A",currency="EUR")
def make(name,score,change=None):
x=copy.deepcopy(BASE)
if change:x.update(change)
return dict(id=name,score=score,data=x,correct=x==BASE)
rows=[make("C1",.99),make("C2",.97),make("C3",.94),make("C4",.85),make("C5",.98),make("C6",.92),
make("E1",.99,dict(total=25000)),make("E2",.98,dict(net=21000)),make("E3",.96,dict(tax=4100)),
make("E4",.99,dict(lines=[13200,8800],net=22000,tax=4400,total=26400)),
make("E5",.97,dict(supplier="SUP-B")),make("E6",.90,dict(supplier="SUP-C"))]
def coherent(x):
return (all(type(v) is int for v in x['lines']+[x['net'],x['tax'],x['total']])
and sum(x['lines'])==x['net'] and x['net']+x['tax']==x['total'] and x['currency']=="EUR")
def policy(threshold):
auto=[r for r in rows if coherent(r['data']) and r['score']>=threshold]
errors=sum(not r['correct'] for r in auto);n=len(rows);accepted=len(auto)
return dict(threshold=threshold,accepted=accepted,review=n-accepted,errors=errors,
coverage=accepted/n,conditionalError=errors/accepted if accepted else None,
activeSeconds=n*10+(n-accepted)*60,withRepairSeconds=n*10+(n-accepted)*60+errors*300)
for r in rows:r['coherent']=coherent(r['data'])
assert sum(r['correct'] for r in rows)==6
assert sum(not r['coherent'] for r in rows)==3
assert coherent(rows[9]['data']) and not rows[9]['correct']
assert coherent(rows[10]['data']) and not rows[10]['correct']
policies=[policy(t) for t in [0,.90,.95,.98,1.01]]
r=policy(.95)
assert (r['accepted'],r['review'],r['errors'],r['activeSeconds'],r['withRepairSeconds'])==(5,7,2,540,1140)
result=dict(python=sys.version.split()[0],seed=None,scope='Deterministic synthetic extracted records; no OCR, model, invoices, people or accounting system tested',currency='EUR',amountUnit='integer cents',baselineSeconds=12*120,assumptions=dict(intakeSeconds=10,reviewSeconds=60,repairSeconds=300,manualSeconds=120),records=rows,policies=policies)
Path(__file__).with_name('results.json').write_text(json.dumps(result,indent=2),encoding='utf8')
print(json.dumps(r))
Codice, dati e istruzioni · JSON. Calcoli didattici eseguiti con Python 3.14.0; figure con Matplotlib 3.11.2. Analisi con assistenza AI, senza dichiarare peer review o revisione umana. Copertina originale ImageGen, illustrativa: non documenta persone, sedi o installazioni EL-AI. Fonti consultate il 26 ottobre 2026.

