Контекст
В pipeline процесса анализа крови есть фаза AI analysis, которая генерирует:
parameterDetails(текст-комментарий на каждый параметр)interpretation(H/L/N через AI, не только формула value vs range)
Эти данные должны попасть в fhir-observation как note[] + interpretation. Вопрос: когда именно они туда записываются — при initial create или отдельным update.
Рассматривали
- Вариант A: поле
aiNote?: stringнаObservationDatainterface, заполняется доsave-fhir-resourcesstep, попадает в Observation сразу при create. Один write — никакого update. Проблема: AI analysis в текущем pipeline запускается послеsave-fhir-resources. Observation уже создан до того как AI сгенерировал parameterDetails. Поле было бы пустым; либо нужно переставлять весь pipeline. - Вариант B: отдельный step
update-fhir-with-ai-analysis— запускается после AI analysis, делаетPUT Observation/{id}с добавленнымиnote[]+interpretation. Двухшаговая запись (create → update), но не требует ломать pipeline. - Вариант C: переструктурировать pipeline — сначала AI analysis на сыром recognition output, потом одним bundle save в FHIR. Сложно: разные шаги пайплайна, между ними нужно где-то держать промежуточное состояние, а Cloud SQL для PHI как раз убираем (см. phi-in-fhir-not-sql). Один write, но требует что AI analysis работает без FHIR Observation как input — неконсистентно с тем, что часть AI-логики читает Observations.
Выбрали: B — отдельный update-step
Pipeline:
save-fhir-resourcesсоздаёт Observations с базовыми полями (valueQuantity,code,referenceRange).- AI analysis читает Observations через FHIR (по IDs которые вернул шаг 1).
update-fhir-with-ai-analysisInngest function делаетPUT Observation/{id}для каждого, добавляяnote[]+interpretation.
Почему
- Не требует переставлять pipeline. AI analysis уже работает после save-fhir-resources в текущей архитектуре.
- Согласуется с тем что FHIR store становится shared state между Inngest steps (см. inngest и phi-in-fhir-not-sql). AI analysis читает Observations из FHIR, обновляет туда же — никакого in-memory передавания тяжёлых JSON.
PUTидемпотентен по FHIR-семантике — повторные запуски enrichment не ломают данные.- Ясная attribution: при create Observation автор — лаборатория (или null); при update —
note[].authorReferenceуказывает на BloodGPT (см. authorship-organization-not-device).
Claude (признание initial mistake): «Запихивать всё в одно
aiNote— неправильно. И ещё проблема: AI analysis запускается ПОСЛЕ save-fhir-resources в Process pipeline»
Следствия
- Реализация — Inngest function
enrichFhirObservations(enrich-fhir-observations.function.ts). Рядом в той же папкеenrichment/живёт целая семья post-AI шагов:overview-generation,panel-overview-generation,follow-up-generation,trends-generation,product-recommendation-generation,save-fhir-ai-resources. - Observation существует в двух состояниях: post-create (без AI-полей) и post-enrichment (с note[] + interpretation). Любые downstream-читатели должны быть готовы к обоим.
- При rebuild AI analysis (поменяли pipeline или prompt) — re-run только enrichment step, не весь pipeline.
- B2B API GET endpoints, читающие Observations между
save-fhir-resourcesиenrichFhirObservations, увидят их безnote[]+interpretation. Edge case (клиент опрашивает прямо во время процессинга, до генерации) — если нужна гарантированная консистентность, ждатьenrichment.completedevent.
Связано
- fhir-observation — куда пишутся note + interpretation
- inngest — оркестрация steps
- zero-extensions-fhir — interpretation использует стандартный ValueSet, не extension
- phi-in-fhir-not-sql — FHIR store как shared state делает этот паттерн возможным
- authorship-organization-not-device — author для AI-добавленных note[]
Источники
Сноски
-
Реализация, accessed 2026-05-17, https://github.com/Realai-plus/bloodgpt-for-business/blob/main/apps/analysis-worker/src/inngest/functions/enrichment/enrich-fhir-observations.function.ts. ↩
-
Worker registration, accessed 2026-05-17, https://github.com/Realai-plus/bloodgpt-for-business/blob/main/apps/analysis-worker/src/inngest/worker.ts. ↩