Контекст

Блок «Main now» в Health Report (Phase 3 Highlights Insight) не может показать 20-50 абнормальных биомаркеров — это убивает смысл «main». Из всего набора отклонений нужно выбрать то подмножество, на которое опытный врач посмотрел бы первым. Решение должно быть воспроизводимым, прослеживаемым, дешёвым (без LLM в момент принятия решения) и калибруемым через врача-в-цикле.

Решение фиксирует какие сигналы участвуют, как они комбинируются, что значат исходы и какие альтернативы отвергнуты. Алгоритм закодирован в отдельном GitHub-репозитории main-biomarkers-detection (Артур, 2026-05-11 v0.1 1).

Решение не про диагностику и не про степень тяжести. Это про приоритет показа — кому показывать первым в My Health / Health Report.

Что нужно от ответа на выходе:

  • Бинарная метка на каждый биомаркер: main либо not_main, без третьей корзины.
  • Детерминированный источник срабатывания: либо граф (biomarker-graph), либо patient FHIR через Ретривер, либо предыдущие измерения. Никаких порогов «из головы», кроме одного откалиброванного параметра.
  • Аудит-след на каждую классификацию: какой сигнал сработал, цитаты (обоснование / quotes / source из графа), версии prompt’a / правил / графа.
  • Один и тот же вердикт для пациента и врача: распределение по аудиториям — работа downstream-writer’ов, не sieve.
  • Полностью воспроизводимый результат для одного и того же входа и версий: без LLM в момент принятия решения, без случайной выборки.

Рассматривали

Восемь сигналов-кандидатов: три из них вошли в выбранную комбинацию через OR; пять отвергнуты как самостоятельные сигналы.

S1 — фенотип-кластер (вошёл в комбинацию)

Биомаркер abnormal И ≥ N = 2 его relatedParameters-сиблингов (из графа, предварительно резолвнутых Ретривером) тоже abnormal в bundle’е. Мысленная модель — «синдром не пара, а кластер»: три сходящихся находки начинают рассказывать историю; два — низший защищаемый минимум, прежде чем шум забивает сигнал.

Плюсы:

  • Захватывает синдромную структуру: даже без контекста пациента видно, что отклонения сходятся на одной системе.
  • Источник цитат — граф; на каждый сработавший сигнал есть quotes / source для аудита.
  • Калибровка через одну рукоятку (N).

Минусы:

  • Зависит от полноты relatedParameters в графе; редкий или необычно сгруппированный синдром может промахнуться.
  • N = 2 стартовая гипотеза, не доказанная — нужна валидация через цикл с врачом на ~50 doctor-reviewed отчётов.

S2 — patient-context match (вошёл в комбинацию)

Биомаркер abnormal И Ретривер вернул ≥ 1 typed match против patient FHIR — реальный диагноз/состояние из matchedConditions либо лекарство из matchedMedications. Чистый поиск, без LLM. Каждое совпадение несёт quotes + source из графа через Ретривера.

Плюсы:

  • Учитывает индивидуальный контекст: отклонение, связанное с конкретным активным диагнозом или лекарством пациента, выходит вперёд.
  • Аудит-след прозрачен — typed match даёт citations.
  • Дёшево, детерминистично, граф уже несёт связи.

Минусы:

  • Зависит от полноты relatedConditions / relatedMedications.
  • Не различает «condition в активной фазе» и «condition в анамнезе» — это делает FHIR на стороне Ретривера, а не sieve.

S3 — сигнал тренда (вошёл в комбинацию)

Биомаркер abnormal И один из паттернов по предыдущим измерениям: первый раз abnormal / усугубление / разворот направления. Стабильно хроническое и улучшающееся не продвигают (пациент и врач знают; уже под контролем).

Плюсы:

  • Захватывает новизну и развитие: первый раз abnormal или усугубление — это сигнал, который кластер и контекст пропустят.
  • Сравнение между лабами через структурно-позиционную нормализацию, см. biomarker-observations-comparability.

Минусы:

  • Требует подачи предыдущих измерений, которые Phase 1 health-report-pipeline сейчас отбрасывает (open).
  • Веса паттернов (какой из трёх privilege’нее) — эмпирический вопрос для цикла с врачом.

Q1 — список панических значений / критических порогов (отвергнуто)

Ручной список «критических» значений на каждый биомаркер — если последнее измерение за порогом, биомаркер автоматически main.

Плюсы:

  • Определённость по абсолютным порогам.
  • Интуитивно безопасный.

Минусы:

  • Бремя сопровождения — пороги дрейфуют по гайдлайнам, нужен врач-в-цикле на каждое обновление.
  • Дублирует интерпретацию, которую уже даёт refRange.
  • Не учитывает контекст пациента — пациент с компенсированным хроническим состоянием получает ложный «main» при каждом тесте.

Почему отвергнуто: сопровождение не масштабируется без постоянного ритма работы с врачом; граф биомаркеров уже несёт ту же информацию через related_conditions, обновляется централизованно. SPEC § 3 фиксирует отказ 2026-05-12; docs/doctors/q1_panic_values.md остался замороженным артефактом, не потребляется.

Q2 — бинарная привязка к условию (заменено на S2)

«Если в FHIR пациента есть любое из условий, связанных с биомаркером в графе → main».

Плюсы:

  • Дёшево, детерминистично.
  • Граф уже несёт связи.

Минусы:

  • Теряет аудит-след на каждое совпадение: quotes / source / rationale из графа не доходят до выхода.
  • Не различает «condition в активной фазе» и «condition в анамнезе».

Почему отвергнуто: строго перекрывается S2 — та же бинарная проверка плюс метка типа совпадения (condition / medication / age-shifted / pregnancy-shifted) и цитаты в trace. Никаких регрессий, больше функциональности.

Q3 — объяснимость через контекст лекарств / диагнозов (отвергнуто)

«Если abnormal-биомаркер можно объяснить активным лекарством или диагнозом → понизить приоритет (не main)».

Плюсы:

  • Интуитивно — «у пациента низкий калий, но он на диуретике, это известный механизм, не критично».

Минусы:

  • Это диагностика. Чтобы честно решить «объяснимо или нет», нужно весить вклад каждой активной причины, оценивать компенсацию, ранжировать причины.
  • Работа врача с историей, не функция-поиск.

Почему отвергнуто: разрастание области в диагностику. Удалено 2026-05-08 явно в SPEC § 3. Доктор делает интерпретацию вниз по потоку — наш модуль ему говорит «вот биомаркер X, и у пациента есть причина Y, которая связана с ним по графу» (это S2). Врач сам решает, объяснён ли он.

Reflex / follow-up логика (отвергнуто)

«Биомаркер, требующий дополнительного теста для интерпретации → main, потому что заслуживает внимания».

Плюсы:

  • Ловит «ну, тут надо пересдать / уточнить».

Минусы:

  • Биомаркер, требующий доп-теста, менее информативен на одном замере, не более.
  • «Main now» — про текущее состояние; «надо пересдать» — про следующий цикл измерений.

Почему отвергнуто: воспроизводит проблему списка панических значений — список нужно поддерживать отдельно от графа. И смысл другой: reflex-флаг — это «уверенность в этом значении ниже», а main — «внимание выше». Reflex нужен, но это отдельный workstream, не сигнал для main.

Числовые шкалы тяжести (отвергнуто)

«Назначим биомаркеру числовой балл (severity / urgency / clinical_weight), top-K = main».

Плюсы:

  • Непрерывная шкала, удобно для ранжирования.
  • Можно сразу тюнить.

Минусы:

  • Субъективность спрятана в весах, нечитаема для читателя аудит-логов.
  • Без калибровки врачом веса — шум.
  • Порог для top-K произволен и зависит от того, сколько биомаркеров было abnormal.

Почему отвергнуто: бинарный main / not_main плюс аудит-след даёт тот же приоритет вниз по потоку без ложной объективности. Если когда-нибудь понадобится ранжирование внутри main (urgent vs routine) — это работа downstream-writer’a.

LOINC-panel pattern matching как основной сигнал (отвергнуто)

«Если abnormal-биомаркеры группируются в известный panel (lipid panel, basic metabolic panel) → весь panel = main».

Плюсы:

  • На стандартах, LOINC уже поддерживает panel definitions.

Минусы:

  • Panel-определения часто не совпадают с тем, как реальный лаб режет тесты.
  • Нестандартные panel’и от регионального лаба не матчатся.
  • Биомаркеры внутри одного panel могут иметь совершенно разные основания для main (Hb низкий vs MCV высокий — разные паттерны, в одном CBC panel’е).

Почему отвергнуто: S1 (фенотип-кластер через relatedParameters из графа) делает то же самое, но точнее — сиблинги определяются клинически, не лабораторной группировкой. Panel definitions могут быть подсказкой для нормализации, не сигналом для main.

Третья корзина — «Possible error / Pay attention» (отвергнуто)

«Добавить третью метку для unit / preanalytic / sanity-check fails».

Плюсы:

  • Даёт UI явное место для «не уверены».

Минусы:

  • Засоряет пространство меток.
  • Смешивает «биомаркер не main» (медицинский вердикт) и «не можем классифицировать» (проблема качества данных).

Почему отвергнуто: sanity-fails эмитим как not_main + явный reason_code в trace (sanity:missing-ref-range / sanity:latest-normal). Аудит-лог видит проблему качества данных отдельно от медицинского решения; пользовательская метка остаётся бинарной.

Выбрали — комбинация S1 + S2 + S3 через OR поверх двух sanity-проверок

Биомаркер main если:

  1. Последнее измерение abnormal (не sanity:latest-normal), И refRange присутствует (не sanity:missing-ref-range), И
  2. Сработал хотя бы один из трёх сигналов: S1 (фенотип-кластер), S2 (patient-context match), S3 (тренд).

OR-комбинация: код причины в trace фиксирует какой именно сигнал сработал (один или несколько); цитаты из графа протаскиваются. N = 2 для S1 — единственный калибруемый параметр.

За

  • Три независимых основания для «main». Кластер (синдром не пара), контекст (реальная активная причина в картине пациента), тренд (новизна или развитие). AND-комбинация over-filter’ит случаи, где врач реально посмотрел бы; OR — слабее, но это правильная слабость.
  • Все границы — refRange или цитаты из графа. Никаких порогов «из головы», кроме калибруемой N=2.
  • Sanity отдельно от вердикта. Проблемы качества данных уходят в reason_code в trace, не в отдельную метку. Пространство меток не засоряется data-quality-сигналами.
  • Без LLM в момент принятия решения. Sieve поверх retrieved-блока — чистая функция; LLM остаётся в upstream-агентах (Диагностик / Ретривер), которые готовят retrieved.

Следствия

  • Подача предыдущих измерений для S3. health-report-pipeline Phase 1 сейчас делает latest-per-biomarker свёртку и отбрасывает предыдущие. Для рабочего S3 нужно либо подгружать их параллельно, либо переделать свёртку — open-вопрос интеграции, см. main-biomarkers-detection § Открытые вопросы.
  • Сравнимость предыдущих измерений между лабами. Структурно-позиционная нормализация (value − upper_ref) / (upper_ref − lower_ref) — отдельная decision-страница biomarker-observations-comparability.
  • Ритм калибровки привязан к врачу-в-цикле. N=2 ждёт ~50 doctor-reviewed отчётов от Кати через общий рабочий цикл. До этого fixture-driven (38 synthetic patient bundles в репозитории Артура).
  • Каталог sanity reason codes — отдельный. Помимо sanity:missing-ref-range / sanity:latest-normal со временем понадобятся unit / preanalytic / unknown-biomarker reason codes. Каталог пока не финализирован (SPEC § 6 open-вопрос).
  • Конвертер Retriever → classifier input на границе. Наш Ретривер выдаёт relatedFound* / relatedMissing* на каждый биомаркер; classifier ждёт retrievedBlockSchema со своими именами полей. Конвертер в Phase 1 — open-вопрос реализации, см. main-biomarkers-detection.
  • Один и тот же вердикт для пациента и врача — не пересматриваем. Распределение по аудиториям идёт вниз по потоку, в writer’ах health-facts-as-generation-substrate.

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

  • Калибровка N=2. Стартовая гипотеза. Валидация на ~50 doctor-reviewed отчётов через цикл с врачом; ждёт полноты графа. Если over-flags → 3; если under-flags → 1.
  • Веса паттернов S3. Какие паттерны (первый раз abnormal / усугубление / разворот) врачи реально выделяют на практике — эмпирический вопрос для рабочего цикла с врачом, не SPEC.
  • Граничные случаи формулы сравнимости между лабами. Одностороннее refRange (только upper для troponin), pediatric (детские) диапазоны, pregnancy-shifted — biomarker-observations-comparability § Open.
  • Каталог sanity reason codes — unit / preanalytic / unknown-biomarker reason codes не финализированы.
  • Pediatric / pregnancy охват. v0.1 adult-only. Расширение — отдельное развёртывание, требует pediatric диапазонов в графе и pediatric-specific S2-записей.

Связано

  • main-biomarkers-detection — concept-страница модуля: контракт classify(), поток управления, что НЕ делает, состояние интеграции в Health Report Pipeline
  • biomarker-observations-comparability — почему предыдущие измерения сравниваются через (value − upper) / (upper − lower), а не через конвертацию единиц; альтернативы и компромиссы
  • health-report-pipeline — Phase 3 Highlights Insight (куда подаётся main-подмножество) и Phase 1 свёртка (откуда уходят предыдущие измерения)
  • medical-expert-loop — общий механизм работы с врачом, через который идёт калибровка N и весов паттернов S3
  • Ретривер — источник retrieved-блока на каждое последнее измерение
  • biomarker-graph — single source of truth для relatedParameters / relatedConditions / relatedMedications
  • Диагностик — собирает diagnostic plan; верх по цепочке от Ретривера
  • biomarker-actuality-service — родственный детерминистический-поверх-LLM-фактов сервис; другой вопрос («можно ли опираться на это значение сейчас» vs «main-биомаркер для Highlights»)
  • health-facts-as-generation-substrate — Variant D Reasoner/Writer split; вниз по потоку от sieve, audience-specific формулировки
  • health-report-vocabulary — словарь Snapshot / Insights / Biomarker Analysis

Сноски

  1. SPEC v0.1 (draft 2026-05-11), docs/SPEC.md, accessed 2026-05-19. https://github.com/Realai-plus/main-biomarkers-detection/blob/main/docs/SPEC.md