Клинический граф знаний BloodGPT — структурированный «справочник связей» между биомаркерами, диагнозами, лекарствами и преаналитическими факторами, с rationale-объяснением каждой связи и цитатами из медицинских guideline’ов. Питает агентов параметр-анализа (diagnostician / retriever) и, через них, опосредованно классификатор актуальности (biomarker-actuality-service).
Где живёт
- В коде:
packages/analysis-core/src/data/biomarker_graph.json(на веткеorigin/feat/v2-5, HEAD64d83432) - Размер: ~13 000 строк JSON, сотни биомаркеров; целиком грузится в память на старте (
DiagnosticGraphExecutorкэширует) — см. large-data-files-storage - Происхождение: исторически создан в Python-сервисе
fhir-services(отсюдаsnake_case-ключи). Кто сегодня де-факто owner content’а — формально не зафиксировано: правки идут от Артура (как разработчика) и Кати (как medical advisor), но «процедуры обновления» нет. Это часть открытого вопроса про админ-интерфейс (см. ниже).
Это не «граф», а ego-network catalog
Хотя файл называется biomarker_graph.json и концептуально кодирует связи между сущностями, структура данных — не global graph, а каталог ego-сетей (1-hop neighborhoods), индексированный по биомаркеру:
- Каждая запись в
biomarkers[name]— это ego-сеть конкретного биомаркера: списки его 1-hop соседей (related_conditions,related_medications,related_parameters,preanalytical_factors) с rationale/quotes/source per ребро. - Каждая запись в
domains[name]— то же самое, но на уровне домена (Tier 2 fallback). - Traversal’а нет — мы никогда не ходим по «графу» от одного биомаркера к другому через посредников; мы только читаем ego-сеть нужного биомаркера.
- Один и тот же узел (например,
statin) дублируется в ego-сетях многих биомаркеров — это OK, потому что мы не оптимизируем под global view, не запрашиваем «кто связан со статином» (inverse-lookup). - Identity у рёбер локальная: «ego-сеть X включает Y», а не «глобальное ребро X↔Y».
Это объясняет, почему все операции в коде сегодня — dict.get(biomarker_name) или dict.get(domain_name). KV-store с биомаркер → ego-network — нативная форма этих данных. Что это означает для выбора storage / data-model — см. biomarker-graph-storage.
Структура
type BiomarkerGraph = {
biomarkers: Record<string, BiomarkerGraphEntry>; // ключ — display-имя биомаркера ("Hemoglobin (Hb)")
domains: Record<string, DomainGraphEntry>; // ключ — домен ("hematology", "thyroid", …)
};
type BiomarkerGraphEntry = {
domain: string; // в какой домен входит этот биомаркер
section_id?: string;
related_parameters: Array<{ name; rationale }>;
related_conditions: Array<{ name; context?; rationale? }>;
related_medications: Array<{ name; context?; rationale? }>;
preanalytical_factors: Array<{ name; context?; rationale? }>;
};
type DomainGraphEntry = { // используется как Tier-2 fallback в Диагностике
scope: string;
related_parameters: Array<{ name; rationale }>;
conditions: Array<{ name; context?; rationale? }>;
medications: Array<{ name; context?; rationale? }>;
preanalytical_factors: Array<{ name; context?; rationale? }>;
};Каждый item в массиве несёт ещё два опциональных поля (на этапе normalizeContextToRationale они приводятся к единому виду):
rationale— короткое клиническое объяснение, почему два понятия связаны (например, для пары «Hemoglobin × Creatinine» — про то, что анемия при CKD многофакторна, EPO-дефицит и т.д.)quotes— verbatim-цитата из guideline’а, на которую опирается rationalesource— название и ссылка на guideline (KDIGO 2026, ATA 2017, …)
quotes + source пробрасываются end-to-end и попадают в personalizer’ом сгенерированный текст под пациента — это и есть «трассируемость», о которой пишут в fhir-modeling-ai-content и fhir-meta-tagging.
Два уровня записей — biomarkers + domains
Граф несёт два параллельных типа entries:
biomarkers[name](113 записей) — детализированные ego-сети для конкретных биомаркеров, которые мы успели разметить вручную. Каждая запись несёт собственный список related_* с rationale/quotes/source per ребро + ссылается на свойdomainстрокой.domains[name](22 записи) — обобщённые ego-сети на уровне клинической области («hematology», «thyroid», «lipids», …). Каждая со scope-описанием (что покрывает) и domain-уровневыми связями.
Двухуровневая структура — не от хорошей жизни. Реальных лабораторных биомаркеров тысячи (в LOINC десятки тысяч), вручную разметить каждого с rationale/quotes — нереально. Поэтому конкретно расписан только подмножество хорошо известных, а остальные покрываются через домен: «если биомаркера в графе нет, дай хотя бы общую domain-картину».
Как consumers это используют (fallback-каскад от specific к general) — это уже логика самих consumers, см. 3. Сам граф просто хранит два уровня данных; cascade-семантика — за пределами графа.
Использование в BloodGPT
- diagnostician — единственный код-консьюмер графа. Загружает файл через
DiagnosticGraphExecutor(lazy cache поimport.meta.url-пути), отдаёт LLM-агенту три инструмента:lookup_biomarker,lookup_domain,submit_diagnostic_plan. LLM выбирает связанные параметры/диагнозы/медикаменты, исполнитель потом дополняетsubmit-payload graph-данными поgraph_biomarker_key/graph_domain(«система впрыскивает related_*»). - retriever — графа не видит, получает diagnostic plan на вход (со списками
not_foundсущностей, которые надо найти в FHIR пациента). - Классификатор актуальности + Reasoner — графа напрямую не видят, но потребляют его через вывод Ретривера (
retrieved/relatedFound*). После Option E-интеграции оба питаются от одного прогона Диагност + Ретривер: классификатор актуальности читаетretrievedper биомаркер на gate-этапе, Reasoner —relatedFound*per биомаркер на этапе анализа для survivor’ов после gate’а.
Failure modes / готчи
snake_case-ключи в TypeScript-типах — намеренный выбор, чтобы оставаться байт-совместимым с JSON-файлом из Python-пайплайна; cast-ы вcamelCaseбез явной конверсии дают silent-undefined-баги- CJS-bundle и
import.meta.url—DiagnosticGraphExecutorрезолвитdata/biomarker_graph.jsonотносительноimport.meta.url-пути; подanalysis-worker(esbuild, strict-mode-bundle) это потенциальный краш — те же грабли, что и у biomarker-actuality-service - Сегодня —
JSON.parse13K строк на каждый старт процесса (cache по first-load). Версионирование = git-история файла; обновить связь = редактировать JSON руками + передеплоить весь пакет — см. ниже
Хранение в production
Сегодняшняя форма (JSON в коде, ~13K строк, parse на каждый старт процесса, правки через PR + redeploy) — это dev-стадия, в production хочется иной форме. Конкретные кандидаты (status quo + admin-UI / Postgres-таблица / hybrid baseline+overlay / graph DB), какие операции нам реально нужны, что в плоской JSON-структуре мешает (editability, cache-invalidation per edge, coverage visibility, ownership) — собрано отдельно: biomarker-graph-storage. Готчи из секции выше про bundle-готовность и parse-на-старте — часть этого же вопроса.
Связано
- biomarker-graph-key-loinc — предложение сделать LOINC каноническим ключом графа (вместо display-имени) — убирает большую часть LLM-fuzzy-match’а из Диагностика, даёт coverage-метрику «из коробки»
- diagnostician — единственный код, который сегодня ходит в граф; через него граф питает Retriever-пайплайн
- retriever — следующий шаг параметр-анализа; графа не видит, но потребляет его «отражение» через diagnostic plan
- biomarker-actuality-service — целевой потребитель отфильтрованного выхода retriever (поле
retrieved); сегодня замокан - biomarker-analysis-pipeline — pipeline, для которого Диагностик и Ретривер изначально написаны
- large-data-files-storage — связан касательно «где хостить большой файл», но смотри секцию «Моделирование графа» — это узкое подмножество вопроса
- fhir-meta-tagging — почему
quotes/sourceобязательны (трассируемость до guideline’ов)
Источники
Источники: 1.
Сноски
-
Дайджест созвона 2026-05-12 (Ильдар + Артур), accessed 2026-05-17, https://github.com/Realai-plus/meeting-digests/blob/main/data/digest/2026/05/2026-05-12T11%3A45%3A00.000Z_%D0%A1%D0%B5%D1%80%D0%B2%D0%B8%D1%81_%D0%B2%D0%B0%D0%BB%D0%B8%D0%B4%D0%BD%D0%BE%D1%81%D1%82%D0%B8-%D1%80%D0%B5%D0%BF%D1%80%D0%B5%D0%B7%D0%B5%D0%BD%D1%82%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D1%81%D1%82%D0%B8-%D1%80%D0%B5%D0%BB%D0%B5%D0%B2%D0%B0%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D0%B8_%D0%B1%D0%B8%D0%BE%D0%BC%D0%B0%D1%80%D0%BA%D0%B5%D1%80%D0%BE%D0%B2_01KRE02872CTM2V6CEERAQ8EVD.md — блок про хранение графа («JSON-файл не production», Postgres-таблица или отдельный репо). ↩