Зачем нужно
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..1 → 1..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 наofficialvsnickname
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-concerncategory для Condition — US-Core-specific, в roadmap
Когда понадобится compliance:
- Выбрать realm profile (US Core / RuCore / IPS)
- Адаптировать builders под required slices, must-support, value set bindings
- Добавить
meta.profileв emitted resources - Включить 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
Источники
Сноски
-
FHIR Profiling overview, accessed 2026-05-17, https://www.hl7.org/fhir/profiling.html. ↩
-
StructureDefinition resource, accessed 2026-05-17, https://www.hl7.org/fhir/structuredefinition.html. ↩
-
Slicing detail, accessed 2026-05-17, https://www.hl7.org/fhir/profiling.html#slicing. ↩
-
FSH (FHIR Shorthand) spec, accessed 2026-05-17, https://hl7.org/fhir/uv/shorthand/. ↩
-
SUSHI compiler, accessed 2026-05-17, https://github.com/FHIR/sushi. ↩
-
IG Publisher, accessed 2026-05-17, https://github.com/HL7/fhir-ig-publisher. ↩
-
Firely Forge, accessed 2026-05-17, https://fire.ly/products/forge/. ↩
-
Simplifier registry, accessed 2026-05-17, https://simplifier.net/. ↩