# Come importare i risultati di laboratorio in un record FHIR

> Tutorial: importa un referto di laboratorio come CSV e come HL7 v2 ORU^R01 in FHIR R4 Observations con codici LOINC e intervalli di riferimento, gestisci marcatori non mappati e referti inviati nuovamente, e traccia ogni risultato al suo laboratorio.

Source: https://developers.anpheros.com/it/guides/import-lab-results-fhir

**Questo tutorial prende un referto di laboratorio come file CSV e come messaggio HL7 v2 ORU^R01, lo importa nel record HL7 FHIR R4 di un paziente con una richiesta ciascuno, e mostra cosa si ottiene: risorse di laboratorio `Observation` con codici LOINC, intervalli di riferimento e provenienza.** Copre anche i due problemi che ogni integrazione di laboratorio incontra: marcatori che non possono essere mappati automaticamente e referti che vengono inviati due volte. Tutto funziona sulla sandbox gratuita con un paziente sintetico.

## Perché i risultati di laboratorio sono difficili da archiviare

Un referto di laboratorio sembra semplice: un marcatore, un valore, un'unità, un intervallo di riferimento, una data. In pratica, ogni laboratorio nomina i marcatori a modo suo ("Emoglobina glicosilata", "HbA1c", "Glycated haemoglobin"), utilizza virgole o punti per i decimali, scrive gli intervalli come `4,0-5,6`, `<5` o `>3.5` e reinvia lo stesso referto quando qualcosa non funziona. Archiviati come testo libero, questi risultati non possono essere confrontati nel tempo, graficati, condivisi con un'altra applicazione o forniti con sicurezza a un modello di intelligenza artificiale.

Un record FHIR archivia ogni risultato come un `Observation` con un codice LOINC, un'unità UCUM, un valore, un intervallo di riferimento e una data, e conserva chi lo ha inviato in una risorsa `Provenance`. Il [connettore di laboratorio](https://developers.anpheros.com/it/guides/lab-connector) esegue questa conversione per te.

```
 lab report (CSV, HL7 v2, JSON)
        │  POST /v1/patients/{id}/labs/import
        ▼
 ┌──────────────────────────────────────────────┐
 │ parse rows → map marker to LOINC → parse     │
 │ value, unit, range → stable id per row       │
 └──────────────────────────────────────────────┘
        │
        ▼
 Observation (category laboratory) + Provenance (author_type import, source system, report id)
        │
        ├─► the patient's app and every app they consented to
        └─► webhooks: resource.created per Observation
```

## Prima di iniziare

È necessaria una chiave sandbox (`sk_test_…`) da [platform.anpheros.com/dashboard](https://platform.anpheros.com/dashboard/) (**Ottieni una chiave sandbox**). Gli esempi utilizzano `curl`; gli [SDK](https://developers.anpheros.com/it/guides/sdks) hanno le stesse chiamate (`labs.importCsv`, `labs.importHl7`, `labs.importItems`).

```bash
export BASE=https://platform.anpheros.com
export KEY=sk_test_…
# Elena Ionescu, a synthetic patient with type 2 diabetes
export PID=$(curl -s "$BASE/fhir/R4/Patient?identifier=https://anpheros.com/fhir/sid/sandbox-persona|p02" \
  -H "Authorization: Bearer $KEY" | jq -r '.entry[0].resource.id')
```

## Passaggio 1: importare un report CSV

Ecco come un laboratorio rumeno potrebbe esportare un report: punti e virgola, virgole decimali, nomi di colonna rumeni e date come `DD.MM.YYYY`. Il connettore riconosce i nomi di colonna rumeni e inglesi.

```bash
cat > report.csv <<'EOF'
Analiza;Rezultat;UM;Interval de referinta;Data
Hemoglobina glicozilata (HbA1c);6,9;%;4,0-5,6;01.10.2026
Glicemie;131;mg/dL;70-100;01.10.2026
TSH;2,1;mUI/L;0,4-4,0;01.10.2026
Marker foarte rar;12;U/L;0-20;01.10.2026
EOF

curl -X POST "$BASE/v1/patients/$PID/labs/import" -H "Authorization: Bearer $KEY" \
     -H "Content-Type: text/csv" -H "X-Anpheros-Source-System: labx" \
     -H "Idempotency-Key: report-2026-10-01-elena" --data-binary @report.csv
```

La risposta (`201`):

```json
{"created": 4, "skipped": 0, "unmapped": ["Marker foarte rar"], "warnings": [],
 "observations": [
  {"id": "ff0bbb44-…", "marker": "Hemoglobina glicozilata (HbA1c)", "code": "4548-4", "system": "http://loinc.org", "value": 6.9, "unit": "%", "date": "2026-10-01"},
  {"id": "ebe5522f-…", "marker": "Glicemie", "code": "2345-7", "system": "http://loinc.org", "value": 131.0, "unit": "mg/dL", "date": "2026-10-01"},
  {"id": "bace44a8-…", "marker": "TSH", "code": "3016-3", "system": "http://loinc.org", "value": 2.1, "unit": "mUI/L", "date": "2026-10-01"},
  {"id": "26fd2270-…", "marker": "Marker foarte rar", "code": "marker_foarte_rar", "system": "urn:anpheros:lab-code", "value": 12.0, "unit": "U/L", "date": "2026-10-01"}]}
```

HbA1c, glucosio e TSH sono stati mappati a LOINC dai loro nomi. Le virgole decimali sono diventate numeri e gli intervalli sono diventati `referenceRange`. Una riga è tornata in `unmapped`. È ancora memorizzata, sotto il codice del laboratorio (`urn:anpheros:lab-code`) con il testo esatto del report, quindi nulla è andato perso.

## Passaggio 2: gestione dei marcatori non mappati

Alcuni nomi richiedono una decisione. "Glicemie" è un buon esempio: LOINC ha un codice per il glucosio nel siero o nel plasma (`2345-7`, un campione venoso prelevato in laboratorio) e un altro per il glucosio nel sangue intero (`2339-0`, ad esempio un prelievo capillare). Su un referto di laboratorio, un semplice "Glicemie" viene mappato a siero o plasma. Un nome che dice "capilar", "capillary" o "sânge integral" viene mappato a sangue intero:

```json
{"created": 1, "skipped": 0, "unmapped": [], "warnings": [],
 "observations": [{"id": "00480a6d-…", "marker": "Glicemie capilara", "code": "2339-0", "system": "http://loinc.org", "value": 140.0, "unit": "mg/dL", "date": "2026-10-02"}]}
```

Per i marcatori che il connettore non riconosce, cercare il codice:

```bash
curl "$BASE/v1/terminology/loinc?q=glucose" -H "Authorization: Bearer $KEY"
```

```json
{"data": [
  {"system": "http://loinc.org", "code": "2339-0", "display": "Glucose [Mass/volume] in Blood", "display_ro": "Glicemie (sânge)", "category": "laboratory", "unit": "mg/dL"},
  {"system": "http://loinc.org", "code": "2345-7", "display": "Glucose [Mass/volume] in Serum or Plasma", "display_ro": "Glicemie (ser)", "category": "laboratory", "unit": "mg/dL"}],
 "total": 2}
```

Quindi inviare il codice in una colonna `LOINC`. Un codice nel referto ha sempre la precedenza sul nome. Mantenere quella mappatura per laboratorio dalla propria parte e aggiungere la colonna a ogni referto proveniente da esso:

```
Analiza;Rezultat;UM;Interval de referinta;Data;LOINC
Glicemie;99;mg/dL;70-100;04.10.2026;2339-0
```

I marcatori che LOINC non copre affatto, come il fittizio "Marker foarte rar", possono rimanere sotto il codice del laboratorio. Vengono mostrati al paziente e inclusi nel referto. Non possono essere confrontati con i risultati di altri laboratori.

## Passaggio 3: inviare lo stesso report due volte

I laboratori inviano nuovamente i report. Ogni riga ottiene un identificatore stabile costruito dall'ID del report, dal codice, dalla data e dal valore, quindi una seconda importazione dello stesso report non crea nulla:

```json
{"created": 0, "skipped": 4, "unmapped": ["Marker foarte rar"], "warnings": [], "observations": []}
```

Le righe che sono cambiate, ad esempio un valore corretto, vengono importate come nuovi risultati. L'intestazione `Idempotency-Key` aggiunge una seconda garanzia: un nuovo tentativo di rete con la stessa chiave restituisce la prima risposta invariata.

## Passaggio 4: importare un messaggio HL7 v2 ORU^R01

La maggior parte dei sistemi informativi di laboratorio utilizza HL7 v2. Inviare il messaggio così com'è (testo ER7, segmenti separati da CR o LF):

```
MSH|^~\&|LABX|LAB1|ANPHEROS|EHR|202610010930||ORU^R01|MSG00077|P|2.5.1
PID|1||12345||Ionescu^Elena||19850903|F
OBR|1||R-77|24331-1^Lipid panel^LN|||202610010800
OBX|1|NM|2093-3^Cholesterol [Mass/volume] in Serum or Plasma^LN||212|mg/dL|<200|H|||F|||202610010800
OBX|2|NM|2085-9^HDL Cholesterol^LN||52|mg/dL|>40|N|||F|||202610010800
```

```bash
curl -X POST "$BASE/v1/patients/$PID/labs/import" -H "Authorization: Bearer $KEY" \
     -H "Content-Type: x-application/hl7-v2+er7" --data-binary @oru.hl7
```

```json
{"created": 2, "skipped": 0, "unmapped": [], "warnings": [],
 "observations": [
  {"id": "b562045b-…", "marker": "Cholesterol [Mass/volume] in Serum or Plasma", "code": "2093-3", "system": "http://loinc.org", "value": 212.0, "unit": "mg/dL", "date": "2026-10-01T08:00:00Z"},
  {"id": "9b3274e4-…", "marker": "HDL Cholesterol", "code": "2085-9", "system": "http://loinc.org", "value": 52.0, "unit": "mg/dL", "date": "2026-10-01T08:00:00Z"}]}
```

`OBX-3` con il sistema di codifica `LN` viene utilizzato direttamente come LOINC. `OBX-7` diventa l'intervallo di riferimento e `OBX-8` (`H`, `N`) l'interpretazione. `OBR-3` (`R-77`) viene mantenuto come ID del report nella provenienza. Il paziente è quello nell'URL: il segmento `PID` non viene utilizzato per trovare un paziente, quindi un `PID` errato non può allegare risultati a qualcun altro.

Si possiedono già risultati analizzati? Inviare JSON: `{"date": "2026-10-01", "origin": "report-77", "items": [{"marker": "Ferritin", "value": 38, "unit": "ng/mL", "ref_low": 15, "ref_high": 150}]}`. La ferritina viene mappata a LOINC `2276-4`.

## Passaggio 5: leggere i risultati come FHIR

```bash
curl "$BASE/fhir/R4/Observation?patient=$PID&code=http://loinc.org|4548-4&_sort=-date&_count=1" \
     -H "Authorization: Bearer $KEY"
```

L'HbA1c importato, come memorizzato:

```json
{
  "resourceType": "Observation",
  "id": "62b1b0c0-…",
  "meta": {"versionId": "1", "lastUpdated": "2026-10-12T20:05:57.011Z"},
  "identifier": [{"system": "urn:anpheros:lab-import", "value": "c52d3cd6449291d34f6bef1516d0ac05"}],
  "status": "final",
  "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory"}]}],
  "code": {"coding": [{"system": "http://loinc.org", "code": "4548-4", "display": "Hemoglobin A1c/Hemoglobin.total in Blood"}],
           "text": "Hemoglobina glicozilata (HbA1c)"},
  "subject": {"reference": "Patient/…"},
  "effectiveDateTime": "2026-10-01",
  "valueQuantity": {"value": 6.9, "unit": "%"},
  "referenceRange": [{"low": {"value": 4.0, "unit": "%"}, "high": {"value": 5.6, "unit": "%"}}]
}
```

Il display LOINC è il nome standard in inglese. `code.text` mantiene la dicitura del laboratorio, in modo che il paziente veda ancora ciò che era sul referto cartaceo. Per ulteriori informazioni su risorse e ricerca, consultare la guida [FHIR platform](https://developers.anpheros.com/it/guides/fhir) e [FHIR code systems](https://developers.anpheros.com/it/guides/fhir-code-systems).

## Step 6: verifica chi l'ha inviato

```bash
curl "$BASE/v1/patients/$PID/provenance/Observation/62b1b0c0-…" -H "Authorization: Bearer $KEY"
```

```json
{"data": [{"version": 1, "activity": "CREATE", "recorded": "2026-10-12T20:05:57.011000Z",
           "author_type": "import", "author": "proj_…", "source_system": "labx", "origin_id": null,
           "fhir": "Provenance/f15c4fee-…"}], "total": 1}
```

`author_type: import` e `source_system: labx` rimangono con il valore. Quando un'applicazione AI leggerà successivamente questo risultato tramite la [context API](https://developers.anpheros.com/it/guides/llm-healthcare-data), sarà etichettato `[import/labx]`, in modo che il modello possa distinguere un risultato di laboratorio da un valore inserito dal paziente.

## Cosa succede dopo

- Il paziente visualizza i risultati nella propria app. Qualsiasi applicazione a cui ha acconsentito può leggerli, nei limiti dei propri scope ([modello di consenso e accesso](https://developers.anpheros.com/it/guides/consent)).
- Un [webhook](https://developers.anpheros.com/it/guides/webhooks) `resource.created` viene attivato per ogni Observation, in modo che una dashboard clinica o un riepilogo AI possa aggiornarsi senza polling.
- `POST /fhir/R4/Observation/$validate?profile=eu-lab` riporta come un risultato si confronta con il profilo del report di laboratorio HL7 Europe.
- Fino a 500 righe per richiesta; i limiti sono elencati in [Limiti e rate limits](https://developers.anpheros.com/it/guides/limits).

## Domande frequenti

### Quali formati di referti di laboratorio possono essere importati in FHIR?
CSV con una riga di intestazione (virgola, punto e virgola o tabulazione, virgola decimale accettata), messaggi HL7 v2 ORU^R01 e JSON. I referti PDF possono essere archiviati come documenti; i valori estratti da essi puntano al documento.

### Cosa succede ai marcatori senza codice LOINC?
Vengono archiviati sotto il codice del laboratorio stesso con il testo esatto dal referto e elencati in `unmapped`. Aggiungere una colonna LOINC per quel laboratorio e verranno mappati. Un codice nel referto ha sempre la precedenza sul nome.

### È sicuro reinviare un referto?
Sì. Ogni riga ha un identificatore stabile costruito dal referto, codice, data e valore, quindi le righe già importate vengono saltate; le righe modificate vengono importate come nuovi risultati.

### Un laboratorio può importare risultati per qualsiasi paziente?
No. Un laboratorio importa nei pazienti il cui progetto ha creato o i cui record la persona ha condiviso con esso tramite OAuth. Altri pazienti non esistono per esso (`404`).

## Correlati

- [API per i risultati di laboratorio](https://developers.anpheros.com/it/guides/lab-results-api): HL7 v2 e FHIR affiancati, e cosa deve contenere un risultato
- [Connettore di laboratorio](https://developers.anpheros.com/it/guides/lab-connector): ogni nome di colonna, campo HL7 e campo di risposta
- [Interoperabilità dei dati sanitari](https://developers.anpheros.com/it/guides/interoperability): perché codici e provenienza rendono utilizzabili i dati scambiati
- [Integrazioni sanitarie](https://developers.anpheros.com/it/guides/healthcare-integrations): cliniche, laboratori, dispositivi e app di terze parti
- [API per dati medici](https://developers.anpheros.com/it/guides/medical-data-api): scrittura di singole osservazioni, condizioni e farmaci
- [Come creare un'app sanitaria AI con un backend FHIR](https://developers.anpheros.com/it/guides/build-ai-healthcare-app): utilizzo di questi risultati in una risposta AI
