Зачем нужно

Base FHIR (Patient, Observation, Condition…) намеренно максимально гибкий — большинство полей optional, value sets широкие, чтобы покрыть любую систему здравоохранения в мире. Это значит: невозможно полагаться на структуру.

Пример из US healthcare: у пациента в Patient.identifier сервер A кладёт hospital MRN (Medical Record Number, внутренний ID госпиталя) первым, у сервера B первым идёт SSN (Social Security Number, федеральный), у сервера C — Medicare Beneficiary Identifier. Каждый из них кодируется через system URL (http://hospital.example.org/mrn / http://hl7.org/fhir/sid/us-ssn / http://hl7.org/fhir/sid/us-medicare). У иностранца SSN не будет вовсе. У детей нет MBI. Для Observation.code сервер A использует только LOINC, сервер B расширяет SNOMED. Чтобы две системы могли обмениваться данными — нужна constraint-конвенция: «в нашей экосистеме Patient выглядит вот так — MRN обязателен, SSN опционален, MBI опционален, NDC не кодируем». Это и есть профиль.

Формально профиль — это FHIR-ресурс StructureDefinition, который наследует от базового ресурса и накладывает дополнительные ограничения. SD — один из членов семейства conformance-ресурсов (вместе с ValueSet, CodeSystem, CapabilityStatement, OperationDefinition, SearchParameter, ImplementationGuide); все они описывают поведение системы, а не данные о пациенте.

Что профиль умеет менять

Cardinality

0..11..1 делает optional поле required. 0..*1..* требует хотя бы один элемент. Профиль может только сужать, не расширять — 1..1 сделать 0..1 нельзя.

Must-support

Пометка «имплементация обязана уметь обработать это поле». Тонкая разница с required:

  • required (через cardinality) — поле должно присутствовать в каждом ресурсе
  • must-support — поле может отсутствовать в ресурсе, но если оно есть — система обязана уметь его прочитать/обработать

Slicing — типизация repeating элементов

Большинство FHIR-полей которые могут повторяться (identifier, name, telecom, address, Observation.component, Bundle.entry) — массивы однотипных объектов. Порядок элементов стандартом не специфицирован.

Пример. У пациента может быть несколько identifier’ов:

"identifier": [
  { "system": "http://hospital.example.org/mrn",  "value": "P-12345"    }, // hospital MRN
  { "system": "http://hl7.org/fhir/sid/us-ssn",   "value": "123-45-6789" }, // SSN
  { "system": "http://hl7.org/fhir/sid/us-medicare", "value": "1AB2-CD3-EF45" }  // Medicare Beneficiary ID
]

Без профиля чтобы достать MRN нужно искать по system:

patient.identifier.find(i => i.system === "http://hospital.example.org/mrn")

И никто не гарантирует что нужный identifier вообще присутствует. У ребёнка нет Medicare Beneficiary ID. У туриста нет SSN.

Slicing решает это. Профиль определяет именованные slices Patient.identifier через discriminator (обычно system). Пример FSH из стиля US Core Patient:

* identifier ^slicing.discriminator.type = #value
* identifier ^slicing.discriminator.path = "system"
* identifier ^slicing.rules = #open
* identifier contains
    mrn 1..1 MS and
    ssn 0..1 MS and
    medicare 0..1 MS
* identifier[mrn].system = "http://hospital.example.org/mrn"
* identifier[ssn].system = "http://hl7.org/fhir/sid/us-ssn"
* identifier[medicare].system = "http://hl7.org/fhir/sid/us-medicare"

Теперь:

  • identifier[mrn] обязателен (1..1) — validator поймает если MRN отсутствует
  • К slice можно обращаться по имени: Patient.identifier:mrn.value
  • IDE / codegen может построить типизированный accessor: patient.mrnIdentifier

Slicing применяется не только к identifier. Типичные usage:

  • Bundle.entry — slices на типы ресурсов (Composition first, Patient, Observation×N)
  • Observation.component — slices на конкретные measurements (systolic + diastolic для BP)
  • Composition.section — slices на разделы документа (problems / medications / vitals)
  • Patient.name — slices на official vs nickname

Fixed values / patterns

Закрепить конкретное значение. Разница:

  • fixed — точное совпадение всего объекта/значения
  • pattern — частичное совпадение, ресурс может добавить ещё поля

Пример: Composition.type.coding.system = "http://loinc.org" (fixed) → заставит coding system всегда быть LOINC.

Value set bindings

Ограничить какие коды можно класть в CodeableConcept. Binding strength:

StrengthПоведение
requiredТолько из этого value set, иначе ресурс невалиден
extensibleПреимущественно из этого, можно extend если нужный код отсутствует
preferredРекомендуется, но не обязательно
exampleПросто иллюстрация, не enforcement

Пример из US Core: Observation.code для vital signs привязан к LOINC vital signs panel с binding required — нельзя класть произвольные LOINC-коды.

Extensions

Добавить новое поле через extension mechanism. Профиль может объявить custom extension (Patient.extension:snils) или унаследовать из родительского IG. Контраст с нашим подходом — zero-extensions-fhir.

References — типизация связей

Reference(Patient)Reference(USCorePatient). Это значит — Observation.subject должен указывать только на пациента который сам соответствует US Core Patient профилю. Цепная конформность.

Слои профилирования

Base FHIR (HL7 spec — Patient, Observation, ...)
    ↓
Realm/Country IG    →  [[../technical/us-core|US Core]], [[../technical/rucore|RuCore]], IL Core, CA Core, AU Core
    ↓
Domain-specific IG  →  IPS (Patient Summary), Da Vinci HRex (US payer), mCODE (oncology), [[../product/fhir-gravity|Gravity SDoH]]
    ↓
Vendor profile      →  Epic FHIR implementation, Cerner FHIR
    ↓
Local profile       →  Internal SaaS / больница

Профиль наследует от родителя: US Core Patient constraint’ит base FHIR Patient; vendor profile может constraint’ить US Core Patient ещё дальше.

Артефакты профилирования

  • StructureDefinition — сам профиль (один на каждый профилированный ресурс или extension)
  • CapabilityStatement — что FHIR-сервер умеет (какие профили support’ит, какие операции / search-параметры)
  • ValueSet / CodeSystem — определения кодов
  • OperationDefinition — custom операции ($everything, $match, $validate)
  • SearchParameter — кастомные search-параметры (в RuCore было упущение — extensions добавили, SearchParameter под них не делали, rucore)
  • ImplementationGuide — meta-ресурс который связывает всё вышеперечисленное в один published IG

Tooling

  • FSH (FHIR Shorthand) — текстовый DSL для написания профилей, human-readable. Пример: * identifier 1..* MS = «identifier обязательный, минимум один, must-support».
  • SUSHI — компилятор FSH → JSON StructureDefinition. Использует Ru-SEMD и большинство современных IG.
  • IG Publisher — генерирует HTML IG-сайт из source (см. fhir-implementation-guide). Это то, что публикуется на build.fhir.org/ig/*.
  • FHIR Validator — валидирует resource против profile (CLI + Java library).
  • Forge (Firely) — visual editor для профилей.
  • Simplifier.net — публичный registry профилей.
  • HAPI FHIR — Java FHIR-сервер с встроенной валидацией против профилей.

Более широкий landscape — fhir-tooling (types-libraries, runtime валидаторы, server SDKs, profile-tooling сравнение).

Из профиля можно автоматически сгенерировать TS / Zod / C# типы — закрывает дыру «builder собирает невалидный ресурс на compile-time». Подробнее — fhir-code-generation.

Validation flow

Resource объявляет свою конформность через meta.profile:

{
  "resourceType": "Patient",
  "meta": {
    "profile": [
      "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
    ]
  },
  "identifier": [ ... ],
  ...
}

Сервер (или offline validator) проверяет ресурс против указанного профиля при POST / PUT. Это claim ресурса о соответствии. Без meta.profile валидация идёт только против base FHIR.

Profile vs Extension vs Custom Resource

  • Profile — constraint существующего resource. Правильный путь для адаптации.
  • Extension — добавить новое поле через extension mechanism (объявляется через StructureDefinition с type = Extension). Используется внутри профиля для нестандартных полей.
  • Custom Resource — определить свой не-FHIR resource type. Антипаттерн — нарушает интероперабельность, ни один FHIR-клиент его не поймёт.

Релевантность для BloodGPT

Мы не профилируем — а на практике это значит:

  • Наш FHIR output (multi-tenant-fhir-storage) на Google Healthcare API пишет vanilla R4 (base FHIR без дополнительных constraints).
  • В каждом ресурсе meta.profile пустой — мы не декларируем conformance ни к US Core, ни к RuCore, ни к IPS.
  • Поэтому FHIR-сервер при POST резурса валидирует только против base FHIR (cardinality, типы данных) — без must-support checks, без required value set bindings, без slicing constraints.
  • В builder-коде (narrative-to-fhir/*) мы пишем поля как нам удобно: Condition.category = problem-list-item — это US Core convention но не enforced US Core profile conformance (мы не декларируем). Если завтра решим что category = encounter-diagnosis — никто не остановит.

Это сознательное решение пока не выходим на регулируемые рынки:

  • US-рынок → US Core profiles обязательны (us-core — HTI-1, CMS Patient Access API)
  • РФ-рынок → RuCore profiles + НСИ value sets (rucore, nsi-rosminzdrav)
  • IPS (International Patient Summary) → для cross-border data exchange / Apple Health Records

Conventions из realm-профилей мы уже частично используем, без явного profile-conformance:

  • Condition.category = problem-list-item — US Core code, выбран как query-friendly default (category-coverage)
  • health-concern category для Condition — US-Core-specific, в roadmap

Когда понадобится compliance:

  1. Выбрать realm profile (US Core / RuCore / IPS)
  2. Адаптировать builders под required slices, must-support, value set bindings
  3. Добавить meta.profile в emitted resources
  4. Включить validation на FHIR-сервере (Google Healthcare API поддерживает custom profile validation через $validate)

Открытые вопросы

  • В каком сценарии profiling нужен первым — US Core (Apple Health Records integration?) или IPS (cross-border)?
  • Будем ли мы публиковать собственный BloodGPT IG (декларируем какой shape наш output имеет — для downstream consumers и compliance audits) или просто claim US-Core/IPS conformance.

Связано

  • fhir-conformance-resources — семейство meta-ресурсов, из которых SD центральный
  • fsh — DSL для авторства SDs
  • fhir-implementation-guide — packaging SDs + ValueSets + markdown в одну поставку
  • fhir-code-generation — pipeline SD → TS / Zod / C# (закрывает дыру build-time)
  • fhir-tooling — landscape инструментов поверх FHIR-спеки
  • us-core — US Core IG, самый distinct пример профилирования (регуляторно обязателен)
  • rucore — российский аналог, контраст по extension-подходу и регуляторному статусу
  • zero-extensions-fhir — наша политика «не добавлять extensions без необходимости», прямая follow-on тема — active
  • fhir-versions — R4 vs R5; профилирование совместимо с обеими, но IG обычно targeting один FHIR major version
  • category-coverage — конкретный пример где US Core conventions у нас уже зашиты
  • multi-tenant-fhir-storage — наше FHIR-хранение; profile validation на этом уровне может включаться opt-in
  • formalize-fhir-profiles — наше решение писать ли свои профили — draft
  • extension-url-conventions — sub-вопрос: URL pattern для наших SDs/extensions — draft

Источники

Источники: 1 2 3 4 5 6 7 8.

Сноски

  1. FHIR Profiling overview, accessed 2026-05-17, https://www.hl7.org/fhir/profiling.html.

  2. StructureDefinition resource, accessed 2026-05-17, https://www.hl7.org/fhir/structuredefinition.html.

  3. Slicing detail, accessed 2026-05-17, https://www.hl7.org/fhir/profiling.html#slicing.

  4. FSH (FHIR Shorthand) spec, accessed 2026-05-17, https://hl7.org/fhir/uv/shorthand/.

  5. SUSHI compiler, accessed 2026-05-17, https://github.com/FHIR/sushi.

  6. IG Publisher, accessed 2026-05-17, https://github.com/HL7/fhir-ig-publisher.

  7. Firely Forge, accessed 2026-05-17, https://fire.ly/products/forge/.

  8. Simplifier registry, accessed 2026-05-17, https://simplifier.net/.