Когда 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 code | Resolved → тот же SNOMED concept ID | Dedup — update existing (onset, severity, recurrence count, clinicalStatus) |
| Code hierarchy (IS-A) | Резолвлен другой код, но IS-A связан с существующим | Reconciliation — refine specificity? merge histories? создать parent + child? |
| Semantic similarity | Код не нашёлся (text only) или нашёлся unrelated | Hard 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 transitions — bug: пациент «усталость прошла» → 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
$subsumesintegration — на каком 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-condition —
clinicalStatus/verificationStatus— оси state transitions - health-report-versioning-model — связанный pattern для aggregate ресурсов (PUT canonical Composition, не create-new)
Источники
Сноски
-
Сессия
ildar/7ff79368, 2026-03-27 — V0. ↩ -
FHIR R4 ValueSet
$subsumesoperation, accessed 2026-05-17, https://www.hl7.org/fhir/codesystem-operation-subsumes.html. ↩