Базовый ресурс для одного клинического измерения / оценки / интерпретации. У нас — каждый лабораторный показатель (glucose, ferritin, vitamin D и т.д.) представлен отдельной Observation.

Конкретный пример — реальная Observation из нашего FHIR-store

Hemoglobin-измерение из реального blood test’а, поднято через GET /fhir/Observation/2358 из локального HAPI (HAPI FHIR v8.4.0, R4). Значение 118 g/L — ниже нормы (referenceRange 120-160), отсюда interpretation: L. PHI-чистый: только server-assigned numeric IDs.

Цветовая конвенция (5 категорий, согласовано с Mermaid в fhir-resource-elements):

Resource (вся карточка целиком) External Resource (на которую указывает Reference) DataType / BackboneElement (структура без identity) primitive (атомарное значение) universal slot (meta / extension) Reference target string

Ключи окрашены по типу value за ними (DataType-ключи зелёные; primitive-ключи jelly amber). Сами primitive-values тоже amber. Reference-target строки оранжевые. Universal-slot ключ (meta) красный.

{
  "resourceType": "Observation",
  "id": "2358",
  "meta": {
    "versionId": "2",
    "lastUpdated": "2026-05-18T01:52:19.836+00:00"
  },
  "status": "final",
  "category": [
    { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory" }] },
    { "coding": [{ "system": "http://bloodgpt.com/fhir/CodeSystem/panel-categories", "code": "hematology" }], "text": "Hematology" }
  ],
  "code": {
    "coding": [{ "system": "http://loinc.org", "code": "718-7", "display": "Hemoglobin, Blood" }],
    "text": "Haemoglobin"
  },
  "subject": { "reference": "Patient/2357" },
  "effectiveDateTime": "2024-10-15",
  "issued": "2026-05-18T01:52:19.830Z",
  "valueQuantity": { "value": 118, "unit": "g/l", "system": "http://unitsofmeasure.org", "code": "g/L" },
  "interpretation": [{ "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", "code": "L", "display": "Low" }] }],
  "referenceRange": [{
    "low": { "value": 120, "unit": "g/l" },
    "high": { "value": 160, "unit": "g/l" }
  }]
}

Замечания к примеру:

  • Вся {...} карточка — это Resource (есть id, лежит на HAPI по URL /Observation/2358). На token-level «Resource-цвета» в JSON нет — есть только в legend как объяснение «весь блок целиком».
  • valueQuantity — choice type value[x] развёрнутый в Quantity DataType (а не valueInteger / valueString).
  • effectiveDateTime — choice type effective[x] развёрнутый в primitive date.
  • referenceRangeBackboneElement (inline struct без identity, локальный для Observation).
  • subject.reference: "Patient/2357" — Reference DataType, строка-target оранжевая (указывает на отдельный Resource Patient/2357 который лежит на сервере отдельно).
  • Два category coding’а — стандартный HL7 observation-category: laboratory плюс наш кастомный http://bloodgpt.com/fhir/CodeSystem/panel-categories: hematology (panel grouping для UI).
  • interpretation: L — стандартный код из v3-ObservationInterpretation ValueSet (значение ниже нормы).

Полная аналитика этого же типа Observation в нашем pipeline (parameter_range_type prompt, LLM-pipeline, AI enrichment) — ниже.

Использование в BloodGPT

  • Лабораторные значения — каждый параметр анализа = одна Observation (value + reference range + interpretation H/L/N).
  • Источник данных — изначально это были BloodGPT-таблицы ParameterAnalysis в Cloud SQL (legacy, пока ещё используется). После миграции (см. phi-in-fhir-not-sql) источником становится сам FHIR store, а на вход pipeline-у приходит сырой результат распознавания (PDF / JSON / HL7), из которого Observations создаются и сохраняются прямо в FHIR.
  • AI-аналитика поверх измеренияparameterDetails (AI-объяснение параметра) → note[].text, isHealthy (флаг “в норме / отклонение”) → стандартный interpretation (H/L/N), trend analysis по нескольким измерениям → отдельная Observation с derivedFrom.

Замечание: маппинг ParameterAnalysis → Observation зафиксирован по состоянию на session 871a7608 (Feb 2026). Архитектура продолжает эволюционировать — будем обновлять страницу когда дойдём до ингеста более поздних сессий, где она пересмотрена.

Ключевые поля для нас

  • subjectPatient/{id}, ссылка на пациента

  • code — что измеряли. CodeableConcept с LOINC через loinc: { system: "http://loinc.org", code: "2345-7", display: "Glucose" }

  • value[x] — само значение, choice type. 8 вариантов (valueQuantity / valueInteger / valueCodeableConcept / valueRange / valueRatio / valueBoolean / valueString) + dataAbsentReason. Создаётся в LLM-промпте parameter_range_type (полная mapping-таблица типов и rationale выбора — см. parameter-range-type-prompt). Design doc — specs/RFC/002-parameter-type-fhir-mapping.md (canonical reference для type-mapping logic, e.g. почему qualitative bool → valueCodeableConcept не valueBoolean — HL7 рекомендация для exceptional values). Production statistics (Jan 2026): QUALITATIVE 75% / DESCRIPTIVE 17% / SEMI_QUANTITATIVE 7% / CATEGORICAL 2% от non-quantitative параметров.

  • referenceRange — нормы лаборатории для этого параметра

  • interpretation — стандартный ValueSet http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation. Коды: N (normal), H (high), L (low), A (abnormal). В production используем подмножество N/H/L/A. В экспериментальной ветке bg-1297-lab-interp (commit 05846324, не merged) расширено до 8 кодов добавляя HH/LL (critical high/low) и HU/LU (significantly above/below upper/lower limit) — для приёма lab-provided interpretation через HL7 OBX-8. Заменяет наш custom isHealthy (см. zero-extensions-fhir).

    Источник кода (BG-1207, через LLM): промпт parameter_range_type (Langfuse label b2b-production, текущая version v10). Полная page — parameter-range-type-prompt. Output schema (verified 2026-04-26):

    • interpretation: enum ["N", "H", "L", "A"] — exactly эти 4 buckвы
    • range_type: enum ["EMPTY_OR_MISSING", "LOWER_BOUND_ONLY", "UPPER_BOUND_ONLY", "BOTH_BOUNDS"]
    • parameter_type: FHIR R4 value[x] enum (8 values + dataAbsentReason)
    • fhir_value: typed FHIR-native value structure
    • reference_range: { min, max } numbers

    Эволюция v1 → v10 — multiple iterations. v11 — test, не deployed.

    Production: 3-tier priority chain (BG-1207 + BG-1258, session 731a3f60, deployed). ObservationData в fhir-client.ts:

    1. interpretation?: CodeableConcept[]seed path (BG-1258), готовый CodeableConcept от лаборатории
    2. interpretationCode?: stringLLM path (BG-1207), single letter из parameter_range_type промпта
    3. Deterministic fallback — math comparison

    Priority: seed → LLM → deterministic. Lab-provided wins.

    Экспериментально (ветка bg-1297-lab-interp, commit 05846324, НЕ merged): расширение до 4-tier добавляя top-rung labInterpretationCode?: string — лаборатория отправляет single letter напрямую через POST /api/v1/process interpretation field или HL7 OBX-8 ingest. Plus mismatch warn-log "Interpretation mismatch: lab-provided disagrees with LLM". Когда merge — open.

    Open (BG-1297 out-of-scope items):

    • UI hint surfacing disagreement (lab vs LLM)
    • Prisma column для audit
    • Rename abnormal_flaglab_interpretation

Reader honesty principle (BG-1250, PR #252)

LLM-промпт parameter_range_type hardcoded не синтезировать fake bounds: “DO NOT SYNTHESIZE: When a bound is absent in the lab data, emit null for that side. Inventing bounds corrupts downstream clinical judgement (e.g., flagging HDL=85 as ‘too high’ because a fake max=100 was invented)“. Раньше parser/промпт мог создать borderlineMin = 0 для upper-bound-only — теперь truly null, range_type: UPPER_BOUND_ONLY. Это сломало UI scale bar (assumed both bounds). Fix в ParamScale.tsx: &&|| (рендерить если хотя бы один bound) — session bb193045.

Lesson: prompt semantics остаются correct, UI должна explicitly handle EMPTY_OR_MISSING / LOWER_BOUND_ONLY / UPPER_BOUND_ONLY / BOTH_BOUNDS variants. Implicit invariant “оба bounds present” — anti-pattern.

  • note[] — массив fhir-annotation; у нас содержит parameterDetails (AI-объяснение) с authorReference → Organization/bloodgpt. Это legacy AI-text path — text живёт здесь до тех пор пока V2.5 → fhir-clinical-impression миграция не сделана. Когда CI-направление будет deployed (BG-1323) — parameterDetails тоже мигрирует в CI (вместе с другими V2.5 fields), Observation останется чистой лабой. Сейчас миграция exploratory — см. fhir-modeling-ai-content.
  • derivedFrom — references на другие Observations. Используется для trends — отдельная derived-Observation, которая представляет анализ нескольких реальных измерений (например, “глюкоза тренд за год” — отдельная Observation со ссылкой на N исходных измерений). Это стандартный FHIR-паттерн для derived data, чтобы не смешивать “сырое измерение” с “вычисленным insights”.
  • device — прямая ссылка на устройство-измеритель (лабораторный анализатор). У нас сейчас не заполняется — нужно интеграция с реальным анализатором лаборатории (см. О1 ниже)

AI enrichment паттерн

AI-анализ запускается после save-fhir-resources в pipeline. Поэтому Observation создаётся с базовыми полями (value, code, referenceRange), а потом отдельный шаг делает PUT Observation/{id} с дополненными note[] + interpretation. См. ai-enrichment-separate-step.

Открытые вопросы

[О1] Будем ли использовать device field

Поле device идеально подходит чтобы атрибутировать конкретное измерение конкретному лабораторному анализатору. Сейчас не используем — на вход приходит уже агрегированный результат (PDF/JSON), без информации о приборе. Если в будущем появится integration с реальным лабораторным оборудованием, device field — стандартный способ это записать.

Связано

  • fhir-annotationnote[] использует Annotation data type
  • fhir-composition — Composition.section.entry ссылается на Observations (supporting biomarkers)
  • fhir-careplanactivity.code использует те же LOINC коды
  • loinc — coding system
  • zero-extensions-fhir — стандартные interpretation + note вместо custom extensions
  • ai-enrichment-separate-step — почему обновление через PUT, а не inline на create
  • phi-in-fhir-not-sql — почему ParameterAnalysis Cloud SQL становится legacy

Источники

Источники: 1 2 3 4.

Сноски

  1. HL7 R4 spec, accessed 2026-05-17, https://hl7.org/fhir/R4/observation.html.

  2. v3-ObservationInterpretation ValueSet, accessed 2026-05-17, http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation.

  3. Реализация, accessed 2026-05-17, https://github.com/Realai-plus/bloodgpt-for-business/blob/main/packages/analysis-core/src/lib/save-fhir-resources.ts.

  4. Сессия ildar/871a7608, 2026-02-13 — (extensions reversal в standard fields).