# Jak zaimportować wyniki laboratoryjne do rekordu FHIR

> Samouczek: zaimportuj raport laboratoryjny jako CSV i jako HL7 v2 ORU^R01 do FHIR R4 Observations z kodami LOINC i zakresami referencyjnymi, obsługuj niezmapowane znaczniki i ponowne wysyłanie raportów oraz śledź każdy wynik do laboratorium.

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

**Ten samouczek przedstawia raport laboratoryjny w postaci pliku CSV oraz wiadomości HL7 v2 ORU^R01, importuje go do rekordu HL7 FHIR R4 pacjenta w jednym żądaniu dla każdego formatu i pokazuje, co otrzymasz w zamian: zasoby laboratoryjne `Observation` z kodami LOINC, zakresami referencyjnymi i pochodzeniem.** Obejmuje również dwa problemy, z którymi boryka się każda integracja laboratoryjna: znaczniki, których nie można zmapować automatycznie, oraz raporty, które są wysyłane dwukrotnie. Wszystko działa na darmowym środowisku piaskownicy z syntetycznym pacjentem.

## Dlaczego wyniki badań laboratoryjnych są trudne do przechowywania

Wynik badania laboratoryjnego wydaje się prosty: parametr, wartość, jednostka, zakres referencyjny, data. W praktyce każde laboratorium nazywa parametry na swój sposób („Hemoglobina glicozilata”, „HbA1c”, „Glycated haemoglobin”), używa przecinków lub kropek do zapisu dziesiętnego, zapisuje zakresy jako `4,0-5,6`, `<5` lub `>3.5` i wysyła ten sam wynik ponownie, gdy coś się nie powiedzie. Przechowywane jako tekst swobodny, wyniki te nie mogą być porównywane w czasie, wykresowane, udostępniane innej aplikacji ani przekazywane modelowi AI z pewnością.

Rekord FHIR przechowuje każdy wynik jako `Observation` z kodem LOINC, jednostką UCUM, wartością, zakresem referencyjnym i datą, a także przechowuje informacje o tym, kto go wysłał w zasobie `Provenance`. [Łącznik laboratoryjny](https://developers.anpheros.com/pl/guides/lab-connector) wykonuje tę konwersję za Ciebie.

```
 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
```

## Zanim zaczniesz

Potrzebujesz klucza sandbox (`sk_test_…`) z [platform.anpheros.com/dashboard](https://platform.anpheros.com/dashboard/) (**Uzyskaj klucz sandbox**). Przykłady używają `curl`; [SDK](https://developers.anpheros.com/pl/guides/sdks) mają te same wywołania (`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')
```

## Krok 1: importowanie raportu CSV

Tak może wyglądać eksport raportu przez rumuńskie laboratorium: średniki, przecinki dziesiętne, rumuńskie nazwy kolumn i daty jako `DD.MM.YYYY`. Konektor rozpoznaje nazwy kolumn rumuńskie i angielskie.

```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
```

Odpowiedź (`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, glukoza i TSH zostały zmapowane do LOINC na podstawie ich nazw. Przecinki dziesiętne zamieniły się w liczby, a zakresy stały się `referenceRange`. Jeden wiersz powrócił w `unmapped`. Jest on nadal przechowywany pod własnym kodem laboratorium (`urn:anpheros:lab-code`) z dokładnym tekstem z raportu, dzięki czemu nic nie zostało utracone.

## Krok 2: obsługa niezmapowanych znaczników

Niektóre nazwy wymagają decyzji. "Glicemie" (glukoza we krwi) jest dobrym przykładem: LOINC ma jeden kod dla glukozy w surowicy lub osoczu (`2345-7`, próbka żylna pobrana w laboratorium) i inny dla glukozy w pełnej krwi (`2339-0`, na przykład z nakłucia palca). W raporcie laboratoryjnym sama nazwa "Glicemie" jest mapowana na surowicę lub osocze. Nazwa zawierająca "capilar", "capillary" lub "sânge integral" jest mapowana na pełną krew:

```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"}]}
```

Dla znaczników, których konektor nie rozpoznaje, wyszukaj kod:

```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}
```

Następnie wyślij kod w kolumnie `LOINC`. Kod w raporcie zawsze ma pierwszeństwo przed nazwą. Zachowaj to mapowanie dla każdego laboratorium po swojej stronie i dodaj kolumnę do każdego raportu z niego:

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

Znaczniki, których LOINC w ogóle nie obejmuje, takie jak wymyślony "Marker foarte rar", mogą pozostać pod kodem laboratorium. Są one wyświetlane pacjentowi i włączane do dokumentacji. Nie można ich porównywać z wynikami innych laboratoriów.

## Krok 3: wysłanie tego samego raportu dwukrotnie

Laboratoria wysyłają raporty ponownie. Każdy wiersz otrzymuje stabilny identyfikator zbudowany z identyfikatora raportu, kodu, daty i wartości, więc drugie zaimportowanie tego samego raportu niczego nie tworzy:

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

Wiersze, które uległy zmianie, na przykład poprawiona wartość, są importowane jako nowe wyniki. Nagłówek `Idempotency-Key` dodaje drugą gwarancję: ponowna próba sieciowa z tym samym kluczem zwraca pierwszą odpowiedź bez zmian.

## Krok 4: import wiadomości HL7 v2 ORU^R01

Większość systemów informatycznych laboratoriów komunikuje się w standardzie HL7 v2. Wyślij wiadomość w oryginalnym formacie (tekst ER7, segmenty oddzielone CR lub 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` z systemem kodowania `LN` jest używany bezpośrednio jako LOINC. `OBX-7` staje się zakresem referencyjnym, a `OBX-8` (`H`, `N`) interpretacją. `OBR-3` (`R-77`) jest zachowywane jako identyfikator raportu w metadanych pochodzenia. Pacjentem jest ten z adresu URL: segment `PID` nie jest używany do wyszukiwania pacjenta, więc błędny `PID` nie może przypisać wyników do innej osoby.

Posiadasz już przetworzone wyniki? Wyślij JSON: `{"date": "2026-10-01", "origin": "report-77", "items": [{"marker": "Ferritin", "value": 38, "unit": "ng/mL", "ref_low": 15, "ref_high": 150}]}`. Ferrytyna jest mapowana na LOINC `2276-4`.

## Krok 5: Odczyt wyników jako FHIR

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

Zaimportowane HbA1c, tak jak są przechowywane:

```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": "%"}}]
}
```

Nazwa LOINC to standardowa angielska nazwa. `code.text` zachowuje własne sformułowanie laboratorium, dzięki czemu pacjent nadal widzi to, co było na papierowym raporcie. Więcej informacji o zasobach i wyszukiwaniu można znaleźć w przewodniku po [platformie FHIR](https://developers.anpheros.com/pl/guides/fhir) oraz [systemach kodowania FHIR](https://developers.anpheros.com/pl/guides/fhir-code-systems).

## Krok 6: Sprawdź, kto wysłał

```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` i `source_system: labx` pozostają z wartością. Kiedy aplikacja AI odczyta później ten wynik za pomocą [API kontekstu](https://developers.anpheros.com/pl/guides/llm-healthcare-data), zostanie on oznaczony jako `[import/labx]`, dzięki czemu model będzie mógł odróżnić wynik laboratoryjny od wartości wprowadzonej przez pacjenta.

## Co dalej

- Pacjent widzi wyniki w swojej aplikacji. Każda aplikacja, na którą wyraził zgodę, może je odczytać, w ramach swoich zakresów ([model zgody i dostępu](https://developers.anpheros.com/pl/guides/consent)).
- Dla każdej obserwacji (Observation) uruchamiany jest [webhook](https://developers.anpheros.com/pl/guides/webhooks) `resource.created`, dzięki czemu pulpit kliniki lub podsumowanie AI może zostać zaktualizowane bez konieczności odpytywania.
- `POST /fhir/R4/Observation/$validate?profile=eu-lab` raportuje, jak wynik wypada w porównaniu z profilem raportu laboratoryjnego HL7 Europe.
- Do 500 wierszy na żądanie; limity są wymienione w [Limity i limity szybkości](https://developers.anpheros.com/pl/guides/limits).

## Często zadawane pytania

### Jakie formaty raportów laboratoryjnych można zaimportować do FHIR?
CSV z wierszem nagłówka (akceptowane przecinek, średnik lub tabulator, przecinek dziesiętny), wiadomości HL7 v2 ORU^R01 oraz JSON. Raporty PDF można przechowywać jako dokumenty; wyodrębnione z nich wartości wskazują z powrotem na dokument.

### Co się dzieje ze znacznikami bez kodu LOINC?
Są one przechowywane pod własnym kodem laboratorium z dokładnym tekstem z raportu i wymienione w `unmapped`. Dodaj kolumnę LOINC dla tego laboratorium, a zostaną one zmapowane. Kod w raporcie zawsze ma pierwszeństwo przed nazwą.

### Czy bezpieczne jest ponowne wysłanie raportu?
Tak. Każdy wiersz ma stabilny identyfikator zbudowany z raportu, kodu, daty i wartości, dzięki czemu zaimportowane już wiersze są pomijane; zmienione wiersze są importowane jako nowe wyniki.

### Czy laboratorium może importować wyniki dla dowolnego pacjenta?
Nie. Laboratorium importuje do pacjentów, których projekt zostało utworzone lub których rekordy dana osoba udostępniła mu za pośrednictwem OAuth. Inni pacjenci dla niego nie istnieją (`404`).

## Powiązane

- [API wyników laboratoryjnych](https://developers.anpheros.com/pl/guides/lab-results-api): HL7 v2 i FHIR obok siebie oraz co musi zawierać pojedynczy wynik
- [Łącznik laboratoryjny](https://developers.anpheros.com/pl/guides/lab-connector): każda nazwa kolumny, pole HL7 i pole odpowiedzi
- [Interoperacyjność danych medycznych](https://developers.anpheros.com/pl/guides/interoperability): dlaczego kody i pochodzenie danych sprawiają, że wymieniane dane są użyteczne
- [Integracje medyczne](https://developers.anpheros.com/pl/guides/healthcare-integrations): przychodnie, laboratoria, urządzenia i aplikacje stron trzecich
- [API danych medycznych](https://developers.anpheros.com/pl/guides/medical-data-api): pisanie pojedynczych obserwacji, schorzeń i leków
- [Jak zbudować aplikację AI w służbie zdrowia z backendem FHIR](https://developers.anpheros.com/pl/guides/build-ai-healthcare-app): wykorzystanie tych wyników w odpowiedzi AI
