Сегодня граф (biomarker-graph) лежит как ~13 000 строк JSON в коде, в packages/analysis-core/src/data/biomarker_graph.json. Это работает на dev-стадии, но для production хочется иной формы. Открытый вопрос — какой именно.
Какие операции с графом мы реально делаем
Чтобы выбор storage не строился на гипотетических use case’ах, посмотрим на реальный code surface:
lookupBiomarker(name)—biomarkers[name]get; если ключа нет, возвращает список всех доступных ключей (LLM решает, попробовать ли другой). Hashtable get-or-list-keys.lookupDomain(domain)—domains[domain]get, та же логика.biomarkersByDomain(helper для prompt-индекса) —groupByбиомаркеров по их полюdomain. In-memory aggregation, делается один раз при load’е.submitDiagnosticPlan— orchestration: читает кэш предыдущих lookup’ов, валидирует, что LLM позвалlookup_*перед submit’ом, впрыскивает кэшированныеrelated_*в финальный план. Никакого нового graph-доступа.evidence-enrichment— lookup по_matchedGraphName(тэг, который Ретривер ставит на FHIR-сущность после фуззи-матча) → достатьrationale/quotes/source. Опять hashtable lookup.
Чего нет: multi-hop traversal, inverse-lookup («какие биомаркеры связаны с состоянием X»), recursive queries, graph-algorithms, JOIN’ы поперёк типов сущностей. Все операции — dict.get(key) плюс несколько in-memory aggregations при первой загрузке.
Это сужает реалистичный выбор: graph DB (Neo4j/Arango) для текущего surface’а — overkill, никаких traversal’ов мы не делаем. Но shape данных всё ещё определяет, насколько production-удобной получается работа — даже когда сами query’и тривиальны.
Возможности для улучшения
Сегодняшний JSON-в-коде ограничивает в нескольких местах одновременно. Это open opportunities при переходе в production:
- Editability. Катя как medical advisor не может править граф без PR + redeploy
analysis-worker(и любого пакета, который шипает@repo/analysis-core). При переходе на graph-вне-кода или admin-UI правки идут через интерфейс, без deploy-цикла. - Coverage visibility. Сейчас нет простого способа сказать «вот эти LOINC-ы в реальных лабораторных отчётах, а вот эти есть в графе» — две системы координат, intersection не считается. Любая модель, где ключ — внешний канонический id, открывает coverage-метрику естественным образом.
- Owner / approval flow. Правки идут от Артура и Кати без формальной процедуры. При уходе графа из кода появляется естественное место для approval flow (PR на git-репо, audit-log в БД, ревью в админ-UI).
JSON.parse13 000 строк на каждый старт процесса. Cache по first-load снимает повторные parse’ы, но первый старт — заметная нагрузка. Не критично; любое не-JSON-в-коде решение это устраняет.
Граф и LOINC-каталог — стоит ли объединить
Параллельная decision-страница biomarker-graph-key-loinc предлагает индексировать граф по LOINC. Если мы туда движемся — открывается естественная мысль: граф и LOINC-каталог становятся одной структурой. Сегодня они отдельные artifact’ы: LOINC-сервис носит коды + standardized names; граф — биомаркеры + related entities + rationale/quotes. С LOINC-ключом эти данные сходятся на одном ID — каждый биомаркер = LOINC code + clinical extras (related, rationale, quotes, source, domain).
Открытое: имеет смысл слить их в один артефакт (одна страница, одна data-model, один admin-UI) — или держать раздельно (LOINC service = base taxonomy, graph = clinical enrichment overlay) с явным cross-link по LOINC code. Зависит от того, насколько эти системы планируется развивать отдельно vs совместно.
Варианты, от минимально-инвазивного к максимальному
- Status quo + admin-UI. Граф остаётся в JSON-файле в коде, но появляется CRUD-UI, который commit’ит правки в репо через GitHub API. Редактирование без redeploy не получаем, но Катя редактирует без знания git. Самый дешёвый вариант — минимум инфраструктурных изменений.
- Postgres-таблица + admin-UI. Граф как row’ы в БД (по биомаркеру или по ребру). Apps читают из БД (с in-memory кэшем), правки через UI = immediate effect, redeploy не нужен. Привычный стек, минимальная operational сложность.
- Hybrid: code-shipped baseline + БД-overlay. Базовый граф в коде (default + быстрый старт), правки/добавления в БД накладываются сверху. Сложнее, но даёт быстрый rollout и rollback.
- Графовая БД (Neo4j/Arango/Memgraph). Природный fit для теоретически-возможных multi-hop query’ев. Но нет use case’а сегодня, который это оправдывает — мы не делаем traversal’ов. Кандидат, если в будущем понадобятся такие операции (например, GraphRAG поверх медицинских знаний).
Зависимые вопросы
- Multi-hop связи. Граф сегодня плоский: биомаркер → прямые соседи. Хотим ли мы «биомаркер → ось физиологии → другие биомаркеры»? Это решает, нужны ли native graph-traversal операции (и оправдывают ли они graph DB).
- Source-attribution per edge. Сейчас
sourceper ребро — одноstringполе (проверено по фактическому JSON: 1690 items, все string’и). Production-grade — many-to-many (несколько guideline’ов на одно ребро) с историей ревизий. Не критично сейчас, но при переходе в БД естественно появится. - Админ-интерфейс. Любая модель кроме сегодняшнего JSON-в-репо требует UI для не-разработчика. Форма «биомаркер ↔ состояние + rationale + цитата». Конкретный shape зависит от модели.
- Coverage-метрика как regular check. Сравнение «какие LOINC в recognize-логах vs какие в графе» — separate diagnostic. Помогает приоритизировать пополнение независимо от модели.
Открытые вопросы
- Multi-hop traversal — гипотетика или есть конкретные запросы downstream’а, которые сегодня имитируются? Без use case’а graph DB не оправдывается.
- Решать вместе с biomarker-graph-key-loinc или отдельно. Вопросы пересекающиеся: LOINC-key даёт стандартизацию + coverage-метрику; Postgres-table даёт editability + per-edge identity + audit. Это не «либо одно, либо другое» — это два дополняющих изменения, оба нужны для prod-ready состояния. Postgres-table не overhead над LOINC-key, скорее наоборот.
Связано
- biomarker-graph — данные / структура / use cases
- biomarker-graph-key-loinc — параллельная decision-страница про ключ записей. Связана: переход на LOINC-ключ может слить граф с LOINC-каталогом в один артефакт.
- large-data-files-storage — связан касательно «где хостить большой файл» как узкого подмножества.