Контекст

Один и тот же биомаркер у пациента может быть сдан в разных лабах: разные единицы измерения, разные методы, разные reference ranges (RR) — даже когда единицы совпадают. Чтобы сигнал тренда в main-biomarkers-detection (усугубление / улучшение / первый раз abnormal / разворот направления) считался корректно, нужно сравнить последнее измерение с предыдущими — а абсолютные значения не сравнимы между лабами без явного приведения.

Простой пример: HbA1c 7.0% по NGSP-методу и HbA1c 53 mmol/mol по IFCC-методу — одно и то же. ALT 35 U/L при upper_ref 40 и ALT 35 U/L при upper_ref 55 — разная клиническая позиция при идентичной единице и значении. Это решение фиксирует как мы приводим эти измерения к сравнимому виду для целей анализа тренда.

Охват — расчёт тренда внутри сигнала S3. Конкретно нужна функция, которая для пары измерений «то, что мы классифицируем сейчас + одно предыдущее» возвращает «дальше или ближе пациент к границе своего лабораторного коридора, чем раньше». Производные метрики — направление и величина тренда.

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

  • Не зависит от знания конвертаций единиц. Каталог ucumvert / fhir.unit не полный по всем биомаркерам; зависимость от него ломает классификатор на любом нестандартном маркере. Carry-over: собственный каталог конвертаций единиц может пригодиться отдельно — для UI-отображения и для panel-группировки; отдельная страница unit-conversion-catalog (не создана).
  • Не предполагает клинического смысла у промежуточных значений. Утверждение не про «порог X означает Y», а только про «структурная позиция в коридоре каждого лаба сместилась туда-то».
  • Корректно пропускает односторонние диапазоны (только upper или только lower) — это норма для troponin, BNP, ESR и многих tumor markers.
  • Симметрично для high и low отклонений — формула не должна выделять одно направление.
  • Полностью воспроизводимо для одного и того же входа: без случайной выборки, без LLM, без зависимости от внешнего состояния.

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

Шесть подходов: структурно-позиционная формула вошла в принятое решение; пять отвергнуты.

Структурно-позиционная формула (выбрано)

Для high-отклонения:

position = (value − upper_ref) / (upper_ref − lower_ref)

Симметрично для low ((lower_ref − value) / (upper_ref − lower_ref)). Если position > 0 — за upper-границей; если < 0 — внутри коридора. Сравнение position_latest vs position_prior даёт усугубление (увеличилась) / улучшение (уменьшилась) / разворот (сменила знак).

Плюсы:

  • Математически осмысленно при разных RR.
  • Не требует знания конвертации единиц: RR в тех же единицах, что и value, — лаб всегда выдаёт согласованно.
  • Работает при разных популяциях / методах, потому что RR — это собственная линейка лаба.
  • Симметрично для high и low отклонений.
  • Проверяемо: формула explicit, каждый шаг воспроизводим.

Минусы:

  • Предполагает, что обе границы присутствуют.
  • Односторонние диапазоны нужно обрабатывать отдельно (см. ниже в «Выбрали»).
  • Не несёт абсолютного клинического смысла — только относительный.
  • Числовая шкала не интуитивна (position = 1.5 сама по себе не говорит «как плохо»).

Конвертация единиц (отвергнуто)

Привести все измерения к каноническим единицам (SI / UCUM) через движок конвертации (например ucumvert или встроенный FHIR convert), потом сравнивать «сырые» значения.

Плюсы:

  • Интуитивно — «привели всё в mmol/L, потом сравниваем».
  • Числовая шкала сохраняет клиническую интуицию.

Минусы:

  • Одинаковая единица ≠ сравнимое значение. Два лаба могут оба выдавать ALT в U/L, но при разных методах (IFCC vs Henry) один реально читается на 30% выше другого. Конвертация единиц это не лечит.
  • Один и тот же метод ≠ один и тот же reference range. Разные популяции (возраст, этнос, регион), разные поколения опубликованных нормограмм — для одной единицы у двух лабов RR могут отличаться на 20-40%.
  • RR — единственная надёжная линейка лаба. Сам лаб сравнивает свои собственные измерения со своим refRange — это и есть сравнимая шкала.
  • Покрытие. Каталоги конвертации (UCUM-based) покрывают распространённые единицы, но нестандартные единицы лабов СНГ / Восточной Европы / regional labs часто отсутствуют. Зависимость от внешнего каталога добавляет точку отказа.

Почему отвергнуто: конвертация единиц остаётся полезной для отображения (показать пациенту HbA1c в обоих единицах) и для группировки в panel’и, но не как основной метод сравнения для тренда. Может пригодиться как вторичная проверка (если позиции противоречат) — но это усложнение для будущего, не для v0.1.

Сравнение без нормализации (отвергнуто)

Просто сравнить value_latest против value_prior, без приведения.

Плюсы:

  • Нулевая сложность, нулевые зависимости.

Минусы:

  • Работает только когда оба измерения в одной и той же единице И с одинаковым RR.
  • Этого нельзя предполагать даже у одного и того же лаба между годами (RR обновляются); между лабами — почти никогда.

Почему отвергнуто: молча выдаёт неверные вердикты тренда в случаях, которые невозможно отличить от валидных во время исполнения. Это скрытая точка отказа — хуже чем явный пропуск, когда предыдущих нет.

z-score / SD-нормализация (отвергнуто)

z = (value − mean) / SD, где mean и SD — population statistics для этого биомаркера в этом лабе.

Плюсы:

  • Статистически правильно для нормально-распределённых биомаркеров.

Минусы:

  • Требует опубликованных population statistics, которые большинство лабов не выдают — только bounds.
  • Mean/SD можно reverse-engineer из bounds в предположении нормальности (mean = (upper+lower)/2, SD = (upper−lower)/4), но это assumes normality, а многие биомаркеры распределены skewed (ferritin, CRP, troponin) — для них z-score теряет смысл.
  • Дополнительная зависимость от стат-таблиц или от assumption о форме distribution.

Почему отвергнуто: нулевое практическое покрытие в общем случае. Если делать reverse-engineer из bounds — это становится hidden assumption нормальности и для skewed маркеров даёт неправильный verdict. Если требовать population statistics от лаба — лаб их не публикует.

Percentile rank в reference range (отвергнуто)

Перевести value в percentile внутри reference distribution (если она доступна).

Плюсы:

  • Интуитивно — «где значение в фактической форме лаб-distribution».
  • Меньше предположений чем z-score (не требует нормальности).

Минусы:

  • Требует данные distribution, не только bounds.
  • Та же проблема покрытия что и у z-score.

Почему отвергнуто: та же — лаб не выдаёт distribution.

Quantile mapping между лабами (отвергнуто)

Построить отображение lab_A_distribution → lab_B_distribution через большой калибровочный набор (или public Bayesian posterior).

Плюсы:

  • Статистически наиболее строго.
  • Честно учитывает различия методов.

Минусы:

  • Требует огромный калибровочный корпус на каждую пару лабов.
  • Реалистично нельзя собрать.
  • Избыточно для сигнала приоритета показа.

Почему отвергнуто: разрастание охвата на порядок, не оправдано для display-сигнала.

Выбрали

Структурно-позиционную формулу (value − upper_ref) / (upper_ref − lower_ref) (симметрично для low). Применяется парами «последнее vs предыдущее» при вычислении S3 усугубления / улучшения / разворота.

Граничные случаи в v0.1:

  • Одностороннее RR (только upper или только lower) — S3 пропуск: «недостаточно данных для тренда», сигнал не срабатывает. Не выдумываем «implicit lower=0» — это меняет форму коридора и ломает симметрию.
  • Предусловие симметрии: upper_ref > lower_ref (sanity-проверка перед формулой). Если refRange degenerate (upper == lower) — пропуск.
  • Cross-method одного лаба — формула работает идентично; метод-specific RR из лаб-отчёта обеспечивает корректность.

Пропуск S3 — общий механизм: «нет предыдущих → S3 не срабатывает, S1+S2 остаются». Расширение этого пропуска на граничные случаи формулы — естественное продолжение.

За

  • RR — собственная линейка лаба. Лаб публикует RR именно потому что свой RR — единственная сравнимая шкала для своих измерений. Использовать что-то другое — игнорировать что лаб говорит про собственную калибровку.
  • Никаких внешних зависимостей по покрытию. RR всегда в отчёте; ucumvert / population stats / quantile mappings — нет.
  • Симметрично и монотонно. Разворот направления определяется сменой знака position; усугубление — увеличением; улучшение — уменьшением. Прозрачно и replayable.
  • Утверждения скромные. Не «X — пограничное значение», не «Y — критическое». Только «пациент сейчас дальше / ближе к границе коридора этого лаба, чем раньше». Формальная сравнимость, не клиническая эвристика.

Следствия

  • Подача предыдущих измерений в main-biomarkers-detection. Сегодня health-report-pipeline Phase 1 свёртка отбрасывает предыдущие; для S3 надо либо подгружать параллельно, либо переделать свёртку. Open в defining-main-biomarker § Следствия.
  • Одностороннее RR — частый случай, не граничный. Troponin, BNP, D-dimer, ESR, tumor markers (PSA / CA-125 / CEA) — почти все имеют только upper. S3 на них пропускает по дизайну. Это потеря покрытия; компенсируется S1+S2.
  • Pediatric / pregnancy диапазоны — формула работает идентично, если RR соответствует cohort’у пациента на момент измерения. Вопрос не математический, а на стороне данных: правильный ли RR отдал лаб для возрастной группы. См. region-aware-ranges.
  • Отображение единиц ≠ нормализация для тренда — это две разные задачи. Конвертация единиц нужна для UI (test page показывает значения в предзаданных единицах региона пациента: например, mmol/L вместо mg/dL для глюкозы) — задача читаемости результата. Нормализация для тренда — это про сравнимость двух измерений между собой через структурную позицию (задача корректности вердикта worsening/improving). Разные задачи, разные методы, разный момент применения; не путать.

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

  • Порог для «значимого» тренда. Насколько большое изменение position считается усугублением vs шумом? Δposition > 0.1 (10% ширины коридора)? Эмпирический вопрос для рабочего цикла с врачом на ~50 doctor-reviewed отчётов одновременно с калибровкой N=2 для S1.
  • Чувствительность разворота направления. Один разворот из low в high — значимо? Два подряд? Паттерн не зафиксирован, ждёт рабочего цикла с врачом.
  • Односторонние диапазоны — расширить? Возможно ввести «soft lower» (value × 0.1?) для маркеров типа troponin — но это уже синтетическая эвристика, выходит за обоснование формулы. Лучше оставить пропуск.
  • Покрытие pediatric/pregnancy диапазонов. Доходят ли pediatric RR из реальных лабов в нашу FHIR-структуру? Если нет — pediatric S3 не работает из-за данных, не из-за математики. См. region-aware-ranges.

Связано

  • main-biomarkers-detection — модуль, в котором эта формула используется (S3 trend signal)
  • defining-main-biomarker — родительская decision: три OR-сигнала, из которых S3 опирается на эту нормализацию
  • health-report-pipeline — Phase 1 свёртка, которая сейчас отбрасывает предыдущие измерения; нужна доработка чтобы S3 работал
  • medical-expert-loop — общий рабочий цикл с врачом, через который калибруется порог «значимого» тренда и чувствительность разворота
  • region-aware-ranges — лаб-specific и cohort-specific reference ranges; источник RR для формулы
  • biomarker-graph — содержит обоснование для биомаркер↔condition связей; не относится к нормализации напрямую, но read-only-контракт тот же
  • biomarker — entity-страница про биомаркер
  • normalization-service / loinc-harmonization-pipeline — другие «нормализации» (имена / коды) на других слоях, не путать с этой числовой