Orchestration shell который интерпретирует один уже-распознанный blood test и производит prose для UI и PDF. Живёт в [apps/analysis-worker/src/inngest/functions/interpretation-analysis.function.ts]1; per-Observation AI-анализ внутри одной из стадий описан на отдельной странице — single-prompt-analysis.
Pipeline называется Test Analysis — на каждый прогон производится один test-scoped результат (FHIR Composition + CarePlan), привязанный к одному BloodTest. Это отличается от health-report-pipeline (patient-scoped, агрегирует все тесты пациента).
Триггер и scope
Pipeline запускается событием fhir.analysis.requested — отправляется из /api/v1/interpret/:testId endpoint в b2b-api. Recognition (PDF/photo → FHIR Observation + DiagnosticReport) происходит вне scope’а этого pipeline’а: на момент trigger’а в FHIR уже лежат resources. b2b клиенты этот endpoint вызывают; исторически b2c тоже использовал тот же путь, сейчас b2c переехал на health-report-pipeline.
Pre-requisite — prisma.parameterRangeDetection таблица должна быть заполнена upstream-сервисом для всех Observations этого теста (per-param range detection — отдельный pipeline, не часть этого).
Stages
fhir.analysis.requested (event)
↓
update-status-analyzing → BloodTest.status = "analyzing"
↓
resolveLocaleOptions
↓
analyze-blood-parameters → BloodParameterAnalysisService.analyze()
├─ fetch FHIR: Patient + DiagnosticReport + Observations
├─ build medical context (Conditions / Medications / Allergies из FHIR)
├─ per Observation (parallel):
│ read parameterRangeDetection (Postgres) для refRange / borderline
│ LLM call ─ `single_parameter_analysis` (Langfuse prompt) — см. [[single-prompt-analysis]]
├─ save → BloodTest.enrichmentDraft.parameters[]
└─ status = "interpreting"
↓
send `interpretation/completed` event
↓
fan-out 5 enrichments (independent functions, parallel, own retries):
├─ enrichment/overview.requested → TestOverviewService → enrichmentDraft.overview + flag hasOverview
├─ enrichment/follow-up.requested → FollowUpService → enrichmentDraft.followUp + flag hasFollowUp
├─ enrichment/panel-overview.requested → PanelOverviewService → enrichmentDraft.panelOverviews + flag hasPanelOverviews
├─ enrichment/trends.requested → TrendsService → enrichmentDraft.trends + flag hasTrends
└─ enrichment/product-recommendations.requested → ProductRecsService (не gating PDF, отдельный consumer)
↓
checkAndTriggerPdf polls 4 boolean flags (hasOverview / hasFollowUp / hasPanelOverviews / hasTrends)
↓ когда все 4 = true
PDF generation
↓
final FHIR write: Composition + CarePlan строятся из enrichmentDraft → source of truth для read-path'а
Каждый enrichment — отдельная Inngest function, создана через общую [factory.ts]2 обёртку (createEnrichmentFunction({eventName, service, flagField, ...})). Granular retry: если overview упал, retry’ится только overview, остальные 4 не трогаются.
Per-param LLM-вызов (один LLM round-trip per Observation, prompt single_parameter_analysis из Langfuse) — описан на single-prompt-analysis; здесь не дублируется. Внутри analyzeParameter3 для каждой Observation также читается prisma.parameterRangeDetection для подгрузки refRange / borderlineMin/Max данных (pre-computed upstream).
analyze-blood-parameters — это жирный шаг: fetch + medical context build + parallel per-param анализ + persistence — всё внутри одного step.run. Не разнесён по Inngest шагам из-за upfront cost decomposition.
Storage layer
После BG-1337 PR-5/PR-7 storage полностью переехал на FHIR-backed модель; legacy Postgres clinical tables дропнуты. Сегодняшнее состояние:
Transient state (живёт во время прогона pipeline’а):
BloodTest.enrichmentDraft— Json column в Postgres BloodTest table4, содержит:
Gating flags (BG-1337 PR-4) — 4 boolean’а на BloodTest:
hasOverview,hasFollowUp,hasPanelOverviews,hasTrends- Каждый enrichment ставит свой флаг в
trueпосле successful write вenrichmentDraft checkAndTriggerPdfpolls эти 4 → когда все true → trigger PDF generation- Это заменило polling legacy clinical таблиц до BG-1337
Post-completion source of truth (после PDF generation):
- FHIR
Composition— содержит overview / panel-overviews / follow-up narrative как sections, ссылается на Observation’ы черезentry[] - FHIR
CarePlan— follow-up tests как care plan activities - Read-path (b2b API endpoint’ы, UI) тянет из FHIR, не из
enrichmentDraft
Dropped в BG-1337 PR-7 (миграционный контекст — для понимания почему сегодня так):
ParameterAnalysis,TestOverview,FollowUpRecommendations,PanelOverview,ParameterTrends— 5 таблиц с per-test clinical data исчезли из Postgres schema7- Их контент переехал в FHIR Composition / CarePlan (post-completion) +
BloodTest.enrichmentDraft(transient pipeline state) - Все legacy backend сервисы которые писали в эти таблицы переведены на enrichmentDraft
Per-param edit affordance
useResultsUpdateParameterDescription / useResultsUpdateTestOverview mutations в legacy [bloodgpt-frontend]8 позволяют doctor/org-admin редактировать AI-generated prose и сохранять. Где сегодня живут эти правки — verify pending: legacy backend писал в ParameterAnalysis / TestOverview (теперь дропнуты), куда mutations пишут после миграции — открытый вопрос. Возможно теперь FHIR Composition section update / Observation note replace.
Открытые вопросы
enrich-fhir-observations.function.ts+save-fhir-ai-resources.function.ts— два дополнительных файла в enrichment/ папке; их роль в pipeline пока не verify’нул. Гипотеза: финальный FHIR write из enrichmentDraft → Composition / CarePlan на финальной фазе. Нужно подтвердить.product-recommendationsenrichment — не вEnrichmentFlagFieldenum (не gating PDF), запускается в том же fan-out, но куда пишет и кто читает — отдельный consumer (карта продуктов на frontend’е?). Требует verify.- Doctor editing AI prose storage — куда сегодня пишут
useResultsUpdateParameterDescription/useResultsUpdateTestOverviewmutations после BG-1337 миграции. Возможно нужно расследованиеapps/b2b-api/endpoint handler’ов. analyze-blood-parametersкак один жирный шаг — fetch FHIR + medical context + parallel per-param + persistence в одномstep.run. Не разнесено на Inngest шаги, что значит retry на любой failure внутри прогоняет весь блок. Acceptable trade-off или candidate для рефакторинга — open.- Failure modes — если 1 из 5 enrichments терминально упадёт,
checkAndTriggerPdfникогда не получит все 4 flag’а true → PDF не сгенерируется. Recovery path не описан.
Связано
- single-prompt-analysis — per-Observation single LLM-call внутри
analyze-blood-parameters(integral part этого pipeline’а, не sibling-подход) - biomarker-analysis-pipeline — другой подход к per-param analysis (V2.5 staged: Reasoner + DoctorWriter + Personalizer), используется в health-report-pipeline
- health-report-pipeline — параллельный patient-scoped pipeline, новый track для b2c через
/api/summary/health-report/generation.requested; не заменяет Test Analysis Pipeline для b2b legacy клиентов - interpretation-scope-patient-vs-test — история перехода test-scoped → patient-scoped
- patient-summary-composition-naming — naming-конфликт
Composition.typeмежду двумя pipeline’ами - legacy-stack-migration — общая мета-страница про миграцию legacy stack; этот pipeline — частный случай (старый track, FHIR storage уже мигрировал, остальное — для b2b клиентов в проде)
- trend-panel — UI компонент в legacy frontend который потребляет trend-выход этого pipeline’а
- fhir-composition / fhir-careplan — целевые FHIR resources на post-completion phase
Сноски
-
apps/analysis-worker/src/inngest/functions/interpretation-analysis.function.ts— orchestrator Inngest function (eventfhir.analysis.requested), accessed 2026-05-19, https://github.com/Realai-plus/bloodgpt-for-business/blob/staging/apps/analysis-worker/src/inngest/functions/interpretation-analysis.function.ts. ↩ -
apps/analysis-worker/src/inngest/functions/enrichment/factory.ts— общая enrichment-function factory (createEnrichmentFunction({eventName, service, flagField, ...})), accessed 2026-05-19, https://github.com/Realai-plus/bloodgpt-for-business/blob/staging/apps/analysis-worker/src/inngest/functions/enrichment/factory.ts. ↩ -
packages/analysis-core/src/services/blood-parameter-analysis.service.ts—BloodParameterAnalysisServiceсanalyze()иanalyzeParameter()(per-Observation LLM-вызов), accessed 2026-05-19, https://github.com/Realai-plus/bloodgpt-for-business/blob/staging/packages/analysis-core/src/services/blood-parameter-analysis.service.ts. ↩ -
packages/database/prisma/schema.prisma(modelBloodTest, полеenrichmentDraft Json?), accessed 2026-05-19, https://github.com/Realai-plus/bloodgpt-for-business/blob/staging/packages/database/prisma/schema.prisma. ↩ -
packages/analysis-core/src/lib/prisma-batch.ts—batchCreateParameterAnalysesпишет вBloodTest.enrichmentDraft.parameters[parameterName](BG-1337 PR-5), accessed 2026-05-19, https://github.com/Realai-plus/bloodgpt-for-business/blob/staging/packages/analysis-core/src/lib/prisma-batch.ts. ↩ -
packages/analysis-core/src/services/test-overview.service.ts— sample enrichment service, пишет вenrichmentDraft.overview(BG-1337 PR-5), accessed 2026-05-19, https://github.com/Realai-plus/bloodgpt-for-business/blob/staging/packages/analysis-core/src/services/test-overview.service.ts. ↩ -
BG-1337 PR-7 в
prisma/schema.prisma:326-329явно фиксирует drop 5 таблиц: «TestOverview / FollowUpRecommendations / PanelOverview / ParameterTrends dropped — content moved to FHIR Composition / CarePlan (post-completion source of truth) and BloodTest.enrichmentDraft (transient pipeline state)», accessed 2026-05-19, https://github.com/Realai-plus/bloodgpt-for-business/blob/staging/packages/database/prisma/schema.prisma#L326-L329. ↩ -
packages/sdk/generated/hooks/index.ts—useResultsUpdateParameterDescription/useResultsUpdateTestOverviewmutation hooks в legacy bloodgpt-frontend, accessed 2026-05-19, https://github.com/Realai-plus/bloodgpt-frontend/blob/main/packages/sdk/generated/hooks/index.ts. ↩