LLM-промпт который анализирует один лабораторный параметр, выбирает reference range “точно как лаборатория задала”, и маппит value в FHIR R4 value[x] structure с SNOMED CT coding для qualitative значений. Это core LLM-step в V2 parameter analysis pipeline.

  • Имя в Langfuse: parameter_range_type
  • Production label: b2b-production → version v10 (на 2026-04-26)
  • Test label: latest или другие → version v11 (для tests, не deployed)
  • Type: chat prompt
  • Strict mode: true (JSON schema validation, additionalProperties: false)
  • Connected ticket: BG-1207 (LLM-driven interpretation для всех типов range)

Output schema (9 required fields)

Все 9 fields mandatory:

{
  thinking_process: string,           // Mandatory CoT
  parameter_type: enum,               // FHIR value[x] tag
  fhir_value: oneOf<...>,             // Typed FHIR value
  range_type: enum,                   // Range shape classification
  has_range: boolean,                 // true for quantitative
  reference_range: { min, max },      // Numbers (0/0 если has_range=false)
  range_explanation: string,          // Why this range
  interpretation_reasoning: string,   // Why this interpretation
  interpretation: enum                // Single-letter FHIR code
}

parameter_type enum (8 values, FHIR R4 value[x] + dataAbsentReason)

ValueКогдаhas_range
valueQuantitynumeric с unit, comparator (< 0.8, > 100, decimals, pH, INR)true
valueIntegerinteger count без unittrue
valueRangenumeric range как value (3-5, 0-1)true
valueRatiotiter (1:160)true
valueCodeableConceptqualitative (Negative/Positive/Detected, ordinal +++, categories)false
valueBooleanstrict binary (rare)false
valueStringfree-text descriptionsfalse
dataAbsentReasontest not performed, missing datafalse

range_type enum (4 values — actual!)

ValueКогда
EMPTY_OR_MISSINGreference range absent или fallback на medical standards
LOWER_BOUND_ONLYinput был > X (e.g., HDL: ”> 40 desirable”)
UPPER_BOUND_ONLYinput был < Y (e.g., ratio: ”< 5.0”)
BOTH_BOUNDSстандартный range или derived (e.g., 3.5-5.5)

(Calibration: ранее в digest 18b47185 я ошибочно описал как BOUNDED/UPPER_BOUND_ONLY/LOWER_BOUND_ONLY/UNBOUNDED. Реально EMPTY_OR_MISSING и BOTH_BOUNDS, не UNBOUNDED/BOUNDED.)

interpretation enum (FHIR Observation.interpretation code)

["N", "H", "L", "A"] — соответствует HL7 v3-ObservationInterpretation system (http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation):

  • N — Normal
  • H — High
  • L — Low
  • A — Abnormal (для non-numeric / qualitative findings)

HH/LL (critically high/low) не используются — out of scope.

Algorithmic flow внутри промпта (3 шага)

STEP 1 — Parameter Classification (FHIR value[x])

Hardcoded rules:

  1. Semantic Override — если input имеет explicit type tag, trust it.
  2. “0 vs Negative” Rule — value=0, range=qualitative (“Negative”) → force valueCodeableConcept, has_range: false.
  3. Edge cases:
    • Qualitative value + unit → ignore unit, → valueCodeableConcept
    • Mixed ordinal+number (”+++ 31”) → ordinal wins
    • Interpretation markers (, H, (High)) с числом → extract число only, → valueQuantity
    • “Слабо-мутная” vs “Прозрачная” → valueCodeableConcept (implies scale)
    • “Not performed” / “Cancelled” / “no material” / “N/A” → dataAbsentReason
  4. Pattern table — 8 input patterns → FHIR types

STEP 2 — Range Extraction (только для has_range: true types)

Priority A — Lab Provided Range (Golden Path):

  • A1: Single numeric (3.5-5.5) — extract.
  • A2: One-sided open-ended:
    • Input < 5.0range_type: UPPER_BOUND_ONLY, min: null, max: 5.0
    • Input > 50.0range_type: LOWER_BOUND_ONLY, min: 50.0, max: null
    • CRITICAL — DO NOT SYNTHESIZE: absent bound = null. Не fabricate “reasonable max/min”. Это reader honesty principle hardcoded в промпте: “Inventing bounds corrupts downstream clinical judgement (e.g., flagging HDL=85 as ‘too high’ because a fake max=100 was invented).”
  • A3: Multi-tier (selection logic):
    • “Male: 10-20; Female: 8-16; Child: 5-10” — выбрать tier по [User_Context]
    • Age Gap Handling: если ни один tier не покрывает age — find closest tier; если gap ≤ 5 years, MUST use closest (не fallback на medical standard); если > 5 years → Priority B.
  • A4: Risk-tiered (safety net):
    • “Diabetes: < 130” — match только если user явно “diabetic”
    • Safety net: если все tiers только disease-specific (нет “Normal”) → fallback B (don’t apply disease range to healthy user)

Priority B — Fallback Generation:

Если range_text empty, или Safety Net triggered, или нет matching age/gender tier (gap > 5 years):

  • Calculate normal range based on General Medical Standards для age + gender
  • CRITICAL: не возвращать has_range: false для quantitative параметров с established medical reference ranges (PSA, FSH, LH, FT4, Testosterone, Estradiol etc).

Labeled threshold directionality:

  • > X (Normal/Optimal/Low Risk) → higher better → LOWER_BOUND_ONLY
  • < Y (Normal/Optimal) → lower better → UPPER_BOUND_ONLY
  • > Z (High Risk) → higher bad → UPPER_BOUND_ONLY (max=Z)

STEP 3 — FHIR Value Mapping + SNOMED CT Coding

Hardcoded SNOMED CT mapping таблицы внутри промпта:

Presence/Absence: Negative=260385009, Positive=10828004, Not detected=260415000, Detected=260373001, Absent=272519000, Present=52101004, Normal=17621005, Abnormal=263654008

Plus Scale: Trace=260405006, +=260347006, ++=260348001, +++=260349009, ++++=260350009

Quantitative Terms: Few=57176003, Moderate=6736007, Many=260396001

dataAbsentReason (HL7 codes): not-performed, error, not-applicable, as-text, unknown, temp-unknown

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

Output → FHIR write pipeline

fhir-resource-creation.function.ts использует output:

interpretationCode: param.interpretation, // LLM single-letter code from parameter_range_type (BG-1207)

fhir_value напрямую идёт в Observation.value[x]:

  • valueQuantityObservation.valueQuantity
  • valueRangeObservation.valueRange
  • valueCodeableConceptObservation.valueCodeableConcept (с SNOMED coding)
  • etc.

reference_range.min/maxObservation.referenceRange[].low/high.

range_type хранится для UI rendering decisions (см. fhir-observation про reader honesty + UI scale bar).

Two-tier interpretation source (BG-1207 + BG-1258)

См. fhir-observation. Lab-provided seed выигрывает над LLM output если присутствует. LLM (этот промпт) — fallback при отсутствии lab seed.

Эволюция (v1 → v10)

10 production-deployed versions + v11 (test). Конкретные rationale изменений между версиями — не доступны через Langfuse API (commit messages не записываются в промпт versions). История доступна только через Claude Code session digests где промпт обсуждался и менялся.

Известные milestones (из ingested сессий):

  • v? → Range inversion + tests (PR #252, BG-1250) — reader honesty principle hardcoded
  • v? → v10 (BG-1207, sessions 731a3f60/bb193045/etc): LLM-driven interpretation для всех range types — UPPER_BOUND_ONLY ratio параметров (cholesterol-to-HDL) направляются correctly (раньше галлюцинировал isHealthy=false)

Полный history эволюции — TBD ingest специальной сессии или langfuse:compare для interesting transitions.

Open / known issues

  • Hallucinations на UPPER_BOUND_ONLY ratio — early versions путали direction (TOTAL CHOL/HDL RATIO 1.71 показывался abnormal). v10 fix через explicit “labeled threshold directionality” rules в промпте.
  • Age gap > 5 years — fallback на medical standard, не на closest tier. Open для review (правильно ли это для конкретных биомаркеров).
  • Безопасность дозы Disease-only ranges — safety net срабатывает, но open вопрос whether disease-only ranges имеют clinical meaning для healthy users в general (e.g., “Diabetes target HbA1c < 7.0” — должен ли применяться к non-diabetic?).
  • thinking_process field is mandatory CoT — токен-cost trade-off vs reasoning quality. Пока strict=true и required.
  • v11 для тестов — не deployed. Что в нём отличается от v10 — нужен langfuse:compare 10 11.

Связано

  • fhir-observation — потребитель interpretation + fhir_value output
  • zero-extensions-fhir — почему standard FHIR interpretation ValueSet, не custom
  • mastra — execution engine (через AI SDK / Mastra agent)
  • bifrost — proxy через который промпт исполняется
  • agent-vs-workflow — promp 3-step pattern (этот — один из шагов)
  • BG-1207 (LLM-driven interpretation для всех типов)
  • BG-1250 (range inversion + reader honesty fix, PR #252)
  • BG-1258 (seed path for lab-provided interpretation)

Источники

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

Сноски

  1. Сессия ildar/731a3f60, 2026-04-21 — ` (BG-1207 wiring).

  2. Сессия ildar/bb193045, 2026-04-22 — ` (BG-1250 reader honesty).

  3. Сессия ildar/dd17b3fd, 2026-04-22 — /7cc1d514` (V2 parity work).

  4. Сессия ildar/7cc1d514, 2026-04-23 — ` (V2 parity work).