Наш алгоритм маппинга raw lab parameters в LOINC-коды. См. сам стандарт — loinc. Pipeline — наша реализация.
Контекст
Лаборатории шлют параметры в произвольных названиях/единицах/языках ("Глюкоза, ммоль/л, кровь", "Tsd/µl", "в п/зр"). Чтобы считать тренды и кодировать в FHIR Observation — нужен маппинг в loinc code. Маппинг детерминированный — один input → один code; однажды решённый — навсегда (см. dictionary-first-paradigm про превращение в управляемый справочник).
Алгоритм (3-step)
Input parameter (lab-specific name + units + material)
│
▼
[1] KeywordGenerator (LLM call)
│ Узкий промпт + chain_of_thought ("реакция in urine context? → pH")
│ Output: 3-7 keyword candidates
│
▼
[2] Search (deterministic)
│ Custom inverted index (BM25/TF-IDF) over LOINC catalog
│ Specimen filter (нормализация input material через blood/urine/etc family)
│ Property filter (units → SCnc/MCnc dimension)
│ Output: top-30 candidates ranked by score
│
▼
[3] Selector (LLM call)
│ Узкий промпт (~392 строки): chain-of-thought, examples, Method rules
│ Tool calling: select_loinc / no_match
│ Output: chosen LOINC code or no_match
│
▼
Decision (deterministic)
│ ConfidenceScorer — 6 checks
│ Grade A-D + action AUTO_ACCEPT / MANUAL_REVIEW / REJECTED
Дизайн-выбор “3 узких промпта вместо single agent” зафиксирован в agent-vs-workflow (граница: open-ended → agent; structured → decomposed).
Иерархия путей при lookup
В целевой архитектуре (dictionary-first-paradigm) lookup проходит несколько уровней по убыванию скорости / детерминированности:
- Dictionary lookup (path-zero) — O(1) hit в Redis по канон-ключу. Маппинг найден ранее, верифицирован → возврат сразу. Это main path в зрелой системе — большинство параметров уже видели.
- Pipeline (3-step) — если в Dictionary нет: keyword → search → select. Записать результат в Dictionary.
- Agent Fallback — safety net для случаев когда [3] вернул
no_matchпосле Selector Cascade.
Чем больше в Dictionary — тем реже срабатывает Pipeline; ещё реже — Agent Fallback. См. dictionary-creation про proactive Dictionary-creation (новая лаба → запросить их parameter list заранее).
Agent Fallback (safety net)
Срабатывает когда [3] не нашёл подходящий LOINC. Это agentic loop:
- 4 tools:
search_loinc/get_loinc/find_methodless_sibling/propose_resolution(terminal) - Max 10 iterations, timeout 30s/call, общий ~120s
- Дедупликация tool-вызовов, budget messages, forced resolution на финальной итерации
- Hallucination check: после
propose_resolutionкод верифицируется черезget_loinc - Provider fallback (Gemini Flash → OpenAI и обратно)
Никогда не выдаёт AUTO_ACCEPT — результаты всегда идут на manual review (grade B-C).
Это safety net, не основной путь. См. agent-fallback-role — обсуждение нужен ли он в зрелой Dictionary-First системе.
Production model config
Cascade fallback:
Keywords: Gemini Flash → GPT-5.2 → Gemini Flash → GPT-5.2
Selection: Gemini Pro → GPT-5.2 → Gemini Pro → GPT-5.2
Цепочка из 4 — это retry-chain: первая попытка Gemini, при ошибке (rate limit, timeout, malformed output) — fallback на GPT-5.2; если и тот fail — повтор Gemini (часто rate limit к этому моменту проходит); если и тот — снова GPT-5.2 как last resort.
Gemini Pro для selection — он более детерминистичен и лучше следует tool-calling instructions, чем GPT-5.2 (на нашем узком промпте). Это про specialization промптов под конкретную модель — узкий instruction увеличивает шансы на качественное выполнение независимо от “силы” модели в общем смысле.
Decision cache → Dictionary
dm:{hash} в Redis сохраняет полный результат маппинга для известных biomarkers — основной вклад в стабильность production benchmark vs локальные runs (16-18/20 vs 14-16/20). Keyword cache (раздельный) почти пуст — не основной фактор.
Артур убрал TTL → это уже не cache, а persistent dictionary layer. Конкретный shape ключа — открытый вопрос (dictionary-first-paradigm open questions).
Specimen families pattern
normalizeInputMaterial("bld") → "blood" → expansion в blood family (Bld, Ser/Plas, BldA, etc). Без нормализации specimen filter не находил hgh (somatotropin, system=Ser/Plas) при input "bld". Эта нормализация — критична для recall, легко забывается при портировании.
Method-only signature правило (Rule 5)
“MAP ONLY what you receive. If input does NOT specify a method → MUST select the candidate WITHOUT method.”
Если входные данные не уточняют метод — выбираем 26484-6 (monocytes, без method), а не 742-7 (manual count). Для production, где маппинг сохраняется навсегда, overspecification создаёт проблемы с трендами (один тест → разные коды).
Где живёт сейчас и куда едем
| Реализация | Где | Status |
|---|---|---|
| Python production | loinc-harmonization-service | Source of truth на 2026-04-26, постепенный phase-out |
| Mastra TS port | bloodgpt-for-business/apps/analysis-worker/src/mastra/ | Scoring/search parity достигнут (post-82806132); production target |
Direction: полный TS-порт → Mastra-port становится production. См. loinc-unification-direction.
Состояние Mastra-порта
Scoring и search parity с production Python достигнут. Pipeline в TS даёт корректный код в candidates с правильным ranking. Остающиеся drift — на уровне LLM analyte confusion:
- albumin → LLM выбирает Microalbumin (14957-5) вместо Albumin (1754-1)
- kreatin → LLM выбирает Creatine (15045-8) вместо Creatinine (77140-2)
Это уже не scoring issue (оба в candidates с правильным rank), а prompt engineering / similar_resolved hints — Dictionary с уже разрешёнными случаями даёт LLM warm-up. См. dictionary-first-paradigm.
Carry-over: lessons из портажа (compound keyword expansion, UCUM dimensionality bonus, system broadening, zod stripping в Mastra и т.п.) — отдельно записать в
meta/уроки.mdкак portage-patterns. Здесь — состояние и direction.
Открытые вопросы
- LOINC analyte disambiguation (albumin / Microalbumin, Creatine / Creatinine) — prompt engineering или Dictionary
similar_resolvedhints similar_resolvedRedis backend для cold-start gap (production имеет, TS-port — нужно verify)
Связанные решения
- agent-vs-workflow — design philosophy “узкие промпты vs single agent” — active
- loinc-unification-direction — TS-port + service unification — active
- dictionary-first-paradigm — Dictionary как продукт, не cache — proposed
- agent-fallback-role — нужен ли agent fallback в зрелой Dictionary-First системе — open
Связано
- loinc — стандарт
- loinc-harmonization-service — production host (Python)
- normalization-service — старая нормализация (parallel путь)
- dictionary-creation — proactive создание Dictionary при онбординге новой лабы
- mastra — execution engine для TS-порта