Сегодня граф (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.parse 13 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. Сейчас source per ребро — одно 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 — связан касательно «где хостить большой файл» как узкого подмножества.