Базовый ресурс для одного клинического измерения / оценки / интерпретации. У нас — каждый лабораторный показатель (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):
Ключи окрашены по типу 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
{ "coding": [{ "system": "http
],
"code": {
"coding": [{ "system": "http
"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
"interpretation": [{ "coding": [{ "system": "http
"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 typevalue[x]развёрнутый в Quantity DataType (а неvalueInteger/valueString).effectiveDateTime— choice typeeffective[x]развёрнутый в primitivedate.referenceRange—BackboneElement(inline struct без identity, локальный для Observation).subject.reference: "Patient/2357"— Reference DataType, строка-target оранжевая (указывает на отдельный ResourcePatient/2357который лежит на сервере отдельно).- Два
categorycoding’а — стандартный HL7observation-category: laboratoryплюс наш кастомныйhttp://bloodgpt.com/fhir/CodeSystem/panel-categories: hematology(panel grouping для UI). interpretation: L— стандартный код изv3-ObservationInterpretationValueSet (значение ниже нормы).
Полная аналитика этого же типа 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). Архитектура продолжает эволюционировать — будем обновлять страницу когда дойдём до ингеста более поздних сессий, где она пересмотрена.
Ключевые поля для нас
-
subject—Patient/{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— стандартный ValueSethttp://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation. Коды:N(normal),H(high),L(low),A(abnormal). В production используем подмножество N/H/L/A. В экспериментальной веткеbg-1297-lab-interp(commit05846324, не merged) расширено до 8 кодов добавляяHH/LL(critical high/low) иHU/LU(significantly above/below upper/lower limit) — для приёма lab-provided interpretation через HL7 OBX-8. Заменяет наш customisHealthy(см. zero-extensions-fhir).Источник кода (BG-1207, через LLM): промпт
parameter_range_type(Langfuse labelb2b-production, текущая versionv10). Полная 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 R4value[x]enum (8 values + dataAbsentReason)fhir_value: typed FHIR-native value structurereference_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:interpretation?: CodeableConcept[]— seed path (BG-1258), готовый CodeableConcept от лабораторииinterpretationCode?: string— LLM path (BG-1207), single letter изparameter_range_typeпромпта- Deterministic fallback — math comparison
Priority: seed → LLM → deterministic. Lab-provided wins.
Экспериментально (ветка
bg-1297-lab-interp, commit05846324, НЕ merged): расширение до 4-tier добавляя top-runglabInterpretationCode?: string— лаборатория отправляет single letter напрямую черезPOST /api/v1/processinterpretationfield или 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_flag→lab_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-annotation —
note[]использует Annotation data type - fhir-composition — Composition.section.entry ссылается на Observations (supporting biomarkers)
- fhir-careplan —
activity.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
Источники
Сноски
-
HL7 R4 spec, accessed 2026-05-17, https://hl7.org/fhir/R4/observation.html. ↩
-
v3-ObservationInterpretation ValueSet, accessed 2026-05-17, http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation. ↩
-
Реализация, accessed 2026-05-17, https://github.com/Realai-plus/bloodgpt-for-business/blob/main/packages/analysis-core/src/lib/save-fhir-resources.ts. ↩
-
Сессия
ildar/871a7608, 2026-02-13 — (extensions reversal в standard fields). ↩