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, содержит:
    • parameters[] — per-Observation analysis (пишет batchCreateParameterAnalyses5 из analyze-blood-parameters)
    • overview — narrative от TestOverviewService6
    • followUp — recommendations от FollowUpService
    • panelOverviews — per-panel прозы от PanelOverviewService
    • trends — trend analyses от TrendsService

Gating flags (BG-1337 PR-4) — 4 boolean’а на BloodTest:

  • hasOverview, hasFollowUp, hasPanelOverviews, hasTrends
  • Каждый enrichment ставит свой флаг в true после successful write в enrichmentDraft
  • checkAndTriggerPdf polls эти 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-recommendations enrichment — не в EnrichmentFlagField enum (не gating PDF), запускается в том же fan-out, но куда пишет и кто читает — отдельный consumer (карта продуктов на frontend’е?). Требует verify.
  • Doctor editing AI prose storage — куда сегодня пишут useResultsUpdateParameterDescription / useResultsUpdateTestOverview mutations после 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

Сноски

  1. apps/analysis-worker/src/inngest/functions/interpretation-analysis.function.ts — orchestrator Inngest function (event fhir.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.

  2. 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.

  3. packages/analysis-core/src/services/blood-parameter-analysis.service.tsBloodParameterAnalysisService с 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.

  4. packages/database/prisma/schema.prisma (model BloodTest, поле enrichmentDraft Json?), accessed 2026-05-19, https://github.com/Realai-plus/bloodgpt-for-business/blob/staging/packages/database/prisma/schema.prisma.

  5. packages/analysis-core/src/lib/prisma-batch.tsbatchCreateParameterAnalyses пишет в 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.

  6. 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.

  7. 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.

  8. packages/sdk/generated/hooks/index.tsuseResultsUpdateParameterDescription / useResultsUpdateTestOverview mutation hooks в legacy bloodgpt-frontend, accessed 2026-05-19, https://github.com/Realai-plus/bloodgpt-frontend/blob/main/packages/sdk/generated/hooks/index.ts.