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→ versionv10(на 2026-04-26) - Test label:
latestили другие → versionv11(для 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 |
|---|---|---|
valueQuantity | numeric с unit, comparator (< 0.8, > 100, decimals, pH, INR) | true |
valueInteger | integer count без unit | true |
valueRange | numeric range как value (3-5, 0-1) | true |
valueRatio | titer (1:160) | true |
valueCodeableConcept | qualitative (Negative/Positive/Detected, ordinal +++, categories) | false |
valueBoolean | strict binary (rare) | false |
valueString | free-text descriptions | false |
dataAbsentReason | test not performed, missing data | false |
range_type enum (4 values — actual!)
| Value | Когда |
|---|---|
EMPTY_OR_MISSING | reference range absent или fallback на medical standards |
LOWER_BOUND_ONLY | input был > X (e.g., HDL: ”> 40 desirable”) |
UPPER_BOUND_ONLY | input был < 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:
- Semantic Override — если input имеет explicit type tag, trust it.
- “0 vs Negative” Rule — value=0, range=qualitative (“Negative”) → force
valueCodeableConcept,has_range: false. - 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
- Qualitative value + unit → ignore unit, →
- 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.0→range_type: UPPER_BOUND_ONLY,min: null, max: 5.0 - Input
> 50.0→range_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).”
- Input
- 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.
- “Male: 10-20; Female: 8-16; Child: 5-10” — выбрать tier по
- 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]:
valueQuantity→Observation.valueQuantityvalueRange→Observation.valueRangevalueCodeableConcept→Observation.valueCodeableConcept(с SNOMED coding)- etc.
reference_range.min/max → Observation.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_processfield is mandatory CoT — токен-cost trade-off vs reasoning quality. Пока strict=true и required.- v11 для тестов — не deployed. Что в нём отличается от v10 — нужен
langfuse:compare 10 11.
Связано
- fhir-observation — потребитель
interpretation+fhir_valueoutput - zero-extensions-fhir — почему standard FHIR
interpretationValueSet, не 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)