Vai al contenuto

06 — Response di fallimento T4MED (OperationOutcome)

Flussi coperti: A, B, C, D, E, F, G, H

Flusso Operazione
A Inserimento nuova prenotazione — POST {base-url} (Bundle transaction)
B Aggiornamento prenotazione — PUT /Appointment/{id}
C Cancellazione prenotazione — DELETE /Appointment/{id}
D Download PDF dati monitoraggio — POST /Patient/{cfisc}/$generate-pdf
E Upload nuovo referto firmato (v1) — POST {base-url} (Bundle transaction)
F Upload referto sostitutivo (v>1) — POST {base-url} (Bundle transaction)
G Annullamento referto — PUT /DocumentReference/{id}
H Aggiornamento dei soli metadati — PUT /DocumentReference/{id}

Per ognuno dei flussi sopra, le specifiche T4MED indicano solo la response "corretta" (200 OK/201 Created con il body di successo). Questo documento definisce il formato della response di FALLIMENTO, valido per tutti e 8 i flussi. Il Flusso D prevede inoltre un caso speciale non di errore (204 No Content — nessun dato clinico disponibile), descritto nella sezione 2.4.


1 — Formato unico di risposta di fallimento: OperationOutcome (FHIR R4)

Lo standard FHIR R4 prevede un solo formato per le risposte di errore, indipendentemente dal tipo di operazione (CRUD su singola risorsa, Bundle transaction, o $operation custom): la risorsa OperationOutcome.

  • Restituita come body JSON con Content-Type: application/fhir+json
  • Accompagnata da uno status HTTP nel range 4xx (errore client) o 5xx (errore server)
  • Struttura sempre identica, qualunque sia il flusso (A–H)

Questo significa che gt4medServices può implementare un solo parser per le risposte di errore di T4MED, da riusare su tutte le chiamate (Appointment, $generate-pdf, Bundle transaction).

1.1 — Struttura generale

HTTP/1.1 <4xx | 5xx>
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "fatal | error | warning | information",
      "code": "<IssueType FHIR R4>",
      "details": {
        "coding": [
          {
            "system": "<eventuale codice errore proprietario T4MED>",
            "code": "<codice-errore>",
            "display": "<descrizione breve>"
          }
        ],
        "text": "<messaggio leggibile per log/diagnostica>"
      },
      "diagnostics": "<dettaglio tecnico, es. id transazione>",
      "expression": [ "<path FHIRPath dell'elemento che ha causato l'errore>" ]
    }
  ]
}

Campi:

Campo Significato
severity gravità (error/fatal bloccano l'operazione; warning/information possono accompagnare anche risposte 200 OK, ma per i flussi A-H si considerano solo errori bloccanti)
code valore dal valueset FHIR R4 IssueType (vedi tabella 1.2)
details codice/testo descrittivo — eventuale codice errore specifico T4MED da mappare
diagnostics testo libero, utile per il logging interno di gt4medServices
expression path dell'elemento del payload che ha causato l'errore (utile per Flussi A/E con Bundle multi-risorsa)

Nota: l'array issue può contenere più elementi (es. più errori di validazione contemporanei su un Bundle). gt4medServices deve iterare su tutti gli elementi, non solo sul primo.

1.2 — Codici HTTP e IssueType più rilevanti per gt4medServices

HTTP Status IssueType code Significato tipico
400 Bad Request invalid, structure Bundle/risorsa non valida, JSON malformato / schema errato
400 Bad Request required campo obbligatorio mancante
400 Bad Request value valore di un campo non valido
400 Bad Request code-invalid codice da terminologia non valido (es. ASL/STS11/prestazione)
401 Unauthorized login header X-API-Key mancante o non valido
403 Forbidden forbidden X-API-Key valida ma IP non autorizzato, o scope insufficiente
404 Not Found not-found Appointment/Patient/DocumentReference/{id} inesistente
405 Method Not Allowed not-supported Operazione/verbo HTTP non supportato su questo endpoint
409 Conflict conflict Conflitto di stato (es. doppia prenotazione, Appointment già cancellato/concluso)
409 Conflict duplicate Risorsa duplicata (es. identifier già esistente)
422 Unprocessable Entity business-rule Regola di business violata (es. data fine < data inizio, paziente non trovato per CF)
429 Too Many Requests throttled Rate limit superato
500/502/503/504 exception, timeout, transient Errore interno T4MED / timeout / servizio non disponibile / errore transitorio (riprovabile)

Nota: l'elenco esatto dei codici HTTP/IssueType effettivamente restituiti da T4MED non è stato dettagliato in fase di specifica. La tabella sopra rappresenta il mapping standard FHIR R4 e copre gli scenari più probabili per ciascun flusso.

1.3 — Bundle transaction (Flussi A, E ed F): tutto-o-niente

Per i Flussi A, E ed F (POST {base-url} con Bundle transaction), lo standard FHIR R4 prevede che, in caso di fallimento di una qualsiasi entry del Bundle, il server:

  • annulli l'intera transazione (atomicità — nessuna risorsa viene creata, incluso un eventuale Patient censito nella stessa transazione)
  • non restituisca un Bundle transaction-response parziale
  • restituisca un singolo OperationOutcome con status HTTP 4xx/5xx

Questo è coerente con il formato unico definito al punto 1.1: anche per gli endpoint Bundle, l'errore non è "un Bundle con entry in errore" ma una risposta OperationOutcome a sé stante.


2 — Esempi per flusso

Per ogni flusso viene fornito un esempio di risposta di fallimento realistico, basato sui dati del caso già usati negli altri documenti (Paziente ROSAVIOLA DALMINA, CF RSVDMN11A41H620X, T4MED Appt.ID T00450).

2.1 — Flusso A — Inserimento nuova prenotazione (POST {base-url}, Bundle transaction)

Esempio 1 — campo obbligatorio mancante (end assente in Appointment)

HTTP/1.1 400 Bad Request
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "required",
      "details": {
        "text": "Appointment.end e' un campo obbligatorio e non e' presente"
      },
      "diagnostics": "Bundle entry[2] (Appointment) - validazione fallita",
      "expression": [ "Bundle.entry[2].resource.end" ]
    }
  ]
}

AZIONE gt4medServices:

  • Non salvare alcuna mappatura CUP↔T4MED (transazione annullata — vedi 1.3).
  • Loggare l'errore con il riferimento alla prenotazione CUP (26B001956).
  • Applicare la strategia di retry (max 3 tentativi, 5s di distanza, timeout 60s — vedi 05-vincoli-e-eccezioni.md) solo se il codice è transient/timeout/5xx; un 400 required è un errore permanente: non va ritentato, va segnalato per intervento manuale (vedi Eccezione A2).

Esempio 2 — identificativo prenotazione duplicato

HTTP/1.1 409 Conflict
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "duplicate",
      "details": {
        "text": "Esiste gia' un Appointment con identifier urn:local:cup:id-prenotazione|26B001956"
      },
      "diagnostics": "Bundle entry[2] (Appointment) - identifier duplicato",
      "expression": [ "Bundle.entry[2].resource.identifier" ]
    }
  ]
}

2.2 — Flusso B — Aggiornamento prenotazione (PUT /Appointment/{id})

Esempio — Appointment non trovato (T00450 non esiste su T4MED)

HTTP/1.1 404 Not Found
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "not-found",
      "details": {
        "text": "Nessuna risorsa Appointment trovata con id 'T00450'"
      },
      "diagnostics": "PUT /Appointment/T00450"
    }
  ]
}

AZIONE gt4medServices:

  • La mappatura interna (26B001956 → T00450) punta a una risorsa che non esiste più su T4MED (possibile disallineamento, vedi Eccezione B1).
  • Non ritentare con lo stesso ID: segnalare il disallineamento per verifica (eventualmente proporre un re-inserimento con il Flusso A).

2.3 — Flusso C — Cancellazione prenotazione (DELETE /Appointment/{id})

Esempio — Appointment già concluso, non cancellabile

HTTP/1.1 409 Conflict
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "conflict",
      "details": {
        "text": "L'Appointment 'T00450' e' nello stato 'fulfilled' e non puo' essere cancellato"
      },
      "diagnostics": "DELETE /Appointment/T00450"
    }
  ]
}

AZIONE gt4medServices:

  • Su 409 conflict: non ritentare, segnalare (vedi Eccezione C1).

2.4 — Flusso D — Download PDF dati monitoraggio (POST /Patient/{cfisc}/$generate-pdf)

Nota: anche se la risposta di SUCCESSO è uno stream binario (Content-Type: application/pdf), la risposta di FALLIMENTO resta comunque un OperationOutcome in application/fhir+json — comportamento standard per le $operation FHIR R4: l'output dichiarato (PDF) viene restituito solo in caso di esito positivo.

A seguito dei test di integrazione, l'operazione $generate-pdf richiede obbligatoriamente anche il parametro T4MedAppointmentId (oltre a DateFrom/DateTo), usato per limitare il telemonitoraggio restituito alla specifica prenotazione. Vedi samples/flusso-d-download-referto.md.

Esempio 1 — Patient non trovato

HTTP/1.1 404 Not Found
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "not-found",
      "details": {
        "text": "Nessuna risorsa Patient trovata con id 'RSVDMN11A41H620X'"
      },
      "diagnostics": "POST /Patient/RSVDMN11A41H620X/$generate-pdf"
    }
  ]
}

Esempio 2 — intervallo date non valido (DateFrom successivo a DateTo)

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "business-rule",
      "details": {
        "text": "Il parametro 'DateFrom' e' successivo al parametro 'DateTo'"
      },
      "diagnostics": "POST /Patient/RSVDMN11A41H620X/$generate-pdf",
      "expression": [ "Parameters.parameter.where(name='DateFrom').value" ]
    }
  ]
}

Esempio 3 — Appointment inesistente (T4MedAppointmentId non trovato)

HTTP/1.1 404 Not Found
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "not-found",
      "details": {
        "text": "Nessun Appointment trovato con id '9999'"
      },
      "diagnostics": "POST /Patient/RSVDMN11A41H620X/$generate-pdf",
      "expression": [ "Parameters.parameter.where(name='T4MedAppointmentId').value" ]
    }
  ]
}

Esempio 4 — Appointment appartenente ad altro paziente

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "business-rule",
      "details": {
        "text": "L'Appointment con id '2436' non e' associato al Patient 'RSVDMN11A41H620X'"
      },
      "diagnostics": "POST /Patient/RSVDMN11A41H620X/$generate-pdf",
      "expression": [ "Parameters.parameter.where(name='T4MedAppointmentId').value" ]
    }
  ]
}

AZIONE gt4medServices:

  • Su errore in questa fase, il merge PDF (MEDWARE + T4MED) non può essere completato: gt4medServices restituisce a gefidServices il solo referto MEDWARE, senza l'allegato dei dati di monitoraggio T4MED. gefidServices procede comunque con la firma digitale del referto — lo stesso comportamento del caso 204 No Content descritto di seguito. Vedi la nota sui timeout nei flussi interattivi in 05-vincoli-e-eccezioni.md.

Caso speciale — nessun dato clinico disponibile: se paziente e parametri sono corretti ma nel periodo richiesto non esistono schede di telemonitoraggio, T4MED risponde 204 No Content (senza body). Non è un errore: non si effettua il merge PDF, viene pubblicato esclusivamente il referto MEDWARE, e il flusso di firma prosegue regolarmente. Vedi samples/flusso-d-download-referto.md.


2.5 — Flusso E — Upload nuovo referto firmato, v1 (POST {base-url}, Bundle transaction)

Esempio 1 — DocumentReference.identifier (UniqueDocumentId) assente

HTTP/1.1 400 Bad Request
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "required",
      "details": {
        "text": "DocumentReference.identifier (UniqueDocumentId del referto) e' obbligatorio"
      },
      "diagnostics": "Bundle entry[1] (DocumentReference) - validazione fallita",
      "expression": [ "Bundle.entry[1].resource.identifier" ]
    }
  ]
}

Esempio 2 — paziente non trovato per il codice fiscale indicato in subject

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "business-rule",
      "details": {
        "text": "Nessun Patient trovato con id 'RSVDMN11A41H620X'"
      },
      "diagnostics": "Bundle entry[1] (DocumentReference) - subject non risolvibile",
      "expression": [ "Bundle.entry[1].resource.subject" ]
    }
  ]
}

AZIONE gt4medServices:

  • Transazione annullata: né BinaryDocumentReference vengono creati su T4MED.
  • Il referto firmato risulterà pubblicato su REPOSITORY/FSE ma non su T4MED (vedi Eccezione E2): da segnalare per allineamento manuale.

2.6 — Flusso F — Upload referto sostitutivo, v>1 (POST {base-url}, Bundle transaction)

Gli scenari di errore sono gli stessi del Flusso E (campo obbligatorio mancante, paziente non trovato), con l'aggiunta del seguente scenario specifico, dovuto alla presenza di relatesTo.target:

Documento originale (relatesTo.target) non trovato

HTTP/1.1 404 Not Found
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "not-found",
      "details": {
        "text": "Nessuna risorsa DocumentReference trovata con id '<id-t4med-del-documento-originale>'"
      },
      "diagnostics": "Bundle entry[1] (DocumentReference) - relatesTo.target non risolvibile",
      "expression": [ "Bundle.entry[1].resource.relatesTo[0].target" ]
    }
  ]
}

AZIONE gt4medServices: come per il Flusso E — transazione annullata (atomicità, vedi 1.3); segnalare per allineamento manuale (Eccezione E2).


2.7 — Flusso G — Annullamento referto (PUT /DocumentReference/{id})

Esempio 1 — DocumentReference originale non trovato

HTTP/1.1 404 Not Found
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "not-found",
      "details": {
        "text": "Nessuna risorsa DocumentReference trovata con id '<id-t4med-del-documento-originale>'"
      },
      "diagnostics": "PUT /DocumentReference/<id-t4med-del-documento-originale>"
    }
  ]
}

Esempio 2 — documento già annullato (status già entered-in-error)

HTTP/1.1 409 Conflict
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "conflict",
      "details": {
        "text": "Il DocumentReference '<id-t4med-del-documento-originale>' e' gia' nello stato 'entered-in-error'"
      },
      "diagnostics": "PUT /DocumentReference/<id-t4med-del-documento-originale>"
    }
  ]
}

AZIONE gt4medServices: su 404/409 non ritentare (PUT non è una Bundle transaction, ma l'esito è comunque definitivo); segnalare il disallineamento per verifica (Eccezione E2) — il referto risulterà annullato su REPO/FSE ma non correttamente riflesso su T4MED.


2.8 — Flusso H — Aggiornamento dei soli metadati (PUT /DocumentReference/{id})

Stesso endpoint del Flusso G; gli scenari di errore sono analoghi.

Esempio 1 — DocumentReference originale non trovato

HTTP/1.1 404 Not Found
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "not-found",
      "details": {
        "text": "Nessuna risorsa DocumentReference trovata con id '<id-DocumentReference>'"
      },
      "diagnostics": "PUT /DocumentReference/<id-DocumentReference>"
    }
  ]
}

Esempio 2 — documento già annullato (status già entered-in-error), non aggiornabile

HTTP/1.1 409 Conflict
Content-Type: application/fhir+json
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "conflict",
      "details": {
        "text": "Il DocumentReference '<id-DocumentReference>' e' nello stato 'entered-in-error' e non puo' essere aggiornato"
      },
      "diagnostics": "PUT /DocumentReference/<id-DocumentReference>"
    }
  ]
}

AZIONE gt4medServices: su 404/409 non ritentare; segnalare il disallineamento per verifica (Eccezione E2) — il metadato risulterà aggiornato lato SINED ma non correttamente riflesso su T4MED.


Flusso Operazione Errori più probabili (HTTP)
A POST {base-url} (Bundle) 400, 409, 422
B PUT /Appointment/{id} 400, 404, 409
C DELETE /Appointment/{id} 404, 409
D POST /Patient/{cfisc}/$generate-pdf 404, 422 (204 No Content = caso speciale, non errore)
E POST {base-url} (Bundle) 400, 422, 409
F POST {base-url} (Bundle) 400, 404, 422, 409
G PUT /DocumentReference/{id} 404, 409
H PUT /DocumentReference/{id} 404, 409

In tutti i casi: stesso resourceType (OperationOutcome), stesso Content-Type (application/fhir+json), stesso array issue[]. gt4medServices può quindi implementare un unico componente di parsing delle risposte di errore, condiviso da tutti e 8 i flussi, e instradare il comportamento (retry, fallback, segnalazione) in base alla coppia (HTTP status, issue[].code).

Punti non ancora confermati nel dettaglio: l'eventuale presenza di codici di errore applicativi T4MED in issue[].details.coding (vedi placeholder in 1.1), e i codici HTTP esatti restituiti per ciascuno scenario di errore della tabella 1.2. La struttura OperationOutcome (FHIR R4) è invece confermata come formato di riferimento per tutte le risposte di fallimento.


4 — Significato degli HTTP status code

Riferimento generale HTTP/REST, applicabile a tutti i flussi (A-H).

4.1 — 400 Bad Request

La richiesta ricevuta dal server non è formalmente valida.

Cause tipiche: JSON non valido; Bundle FHIR malformato; campi obbligatori mancanti; formato di data non corretto; identificativi non valorizzati; errori di sintassi nella richiesta.

4.2 — 401 Unauthorized

L'header X-API-Key è mancante o non valido.

4.3 — 403 Forbidden

L'X-API-Key è valida ma la richiesta proviene da un IP non autorizzato, oppure lo scope associato alla chiave non consente l'operazione richiesta.

4.4 — 404 Not Found

La risorsa richiesta non esiste oppure non è stata trovata.

Cause tipiche: Appointment inesistente; Patient inesistente; DocumentReference inesistente; identificativo errato; risorsa già cancellata.

Esempio: PUT /Appointment/12345 quando l'appuntamento 12345 non è presente nel sistema.

4.5 — 405 Method Not Allowed

L'endpoint esiste ma il metodo HTTP utilizzato non è consentito su quell'endpoint.

4.6 — 409 Conflict

La richiesta è formalmente corretta ma entra in conflitto con lo stato corrente delle informazioni presenti nel sistema.

Cause tipiche: appuntamento già esistente; prenotazione già cancellata; modifica concorrente da parte di un altro operatore; versione della risorsa non più aggiornata; referto già acquisito; duplicazione di identificativi univoci.

Esempio: creazione di una prenotazione già presente nel sistema.

4.7 — 422 Unprocessable Entity

La richiesta è formalmente valida ma non supera i controlli funzionali o le regole di business.

Cause tipiche: dati clinici incoerenti; paziente non eleggibile; appuntamento non modificabile; documento non conforme; referto privo dei requisiti previsti; regole applicative non soddisfatte.

Esempio: richiesta di prenotazione valida dal punto di vista sintattico ma riferita ad una prestazione non consentita.

4.8 — 500 Internal Server Error

Errore interno del sistema (eccezione non gestita, errore applicativo, errore di configurazione, database non disponibile, servizio esterno non raggiungibile). Il client generalmente non può correggere autonomamente il problema.

4.9 — 503 Service Unavailable

Servizio temporaneamente non disponibile (manutenzione, sovraccarico, dipendenza esterna non raggiungibile, timeout infrastrutturale). Normalmente il client può riprovare l'operazione successivamente.