Контейнер для группы FHIR-ресурсов. Используется широко: API-операции (создать несколько ресурсов одним запросом), search responses (сервер возвращает множество ресурсов на query), document export (clinical document как root + referenced resources), async messaging.

Структура Bundle

Resource Bundle (FHIR R4) имеет следующие поля1:

ПолеCardinalityКогда используется
type1..1 (code)Всегда — определяет семантику (см. таблицу ниже)
entry[]0..* (BackboneElement)Сами ресурсы / операции; разворачивается ниже
identifier0..1 (Identifier)Идентифицировать сам Bundle (transaction id, document id)
timestamp0..1 (instant)Когда Bundle был assembled
total0..1 (unsignedInt)Для searchset — общее число matching resources (для пагинации)
link[]0..* (BackboneElement)Pagination links (self, next, prev) — для searchset с paging
signature0..1 (Signature)Digital signature — особенно для document Bundle

Каждый entry сам — BackboneElement с несколькими полями:

  • fullUrl (uri) — URI ресурса. Для нового ресурса в transaction — urn:uuid:<UUID> placeholder; для existing — absolute URL.
  • resource (Resource) — сам FHIR-ресурс целиком.
  • request (для transaction/batch) — method (POST/PUT/DELETE/GET), url, плюс conditional headers: ifNoneMatch, ifMatch, ifNoneExist, ifModifiedSince.
  • response (от сервера) — status (201 Created, 200 OK, etc.), location (assigned URL), etag, lastModified, outcome (если были warnings/errors).
  • search (для searchset) — mode (match / include / outcome), score (для ranked results).
  • link[] — links специфичные для entry.

Типы Bundle (FHIR R4)

Bundle.type — обязательное поле, определяет семантику обработки:

TypeЧто это
transactionАтомарный batch CRUD — сервер применяет всё или ничего (ACID). Resolve внутренних urn:uuid: placeholder’ов.
batchНеатомарный batch CRUD — каждый entry независим, failures одного не блокируют другие.
documentДокумент-снимок: первый entry — Composition, дальше referenced ресурсы. Семантически законченный clinical document. Используется в ips-standard (Patient/$summary возвращает Bundle type=document).
searchsetОтвет сервера на FHIR search query (GET Observation?subject=Patient/123 → matching ресурсы + included).
collectionBag of resources без специфической семантики — просто группа.
historyОтвет сервера на history-операцию — versions ресурса.
messageAsync messaging — первый entry это MessageHeader.

Transaction Bundle

В нашем стеке — основной кейс. Pipeline-шаг save-fhir-resources собирает Bundle с Composition + Observations + ServiceRequest + CarePlan + Provenance + Organization и POST-ит в google-healthcare-api одним запросом. Атомарность критична: частичный fail хуже чем full rollback (получится half-saved analysis без Composition или без observations).

Пример Bundle (Patient + Observation которая ссылается на ещё-не-созданный Patient):

{
  "resourceType": "Bundle",
  "type": "transaction",
  "entry": [
    {
      "fullUrl": "urn:uuid:patient-1",
      "resource": { "resourceType": "Patient", ... },
      "request": { "method": "POST", "url": "Patient" }
    },
    {
      "fullUrl": "urn:uuid:obs-1",
      "resource": {
        "resourceType": "Observation",
        "subject": { "reference": "urn:uuid:patient-1" }
      },
      "request": { "method": "POST", "url": "Observation" }
    }
  ]
}

UUID resolution

Ключевой механизм: одна и та же строка urn:uuid:patient-1 живёт в двух местах — как fullUrl нового ресурса (декларация placeholder’а) и как Reference.reference из другого entry (ссылка на него). Сервер matches их по строке, присваивает real id, переписывает ссылки.

Bundle transaction UUID resolution — 3 фазы (клиент → сервер resolve → сохранено)

Источник Mermaid — attachments/bundle-uuid-resolution.mmd.

Четыре прохода на сервере2:

  1. Parse — собрать все urn:uuid:* placeholder’ы из entry[].fullUrl
  2. Assign real IDs — для каждого POST entry сгенерировать server-assigned id (urn:uuid:patient-1 → Patient/p-9001)
  3. Rewrite references — найти все Reference.reference со значением urn:uuid:patient-1 и заменить на Patient/p-9001
  4. Apply atomically — записать всё за одну transaction. Любой fail → rollback всех entries (ACID).

Альтернативные форматы fullUrl:

  • urn:uuid:<UUID> — placeholder для нового ресурса (POST). Resolve’ится в server-assigned id.
  • Absolute URL (https://server/Patient/123) — для existing ресурса (PUT/PATCH/DELETE/GET). Не resolve’ится.
  • Conditional через ifNoneExist — см. ниже.

Conditional create — ifNoneExist

Паттерн для shared ресурсов, которые должны существовать в FHIR store ровно одной копией (Organization-as-author, ранее fhir-device). В entry.request ресурса добавляется условие — сервер создаст ресурс только если такого ещё нет:

{
  "resource": { "resourceType": "Organization", "id": "bloodgpt-com", ... },
  "request": {
    "method": "POST",
    "url": "Organization",
    "ifNoneExist": "identifier=bloodgpt-com"
  }
}

Идемпотентно — Bundle можно повторно отправить без дублей. Применяется для refs между Bundle’ами: один раз создан Organization/bloodgpt, далее каждый analysis Bundle ссылается на него по identifier через ifNoneExist.

Можно ли генерировать кодом? Codegen FHIR-инструменты (fhir-code-generation) разделяются на два слоя:

  • Type generation — типы / билдеры из StructureDefinition. Bundle.entry.request.ifNoneExist появляется как typed string field; задать значение через builder — генерится автоматически. Тривиально.
  • Workflow / pattern generation — «когда применять ifNoneExist, на какой Identifier, с какой URL’ой» — не выводится из спеки. Это application decision: знание «Organization — singleton, всегда ifNoneExist по identifier» не часть FHIR profile, а часть нашей domain logic.

В save-fhir-resources.ts ifNoneExist для Organization пишется руками. Если бы хотели автоматизировать — сделали бы helper createSharedResource(organization) который сам бы emit’ил entry с правильным ifNoneExist. Это уровень custom infrastructure, а не FHIR codegen.

Использование в BloodGPT

Один transaction Bundle = один analysis run. Реализация — packages/analysis-core/src/lib/save-fhir-resources.ts. Document / searchset / batch / message не используем.

Связано

Сноски

  1. HL7 R4 spec — Bundle, accessed 2026-05-20, https://hl7.org/fhir/R4/bundle.html.

  2. HL7 R4 spec — Bundle transaction processing, accessed 2026-05-20, https://hl7.org/fhir/R4/bundle.html#transaction.