Сейчас мы пишем vanilla R4 и хранимся в Google Healthcare API без meta.profile. Каждый builder в коде содержит свою версию того «как должен выглядеть наш Composition / Observation / ClinicalImpression» — это неформальный профайл «в голове + в коде + в wiki». Вопрос — стоит ли формализовать: написать StructureDefinitions, сгенерировать TS-типы + Zod-валидаторы, опционально публиковать IG.

Status: draft

Не да/нет. Несколько осей решения:

  1. Какой первый профиль (Composition / Patient / Observation)
  2. Когда триггер (сейчас / при втором партнёре / при regulatory ask)
  3. Какой scope генерации (только types / + Zod runtime / + IG для партнёров)
  4. Какой инструмент ( Medplum SDK)

Каждая ось решается отдельно. Rollout incremental — можно формализовать один ресурс, годами использовать только его, расширять по мере появления пользы.

Контекст

Что есть сейчас:

  • Vanilla R4, без meta.profile в emitted ресурсах
  • ~12 builder’ов в narrative-to-fhir/fhir-builders/*.ts + ~5 lib/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 вместо vanilla Composition
  • Опционально — публикуемый 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.

  1. Первый профиль — BloodGPTComposition (наш центральный AI-output container). Аргументы: централен в pipeline, самый стабильный shape (живёт с 2024), используется во всех downstream tasks (patient summary, timeline, partner exports). Меньший риск drift’а на пилоте.
  2. Scope генерации первой итерации — только TS-types. FSH → JSON SD → fhir-ts-codegen → TS interface. Builder типизирован, compile-time gate.
  3. Инструмент — fhir-ts-codegen от reason-healthcare. Open-source, активный maintainer, light-weight. Подробнее — fhir-tooling.
  4. Без публикации IG на первом этапе. SDs живут в bloodgpt-fhir-profiles/ репо локально. Публикация откладывается до появления партнёра-консьюмера или regulatory ask.
  5. Zod runtime parser добавляется когда / если: LLM-output становится сложнее и runtime garbage статистически появляется (наблюдаем телеметрию build failures).
  6. Расширение на остальные ресурсы — по мере пользы. Не всё разом.

Почему

  • (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/fhir Composition на generated BloodGPTComposition
  • 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)?

Связано