Когда write-tool (recordSymptom / recordCondition / etc.) или narrative-to-fhir пайплайн пишет clinical resource — это может быть новая запись или обновление существующей. Пациент через 2 недели снова жалуется на головную боль — это та же Condition или новая? Если он сказал «вчера снова мигрень» — это уточнение headache → migraine или отдельный эпизод?

Контекст

Каждый write-tool в Mastra V0.5 (recordSymptom/etc) и narrative-to-fhir pipeline сталкивается с этим. По умолчанию — каждое срабатывание создаёт новый resource, что ведёт к дубликатам в FHIR-store пациента. Нужна стратегия определять «это про существующий resource» vs «это новый».

Применяется cross-pipeline:

  • V0.5 Mastra write tools — chat / survey / document-import; пациент может сообщать одно и то же многократно
  • narrative-to-fhir — если пациент загружает два анализа через 2 недели, оба narrative содержат «headache history» — два Condition’а или один?

Три уровня sameness

УровеньСлучайЧто делать
Exact codeResolved → тот же SNOMED concept IDDedup — update existing (onset, severity, recurrence count, clinicalStatus)
Code hierarchy (IS-A)Резолвлен другой код, но IS-A связан с существующимReconciliation — refine specificity? merge histories? создать parent + child?
Semantic similarityКод не нашёлся (text only) или нашёлся unrelatedHard problem — text similarity / embedding match / human review

Плюс state transitions — пациент говорит «усталость прошла» → tool должен подтвердить clinicalStatus: resolved, не реактивировать. Это та же reconciliation проблема — система должна понимать что это про существующий resource, не новый.

Текущее состояние

V0.5 Mastra write tools

  • Exact-match dedup by code — да (или name match в английском если $expand не реализован, см. clinical-code-resolution V0.5 progression)
  • Hierarchy — нет, не делается
  • Semantic — нет
  • State transitionsbug: пациент «усталость прошла» → tool реактивирует Condition.clinicalStatus: resolved → active. Добавлен clinicalStatus параметр в V0.5; agent prompt нужно дотюнить.

narrative-to-fhir

  • TBD verify — есть ли dedup against existing FHIR resources, или каждый analysis run создаёт новые ресурсы
  • Этот pipeline пишет per-extraction, не per-write-event — natural identifier’ы (subject + code + effective[X]) могут давать implicit dedup на Observation, но для Condition / Procedure это не работает

Решение (V0.5)

Exact code match — достаточен для dedup. Иерархия (Migraine vs Headache, parent/child) и semantic similarity — это reconciliation task, не dedup. Отложены как отдельная фича когда появятся реальные данные и понятны кейсы.

Ильдар: «меня смущают такие половинчатые подходы… оптимизация это хорошо, но как будто ее тоже стоит продумывать последовательно, ориентируясь на продуманные кейсы»

V0.5 dedup-only. Этого достаточно для prototype — будем accumulate реальные дубликаты, поймём паттерны, потом design reconciliation strategy.

Дизайн-варианты для reconciliation (TBD)

Триггеры — когда реконсилировать

  • At-write (synchronous lookup) — каждый write-tool делает search перед POST. Latency penalty, но immediate consistency.
  • Cron / scheduled batch — фоновая работа консолидирует ресурсы пациента периодически. Async, можно использовать heavier checks (semantic / embeddings).
  • Manual review — admin dashboard со списком potential duplicates; человек решает merge / keep separate.

Скорее всего — комбинация: at-write для exact-match (cheap), cron для hierarchy (medium), manual review для semantic (expensive).

Actions — что делать с дубликатом

  • Auto-merge — слить ресурсы в один, истории сложить
  • Auto-update — обновить onset / clinicalStatus / severity на existing, drop new
  • Ask patient — chat-агент задаёт уточняющий вопрос («Вы про ту же головную боль что раньше, или это новый эпизод?»)
  • Admin dashboard — human review для unclear case’ов

Hierarchy detection

SNOMED имеет subsumes operation (FHIR $subsumes) — проверяет parent/child relationship двух concept ID. Можно вызвать на terminology server’е для reconciliation candidate’ов.

GET /CodeSystem/$subsumes?system=http://snomed.info/sct&codeA=37796009&codeB=25064002
→ subsumes (Migraine IS-A Headache)

Semantic similarity

Hard. Возможные подходы:

  • Embedding similarity на FSN / display text — вычислительно дёшево, но noisy
  • LLM judge на pair — точнее, дороже
  • Patient confirmation — самый надёжный, но требует UX

Известные кейсы

Update reactivation bug

V0.5 walkthrough (session 7ff79368): пациент говорит «усталость прошла» → tool реактивирует Condition.clinicalStatus: resolved → active вместо подтверждения resolved. Источник bug’а — agent не понимает что input может быть state transition для existing condition.

Fix: Добавлен clinicalStatus параметр в V0.5 tool signature. Agent prompt должен инструктировать «check existing, transition state if appropriate, not always activate». Требует prompt tuning + verification на real walkthrough’ах.

Headache → Migraine refinement scenario

Пациент day 1: «у меня головная боль» → Condition со SNOMED 25064002 Headache, clinicalStatus: active. Day 14: «снова болит голова, кажется это мигрень» → recordSymptom снова срабатывает с term=“migraine” → resolved в SNOMED 37796009 Migraine.

Что должно произойти — TBD. Варианты:

  • (a) Создать новый Condition для Migraine, оставить Headache. Lose connection между ними.
  • (b) Update existing Headache → Migraine (refine specificity, save history через resource versioning).
  • (c) Создать оба, связать через Condition.relatedItem или Provenance.

Для V0.5 — (a) (вариант по умолчанию: каждый write — отдельный resource). Не оптимально, но не блокирует MVP. Реальный design — после observation реальных кейсов.

Open questions

  • Reconciliation triggers + actions — обсуждали в session 7ff79368, отложено. Что выбрать когда дойдёт.
  • narrative-to-fhir vs V0.5 reconciliation strategy — должны ли они быть consistent? Или narrative-to-fhir может полагаться на natural identifiers (Observation by subject+code+date), а V0.5 ждать explicit reconciliation?
  • Cross-pipeline duplicates — пациент сообщил symptom в chat (V0.5) → потом загрузил medical note где упомянут тот же symptom (narrative-to-fhir). Это duplicate? Какой winner?
  • SNOMED $subsumes integration — на каком terminology server’е (наш Snowstorm Lite если deploy’нем, или внешний)? Performance под realistic load?
  • State-transition prompt tuning — конкретный agent prompt fix для V0.5, нужны test cases.
  • Condition.recurrence / recurrenceCount — есть ли стандартное FHIR-поле для «третий эпизод headache»? Если нет — extension? (Контраст с zero-extensions-fhir.)

Связано

  • clinical-code-resolution — coding pipeline (как term превращается в code); reconciliation работает на result coding’а
  • llm-numeric-codes-policy — связанная decision (что не давать LLM генерировать numeric codes — даёт нам надёжный input для reconciliation: same code = same concept)
  • fhir-meta-tagging — meta-tags для filter’а duplicate-кандидатов (например, ?_tag=source|health-chat&_tag=origin|ai-generated&subject=X&code=Y)
  • snomed — SNOMED hierarchy через IS-A relationships используется для reconciliation
  • medical-context-survey — V0.5 tools используются в survey
  • fhir-conditionclinicalStatus / verificationStatus — оси state transitions
  • health-report-versioning-model — связанный pattern для aggregate ресурсов (PUT canonical Composition, не create-new)

Источники

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

Сноски

  1. Сессия ildar/7ff79368, 2026-03-27 — V0.

  2. FHIR R4 ValueSet $subsumes operation, accessed 2026-05-17, https://www.hl7.org/fhir/codesystem-operation-subsumes.html.