Perché DataUnchain Soluzioni Costi Tecnologia Blog GitHub ↗
Italiano English
Blog · 15 Marzo 2026

Architettura di un Sistema di Elaborazione Documenti con AI: Guida Tecnica

Come è fatto internamente un sistema di elaborazione documenti con AI? Questa guida tecnica spiega l'architettura layer per layer, con diagrammi, schema del database, e le decisioni di design che fanno la differenza in produzione.

Vista d'insieme: i servizi del sistema

┌──────────────────────────────────────────────────────────────┐
│                    DOCKER COMPOSE STACK                      │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────────────┐ │
│  │  telegram-   │  │    email-    │  │   API REST         │ │
│  │  bot         │  │    monitor   │  │   FastAPI :8000    │ │
│  └──────┬───────┘  └──────┬───────┘  └─────────┬──────────┘ │
│         │                 │                     │            │
│         └────────────────┬┘                     │            │
│                          │                      │            │
│              ┌───────────▼──────────────────────▼──────────┐ │
│              │          PROCESSOR                          │ │
│              │   Classificazione + Estrazione AI           │ │
│              │   Validazione + Confidence Score            │ │
│              └───────────────────────┬──────────────────────┘ │
│                                      │                      │
│              ┌───────────────────────▼──────────────────────┐ │
│              │          OUTPUT ROUTER                      │ │
│              │   Dead-letter queue + Retry logic           │ │
│              │   18 adapter: Webhook/CRM/ERP/Gestionale    │ │
│              └──────────────────────────────────────────────┘ │
│                                                              │
│  ┌──────────────────┐  ┌──────────────────────────────────┐ │
│  │  Ollama          │  │  Dashboard Streamlit :8501        │ │
│  │  LLM server      │  │  Monitoring + Impostazioni       │ │
│  │  GPU accelerated │  │  Revisione documenti             │ │
│  └──────────────────┘  └──────────────────────────────────┘ │
│                                                              │
│  Volume persistente: /data/                                  │
│  SQLite DB + archivio PDF + results JSON + config           │
└──────────────────────────────────────────────────────────────┘

Schema del database

Il database SQLite contiene lo storico di ogni documento elaborato. Le tabelle principali:

CREATE TABLE documents (
  id            INTEGER PRIMARY KEY AUTOINCREMENT,
  file_name     TEXT NOT NULL,
  file_hash     TEXT UNIQUE,           -- SHA256 per deduplicazione
  file_path     TEXT,                  -- Percorso nell'archivio
  source        TEXT,                  -- 'telegram' | 'email' | 'api'
  received_at   DATETIME DEFAULT CURRENT_TIMESTAMP,
  processed_at  DATETIME,
  status        TEXT DEFAULT 'pending', -- 'pending' | 'processing' | 'done' | 'error'

  -- Risultati estrazione
  tipo_documento TEXT,                 -- 'fattura' | 'ddt' | 'ordine' | ...
  result_path    TEXT,                 -- Percorso al JSON con tutti i campi estratti
  confidence     INTEGER,              -- 0-100
  audit_status   TEXT,                 -- 'VALIDATED' | 'NEEDS_REVIEW' | 'ERROR'

  -- Math check
  math_ok        BOOLEAN,
  math_delta     REAL,

  -- Revisione umana
  reviewed_by    TEXT,
  reviewed_at    DATETIME,
  review_notes   TEXT,

  -- Dispatch adapter
  dispatched_adapters  TEXT,           -- JSON array: ['webhook', 'zucchetti']
  dispatch_errors      TEXT            -- JSON: {'sap_b1': 'Connection timeout'}
);

Il Processor: cuore del sistema

Il processor è il servizio che trasforma il documento grezzo in dati strutturati. Le sue responsabilità:

Step 1: Preprocessing del documento

Il PDF viene convertito in immagini PNG con pdf2image (DPI configurabile, default 200). Per ogni pagina, il sistema applica: deskewing automatico, normalizzazione del contrasto, riduzione del rumore. Le immagini vengono codificate in base64 per l'invio al modello VLM.

Step 2: Classificazione tipo documento

Il VLM riceve la prima pagina e un prompt di classificazione. Risponde con il tipo documento: 'fattura', 'ddt', 'ordine_acquisto', 'nota_credito', 'contratto', 'altro'. Questa classificazione determina lo schema di estrazione da usare nel passo successivo.

Step 3: Estrazione strutturata

Il VLM riceve tutte le pagine del documento e un prompt specifico per il tipo documento. Il prompt descrive esattamente i campi da estrarre e il formato JSON richiesto. Il modello risponde con il JSON strutturato.

Step 4: Validazione

Math check: verifica che imponibile + IVA = totale con tolleranza configurabile. Validazione formato P.IVA (11 cifre + checksum), CF, IBAN, date (ISO 8601). Calcolo confidence score (0-100) basato su completezza campi e risultato validazioni.

Step 5: Audit status

VALIDATED: confidence ≥ 85 e math check OK → pronto per dispatch automatico. NEEDS_REVIEW: confidence 60-84 o math check fallito → entra in coda revisione umana. ERROR: confidence < 60 o campi obbligatori mancanti → richiede intervento manuale.

Il Router e la dead-letter queue

Il router riceve il risultato validato dal processor e lo invia agli adapter configurati. Il pattern architetturale fondamentale è la dead-letter queue:

┌─────────────────────────────────────────────────┐
│  ROUTER: _safe_dispatch()                        │
│                                                  │
│  Per ogni adapter configurato:                   │
│    try:                                          │
│      adapter.dispatch(result)  ───→ ✅ Successo  │
│    except Exception as e:                        │
│      → salva in failed_dispatches.jsonl          │
│      → continua con il prossimo adapter          │
│                                                  │
│  Failed dispatches:                              │
│    {                                             │
│      "doc_id": 1234,                             │
│      "adapter": "sap_b1",                        │
│      "error": "Connection timeout",              │
│      "timestamp": "2026-03-15T10:30:00Z",        │
│      "payload": {...}                            │
│    }                                             │
│                                                  │
│  Retry: manuale via dashboard o API              │
│  Auto-retry: configurabile ogni N minuti         │
└─────────────────────────────────────────────────┘

Questo pattern garantisce che un adapter fallito non blocchi gli altri. Se SAP B1 è irraggiungibile, il documento viene comunque inviato a Salesforce, all'email e al webhook. Solo il dispatch SAP B1 finisce nella dead-letter queue per il retry successivo.

Decisioni di design critiche

Perché SQLite invece di PostgreSQL?

SQLite è sufficiente per volumi PMI (<50.000 documenti/anno) e azzera la complessità operativa. Nessun servizio database separato da mantenere. Il file .db è copiabile direttamente per i backup. Per volumi enterprise (>500.000 documenti/anno), la migrazione a PostgreSQL è prevista ma non necessaria per il 95% dei casi.

Perché Ollama come LLM server?

Ollama gestisce il lifecycle del modello (download, versioning, serving con GPU), espone un'API REST compatibile con OpenAI, gestisce la VRAM e il batching. Alternative valutate: llama.cpp diretto (meno funzionale), vLLM (ottimo ma più complesso per singolo modello), LocalAI (più versatile ma overhead maggiore).

Perché Docker Compose invece di Kubernetes?

Kubernetes è sovradimensionato per un'installazione single-node in una PMI. Docker Compose offre orchestrazione sufficiente, deploy in <30 minuti, familiarità diffusa, update senza downtime con restart dei singoli container. Kubernetes ha senso solo per deployment multi-nodo o cloud managed (EKS, GKE).

Perché JSON per i risultati invece di colonne DB?

Lo schema di estrazione è diverso per ogni tipo documento (fattura vs DDT vs contratto). Salvare tutto in JSON permette di aggiungere nuovi tipi documento e nuovi campi senza migrazioni dello schema del database. Solo i metadati (confidence, status, tipo) sono in colonne DB per permettere query efficienti.

Il layer di integrazione: 18 adapter

Ogni adapter implementa una sola interfaccia: riceve il payload JSON del documento e lo invia al sistema target. Gli adapter sono indipendenti: fallire in uno non blocca gli altri.

Categoria Adapter Protocollo
Output genericoWebhookHTTP POST JSON
CSV/ExcelFile append
EmailSMTP
Google SheetsSheets API v4
Fatturazione ITFatturaPA XMLGenerazione XML SDI
Fatture in CloudREST API
CRMSalesforceREST API + OAuth2
HubSpotREST API Private App
OdooXML-RPC
Gestionali ITZucchettiREST / CSV
TeamSystemDigital Hub API
MexalTracciato ASCII
EnterpriseMicrosoft 365Graph API + OAuth2
SAP Business OneService Layer REST

Monitoring e metriche chiave

Le metriche da monitorare in produzione:

  • 📊 Throughput: Documenti elaborati/ora. Alert se cala >20% rispetto alla baseline.
  • 🎯 Tasso revisione manuale: % documenti con audit_status = NEEDS_REVIEW. Obiettivo <10%. Aumenti anomali segnalano problemi di qualità dei documenti in input.
  • Dead-letter queue size: Numero di dispatch in errore in attesa di retry. Alert se supera 50.
  • ⏱️ Latenza elaborazione: Tempo medio dal ricevimento al completamento. Aumenti indicano problemi GPU o Ollama.
  • 📈 Confidence score medio: Tendenza nel tempo. Calo progressivo può indicare drift nei tipi di documento.

Questa è l'architettura che alimenta DataUnchain. Open source, deployabile in 30 minuti.

Esplora DataUnchain →