Сейчас мы пишем vanilla R4 и хранимся в Google Healthcare API без meta.profile. Каждый builder в коде содержит свою версию того «как должен выглядеть наш Composition / Observation / ClinicalImpression» — это неформальный профайл «в голове + в коде + в wiki». Вопрос — стоит ли формализовать: написать StructureDefinitions, сгенерировать TS-типы + Zod-валидаторы, опционально публиковать IG.
Status: draft
Не да/нет. Несколько осей решения:
- Какой первый профиль (Composition / Patient / Observation)
- Когда триггер (сейчас / при втором партнёре / при regulatory ask)
- Какой scope генерации (только types / + Zod runtime / + IG для партнёров)
- Какой инструмент ( Medplum SDK)
Каждая ось решается отдельно. Rollout incremental — можно формализовать один ресурс, годами использовать только его, расширять по мере появления пользы.
Контекст
Что есть сейчас:
- Vanilla R4, без
meta.profileв emitted ресурсах - ~12 builder’ов в
narrative-to-fhir/fhir-builders/*.ts+ ~5lib/fhir-*-builder.ts - ~10 custom extensions с двумя URL-конвенциями (см. extension-url-conventions)
- Типы из
@types/fhir(vanilla base, без profile-awareness) - Validation — только сервер-side в Google Healthcare API (base R4)
Что появляется когда формализуем:
- FSH-исходники для каждого нашего профиля (
bloodgpt-fhir-profiles/репозиторий, см. IG-layout) - Generated TS-типы и опционально Zod schemas
- Builder’ы используют generated
BloodGPTCompositionвместо vanillaComposition - Опционально — публикуемый IG для партнёров
meta.profileставится в emitted ресурсах
Рассматривали
(A) Не формализовать — оставить vanilla R4
Status quo. Builder’ы пишут «как удобно», typing — base R4, валидация на сервере.
За:
- Нулевая cost adoption: ничего не нужно делать
- Низкий blast radius: смена convention в коде свободная, не привязывает к опубликованному IG
- Не привязывает к profile-aware tooling (никакой миграции в codegen-pipeline)
- Подходит для early-stage когда product не оформлён
Против:
- Builders могут собрать невалидный ресурс — узнаём только при write на сервер
- LLM-output → builder — нет gate, drift между schema в коде и фактическим shape ловится поздно
- Партнёрам отдаём «прочитайте wiki» вместо машиночитаемого contract’а
- Каждый новый extension — ad-hoc, в URL хаос (см. extension-url-conventions)
(B) Формализовать всё разом — full IG со всеми профилями
Пишем FSH для всех ~5 ресурсов BloodGPT + всех ~10 extensions сразу. Один большой PR. Codegen на CI. meta.profile во всех новых ресурсах. Publishable IG.
За:
- Один coordinated effort вместо растянутого
- Все builders типизированы единообразно
- IG готов к публикации как только понадобится
Против:
- Большой PR — высокий риск, сложный review
- Все ресурсы сразу с
meta.profile→ migration ранее записанных в FHIR-store (~старые ресурсы без profile, новые с — несогласованность) - Большой upfront cost при неподтверждённой пользе
- Не успеем выкатить до next quarter
(C) Incremental rollout по одному ресурсу
Начинаем с одного профиля (наиболее централбного или наиболее болезненного). Только этот ресурс получает meta.profile. Остальные builders живут как есть. Когда оцениваем pay-off и убеждаемся что pattern работает — добавляем второй.
За:
- Low blast radius: один builder типизирован, остальные не трогаем
- Откат тривиален: убрать
meta.profileиз одного места - Пилот на конкретном случае → доказательство ROI до большой инвестиции
- Можно остановиться на любом этапе если pay-off не подтверждается
- Standard pattern для команд переходящих от ad-hoc к formal
Против:
- Дольше до того момента когда весь pipeline типизирован
- Если в первом профиле выберем нетипичный ресурс — pattern может не масштабироваться
- В transitional state — часть ресурсов с
meta.profile, часть без; нужен tracking
(D) Half-step: только TS-types без Zod / IG
Пишем FSH → SUSHI → JSON SDs → fhir-ts-codegen → TS-interfaces. Builders типизированы (compile-time gate). Без Zod runtime parser’а, без публикации IG. SDs живут локально, не публикуются.
За:
- Самый дешёвый формальный шаг: type safety без runtime cost и без публичной поверхности
- Не требует миграции FHIR-store
- Не требует публикации IG (никакого external contract)
- Покрывает 80% боли от ad-hoc builders
Против:
- Без Zod — нет runtime gate для LLM-output (mismatch между LLM и builder ловится только когда LLM прислал явно невалидный shape)
- Без published IG — партнёрам всё ещё показываем wiki / код
- SDs живут «в полумраке» — есть formal spec, но не виден внешне
Выбрали (предлагается)
Склонность к комбинации (C) + (D): incremental rollout, начиная с TS-types-only.
- Первый профиль —
BloodGPTComposition(наш центральный AI-output container). Аргументы: централен в pipeline, самый стабильный shape (живёт с 2024), используется во всех downstream tasks (patient summary, timeline, partner exports). Меньший риск drift’а на пилоте. - Scope генерации первой итерации — только TS-types. FSH → JSON SD → fhir-ts-codegen → TS interface. Builder типизирован, compile-time gate.
- Инструмент —
fhir-ts-codegenот reason-healthcare. Open-source, активный maintainer, light-weight. Подробнее — fhir-tooling. - Без публикации IG на первом этапе. SDs живут в
bloodgpt-fhir-profiles/репо локально. Публикация откладывается до появления партнёра-консьюмера или regulatory ask. - Zod runtime parser добавляется когда / если: LLM-output становится сложнее и runtime garbage статистически появляется (наблюдаем телеметрию build failures).
- Расширение на остальные ресурсы — по мере пользы. Не всё разом.
Почему
- (C) минимизирует риск — один builder можно откатить тривиально, plan B доступен на каждом шаге.
- (D) даёт большую часть пользы (compile-time gate, formal spec) при низком cost. Zod / IG — opt-in поверх когда подтверждается pay-off.
- Composition как первый — потому что он самый стабильный и centralном. Если pilot не работает на Composition, не работает нигде.
- Incremental — стандартный pattern. Большинство FHIR-команд проходят этот путь так.
Следствия
- Появляется отдельный репо
bloodgpt-fhir-profiles/(или папкаpackages/fhir-profiles/в monorepo) - На CI добавляется
sushi build+fhir-ts-codegen - В
narrative-to-fhir/fhir-builders/composition.tsимпорт меняется с@types/fhirComposition на generatedBloodGPTComposition - Emitted Composition получает
meta.profile - Open вопрос: backfill старых Composition в FHIR-store или просто newly-emitted получают profile?
- Sub-decision: URL convention для SDs (см. extension-url-conventions) — резолвится первым делом
Открытые вопросы
- Composition vs Patient vs Observation как первый профиль — это сильное соглашение? Composition выбран на интуиции, но Patient может быть проще (более стабильный shape, меньше дискуссий)
- Backfill
meta.profileдля существующих записей в FHIR-store или forward-only? - Если codegen ломает обратную совместимость builders’а — какой rollback path?
- Когда добавлять Zod — порог по объёму LLM-output failures, или по календарю?
- Когда триггерить публикацию IG — на первом партнёре, или раньше (для собственных тестов compliance)?
Связано
- zero-extensions-fhir — текущее состояние «vanilla R4, минимизируем extensions» — active
- extension-url-conventions — sub-decision: URL pattern для SDs/extensions — draft
- fhir-profiling — что такое profile вообще
- fhir-conformance-resources — семейство ресурсов которые писать
- fsh — DSL который используем для авторства
- fhir-code-generation — pipeline SD → TS / Zod
- fhir-implementation-guide — что появится при публикации
- fhir-tooling — выбор инструмента
- fhir-modeling-ai-content — синтез о том как AI-output ложится на FHIR; этот decision формализует то что там описано