Какие URL’ы класть в Resource.identifier.system на ресурсах, которые пишет Health Report Pipeline. Сейчас не зафиксировано в один принцип — три URL’а живут по разной логике, и при rename’е вocabulary возникают вопросы куда что переименовывать.
Контекст
В FHIR Resource.identifier[] — массив business-identifiers (отдельно от Resource.id, который generator’ит сервер). Каждый identifier = { system: URL, value: string }. system namespacing’ует identifier (определяет «какого рода» он), value — само значение.
Мы используем identifier’ы для:
- Поиска ресурса по бизнес-ключу:
GET /ClinicalImpression?identifier=<system>|<value> - Conditional PUT (upsert):
PUT /ClinicalImpression?identifier=<system>|<value>— если уже есть, апдейт; нет — создание
Это позволяет нам не хранить FHIR-server-assigned id для look-up’a — мы знаем fhirPatientId и identifier system из codebase, и этого хватает.
Что у нас сейчас (Health Report Pipeline)
Pipeline пишет три patient-scoped FHIR-ресурса. Все они keyed по fhirPatientId (один resource per patient):
| Resource | identifier.system | identifier.value |
|---|---|---|
| ClinicalImpression | http://bloodgpt.com/fhir/identifier/clinical-impression | <fhirPatientId> |
| Composition | http://bloodgpt.com/fhir/identifier/patient-summary | <fhirPatientId> |
| CarePlan | http://bloodgpt.com/fhir/identifier/care-plan | <fhirPatientId> |
Inconsistency: два URL’а названы по resource-type (clinical-impression, care-plan), один — по семантической роли (patient-summary). Vocabulary evolves («patient-summary» → «health-report-composition»? или «report-composition»?), и неясно, переименовывать ли все три по одному правилу.
Возможные принципы
Принцип A — по resource-type
system = resource type, всегда. Семантическая роль (что этот resource значит) — отдельно (через Composition.type code, и т.п.), не через identifier.system.
http://bloodgpt.com/fhir/identifier/clinical-impression
http://bloodgpt.com/fhir/identifier/composition
http://bloodgpt.com/fhir/identifier/care-plan
Pro:
- Стабильно — vocabulary меняется, identifier URL’ы — нет
- Однородно — один паттерн на все resource types
- Существующие два из трёх уже соответствуют (
clinical-impression,care-plan)
Con:
- Не различает множественные writer’ы. Если test-analysis-pipeline тоже пишет Composition этому пациенту — конфликт identifier’a. (На сейчас этого нет, но архитектурно ограничивает.)
- Менее explicit что resource — Health Report’овский
Принцип B — по семантической роли
system = роль в продукте.
http://bloodgpt.com/fhir/identifier/health-report-clinical-impression
http://bloodgpt.com/fhir/identifier/health-report-composition
http://bloodgpt.com/fhir/identifier/health-report-care-plan
Или короче:
http://bloodgpt.com/fhir/identifier/health-report/clinical-impression
http://bloodgpt.com/fhir/identifier/health-report/composition
http://bloodgpt.com/fhir/identifier/health-report/care-plan
Pro:
- Explicit что resource написан Health Report Pipeline’ом
- Можно иметь несколько Composition на пациента из разных pipeline’ов без коллизии identifier’a
- Read-side понимает context («это Health Report’овский Composition, не lab discharge-summary»)
Con:
- Vocabulary-bound — если pipeline переименуется (как уже было
actuality-pipeline-v1→health-report), identifier URL’ы превратятся в legacy - Длиннее
- Дублирует с
meta.tag(где можно положить pipeline-source)
Принцип C — по resource-type + meta.tag для source
system = resource-type (Принцип A), а Resource.meta.tag несёт «кто написал». Read-side фильтрует по meta.tag когда нужно ограничить scope.
identifier.system = http://bloodgpt.com/fhir/identifier/clinical-impression
meta.tag[].system = http://bloodgpt.com/fhir/source
meta.tag[].code = health-report-pipeline
Pro:
- Identifier стабилен (Принцип A)
- Source трекается separately
- Existing AIAST security tag, analysisRunId tag — уже используем
meta.tagpattern - FHIR-conventional (meta.tag = «labels» что-то)
Con:
- Read-side сложнее (нужен
_security/_tagfilter в запросах) - Если несколько pipeline’ов пишут в один CI — overwrite друг друга
Граница между identifier и meta.tag
Они часто путаются — оба key-value на ресурсе, оба имеют system + value/code, оба searchable. Их различает роль:
identifier[] | meta.tag[] | |
|---|---|---|
| Семантика | «WHICH resource» — business primary key | «WHAT property has this resource» — label/classification |
| Cardinality | обычно один per (system, value) — уникальная пара идентифицирует ресурс | many per resource, обычно multiple одновременно |
| Stability | permanent — заданный identifier не меняется | accumulates — теги добавляются / некоторые могут rotate |
| Search semantics | ?identifier=system|value — exact match, retrieve THE resource | ?_tag=system|code — filter, retrieve set of resources |
| Use case | conditional PUT / upsert / lookup by patient | filter «все AI», «всё из chat», «всё в этом run» |
| Наша конвенция | resource-type per patient (см. выше) — один CI / Composition / CarePlan per patient | origin / source / security / run-id (см. fhir-meta-tagging) |
Примеры в нашей системе:
ClinicalImpression для пациента 52:
{
"identifier": [
{ "system": ".../identifier/clinical-impression", "value": "52" } // ← КТО этот ресурс
],
"meta": {
"tag": [
{ "system": ".../CodeSystem/origin", "code": "ai-generated" }, // ← origin
{ "system": ".../CodeSystem/source", "code": "document-import" }, // ← source
{ "system": ".../analysisRunId", "code": "<runId>" } // ← run-stamp
],
"security": [
{ "system": "...v3-ObservationValue", "code": "AIAST" } // ← AI-asserted
]
}
}identifier — «это THE CI для патиента 52» (упсёрт ключ, обновляется через PUT).
meta.tag + meta.security — «вот какие свойства у него» (labels).
Боковой случай — когда выбор не очевиден
Source attribution (meta.tag source = health-chat / survey / document-import) могла бы быть identifier-system’ом (identifier.system=health-chat, value=...). Не работает потому что:
- Один Composition с identifier’ом «document-import|
» можно было бы сделать, но тогда identifier-key — это сам документ, а не «THE Composition пациента». Conditional PUT по identifier перезаписывал бы wrong resource. - Если на patient’е есть 3 chat-сессии + 1 survey + 2 document-import’a, и все пишут Composition’ы — multiple identifier values нужны (один CI per chat-session). Дублирует resourceCounts от хвоста UI.
source — это категория, не identity. → meta.tag, не identifier.
Per Принцип A (resource-type), source как идентификатор был бы overengineering. См. fhir-meta-tagging для полной policy.
Параллельные вопросы (открытые отдельно)
Помимо identifier.system есть ещё несколько persisted strings, у которых аналогичная проблема:
- Composition.type.code — сейчас
patient-summary(по семантической роли). Vocabulary-bound. Решено в patient-summary-composition-naming (active) — оставить как «patient-summary» для V2.5-track. Переоткрыть когда фиксируем Health Report имя? - FHIR extension URL
bloodgpt-parameter-analysis— на CI sub-extension’ах. Сейчас вfhir-clinical-impression-builder.ts:227иfhir-clinical-impression-parser.ts:13. Vocabulary-bound (тоже про parameter vs biomarker). - Device.id —
bloodgpt-pipeline-v2-5. Version-bound. Черезv2-5хвост. - DB columns —
BloodTest.fhirAssessmentId,Patient.lastSummaryStatus / lastSummaryAt / lastSummaryRunKey. Vocabulary-bound.
Каждый можно решать по тому же principle-choice как identifier.system. Согласованный outcome желателен — иначе один identifier меняется, другой остаётся, и snake-pit получается ещё хуже.
Что blокирует rename — existing data
На сейчас staging HAPI содержит resources с текущими identifier’ами. Если rename’нем без migration:
- Existing CIs с
identifier.system=clinical-impressionне найдутся по новому URL’у - Conditional PUT под новым URL создаст orphan (старый + новый)
Migration варианты:
- Wipe & re-seed staging FHIR — если staging-data не precious
- Read-side fallback — поддерживать оба URL’а в reader’е, новые writes под новым URL, transition period
- Backfill script — переписать identifier.system на existing resources
Per Phase F rationale: actuality-mvp-dirty branch не deploy’ится в prod; staging-data написана старой production-line.
Открытые вопросы
- Какой principle выбрать (A / B / C)?
- Какие persisted strings включать в один coherent rename (extension URL, Composition.type, Device.id, DB columns)?
- Wipeable ли staging FHIR data — определяет нужна ли migration
- Будут ли несколько writer-pipeline’ов на одном resource (если да — Принцип B/C предпочтительнее)
- Cohabitation с FHIR R4 standard identifiers (MRN, SSN system’ы) — конвенция HL7 vs наша
Связано
- health-report-vocabulary — словарь системы, который motivates rename’ы
- health-report-pipeline — что пишет identifier’ы
- patient-summary-composition-naming — historical decision для Composition.type
- fhir-composition — entity-страница
- fhir-clinical-impression — entity-страница
- fhir-meta-tagging — paralllel pattern для source attribution через meta.tag