Skip to content

Schemas — DocumentEnvelope v2

Il contratto di output di Factum Parse prevede due livelli ben distinti: il wrapper HTTP (ParseResponse / DocumentResponse) e l’envelope interno DocumentEnvelope v2 (il contenuto semanticamente validato).

Source of truth: backend/app/schemas/envelope.py.

DocumentEnvelope v2 — il contratto del contenuto

Section titled “DocumentEnvelope v2 — il contratto del contenuto”

È l’unico formato wire del risultato di parsing, sia per upload che per parse diretto. I vecchi record v1 vengono convertiti on-read (vedi backend/app/services/envelope.py): niente dualità wire.

{
"schema_version": "2.0",
"document_type": "invoice",
"meta": {
"cost_eur": 0.0,
"confidence": 1.0,
"provider": "fast-path",
"prompt_version": "fast-path-v1"
},
"payload": {
"kind": "invoice",
"dati_trasmissione": {},
"cedente_prestatore": {},
"cessionario_committente": {},
"corpi": []
}
}
Field Pydantic type Default Contract
schema_version Literal["2.0"] "2.0" Fixed wire version
document_type Literal["invoice", "generic"] Semantic document type
meta EnvelopeMeta empty model Parser metadata
payload InvoicePayload | GenericPayload Discriminated by kind
{ "cost_eur": 0.00037, "confidence": 0.97, "provider": "deepseek-v3", "prompt_version": "fattura-v1-a1b2c3d4" }
Field Type Default Note
cost_eur float 0.0 Costo in EUR dell’elaborazione LLM
confidence float 1.0 Confidence score 0.0–1.0 (1.0 = deterministico)
provider string "" Provider LLM, vuoto per deterministico
prompt_version string "" Versione del prompt template
Field Type Default Description
kind Literal["invoice"] "invoice" Discriminant
dati_trasmissione dict[str, Any] {} FatturaPA transmission data
cedente_prestatore dict[str, Any] {} Supplier data
cessionario_committente dict[str, Any] {} Customer data
corpi list[dict[str, Any]] [] Invoice line/body data

I campi sono dict grezzi come trasportati dal parser: il payload NON normalizza (fedeltà al dominio). extra="allow" garantisce compatibilità forward del JSONB.

Field Type Default
kind Literal["generic"] "generic"
content dict[str, Any] {}

Il payload è una Legal Union Pydantic v2 discriminata su kind:

DocumentPayload = Annotated[
InvoicePayload | GenericPayload,
Field(discriminator="kind"),
]

Il backend enforce anche:

document_type == payload.kind

Un mismatch viene rifiutato dal model validator. Questo è un invariant contrattuale, non una convenzione client-side.

Entrambi i payload models usano extra="allow". Nuovi campi parser possono essere trasportati senza rompere i consumer che leggono solo campi stabili. Aggiungere un nuovo document_type richiede un nuovo payload model e un corrispondente branch nella union.

I vecchi record v1 vengono upgradati on-read dal backend. Il wire contract corrente è v2 only.

HTTP response
└── ParseResponse
├── job_id / status
├── provider_used / prompt_version
├── confidence / cost_usd / tokens
├── content_hash
└── result
└── DocumentEnvelope v2
├── schema_version
├── document_type
├── meta
└── payload
Campo Tipo Note
job_id string UUID del job
status "done" | "failed" | "queued" Stato elaborazione
document_type string Tipo documento rilevato
provider_used string Provider LLM usato
prompt_version string Versione del prompt
confidence float | null Confidence score
cost_usd float Costo in USD
tokens integer Token consumati
fallbacks string[] Fallback attivati
content_hash string SHA-256 canonico
result DocumentEnvelope | null Contenuto strutturato v2
error string | null Messaggio errore
Campo Tipo Note
job_id string UUID del job
doc_type string Tipo documento rilevato
content_hash string SHA-256 canonico
size_bytes integer Dimensione in byte
mime_type string MIME type rilevato
deduplicated boolean Cache hit
status "done" | "requires_vision" | "queued" | "failed" | "expired" Stato
message string | null Messaggio contestuale

La distinzione è netta: DocumentResponse descrive stato di ingestione, ParseResponse trasporta il DocumentEnvelope v2 strutturato.