Визуализируем FHIR — IT-стандарт для медицины




    Здравствуйте. Меня зовут Андрей, я работаю в компании, создающей IT-решения в области медицины. В качестве основного языка разработки мы используем Clojure, а также (в зависимости от проекта/модуля) Python, Javascript, Go, C, C#, Rust, Objective-C и т.д.

    Важное место в нашем технологическом стеке занимает международный стандарт FHIR (Fast Healthcare Interoperability Resources), определяющий формат хранения/обмена/предоставления медицинской информации в электронном виде и включающий в себя спецификацию RESTful API клиент-серверного взаимодействия.

    Некоторое время назад я начал пет-проект приложения, которое визуализирует содержимое ресурсов произвольного FHIR-сервера и позволяет производить базовые CRUD — операции. В КДПВ показан скриншот страницы редактирования элемента ресурса типа Patient.

    Под катом небольшое описание и ссылка на онлайн-демо — можно будет пощупать настоящий живой FHIR-сервер, потыкать кнопочки, посмотреть/посоздавать/поредактировать различные ресурсы и даже попробовать вызвать тот самый хабраэффект! )

    Пара слов про FHIR


    Я не буду переписывать сюда описание стандарта, желающие смогут узнать все подробности по ссылке выше, почитать другие материалы на различных ресурсах, а также задать вопросы и присоединиться к обсуждению в FHIR-чате. Дам только общее представление: центральным понятием является ресурс, ресурсы разделены по типам и группам, каждый тип имеет собственную структуру полей, значения которых могут быть примитивными или композитными типами и ссылками на другие ресурсы. Поля могут быть обязательными или не обязательными к заполнению, содержать одно значение или коллекцию значений. Например, ресурс Patient имеет поля примитивного типа: дата рождения/пол/..., композитного типа: имя/адрес/...., ссылки на организацию и список лечащих врачей и т.п.

    Для каждого ресурса хранится история его изменений как перечень состояний с датами их актуальности и номером версии объекта. RESTful API позволяет запросить метаданные о составе и структуре ресурсов, поддерживаемых данным FHIR-сервером, список элементов ресурса любого типа с широкими возможностями фильтрации по значениям отдельных параметров, включением зависимых ресурсов, ограничить выдачу результата значениями перечисленных полей, сортировать результат запроса по сложным критериям, и т.д. Также есть методы поддержки CRUD на уровне элемента ресурса — создание/обновление с валидацией структуры и наличием обязательных полей, удаление элемента. Существуют методы API для просмотра истории изменений как на уровне элемента так и на уровне типа ресурса.

    В типичном приложении использование этого универсального API абстрагировано толстым слоем бизнес-логики конкретного клиента. Например, при назначении визита пациента к врачу запрашиваются данные о количестве его медицинских страховок и сроках их действия, история предыдущих визитов, сведения о балансе взаиморасчетов пациента с клиникой и т.д., данные о расписании выбранного врача и наличии свободных часов для приема и т.п. И все это в удобной форме представляется на экране рабочего места сотрудника, осуществляющего запись. Или автоматический планировщик задач периодически по заданному расписанию запускает процесс, запрашивающий список ближайших предстоящих визитов и автоматически рассылающий пациентам смс с текстом напоминаний или уведомлений по заранее заданным шаблонам.

    Но мне пришла идея сделать универсальную визуализацию содержимого ресурсов FHIR-сервера, и так появился проект под названием…

    FHIR-face


    Приложение позволяет подключиться к любому FHIR-серверу и осуществлять просмотр содержимого ресурсов и базовый CRUD. Одна из сложностей такого универсального подхода заключается в том, что разные сервера могут иметь разные версии FHIR-стандарта, реализовывать его не в полном объеме, иметь отклонения в перечне, составе и структуре ресурсов и API, а также иметь дополнительную функциональность, не входящую в спецификацию. Но если данный сервер позволяет запросить метаданные о составе и структуре поддерживаемых ресурсов, то можно добавить его поддержку в этом проекте.

    Интерфейс проекта имхо интуитивно понятный. Выбор адреса сервера осуществляется через параметр адресной строки, но в текущей демо-версии по умолчанию выбран hapi.fhir.org. Со стартовой страницы происходит загрузка состава и структуры ресурсов, и предлагается выбрать конкретный тип ресурса для просмотра его содержимого. При выборе типа ресурса делается запрос на ограниченное число его элементов, которые выводятся в таблицу с колонками: идентификатор, условное пользовательское представление (при возможности его получения) и размер в символах строковой сериализации. Работает полнотекстовый поиск по содержимому ресурса. При клике на строку таблицы или на кнопку создания нового элемента происходит редирект на страницу содержимого элемента ресурса.

    В верхней части страницы элемента есть кнопки полной свертки/развертки иерархического содержимого и кнопка изменения стиля представления реквизитов. Содержимое элемента представлено списком реквизитов. Каждый реквизит имеет имя, тип, краткое описание и значение. Залитый черным кружок слева от реквизита означает что этот реквизит присутствует в ресурсе (даже если не выбрано его значение — в этом случае в элементе есть этот реквизит, но он имеет пустое значение), пустой кружок означает отсутствие данного реквизита в элементе, но наличие в списке реквизитов структуры данного типа ресурса. Любой реквизит можно добавлять/удалять из элемента по клику мышкой на пиктограмму кружка слева от имени. Реквизиты, не перечисленные в составе структуры типа ресурса, но имеющиеся в элементе, выделены пурпурным цветом.

    Значения примитивных типов представлены соответствующими типизированными виджетами — дата, время, число, строка и т.п. Пиктограмма правее строковых реквизитов переключает режим ввода/редактирования текста — с переводом строк или без. При редактировании автоматически изменяется размер виджета в зависимости от его содержимого. При стартовом заполнении формы все текстовые поля длиннее 50 символов представлены виджетами textArea с переводом строк. Виджеты ссылок представлены типом ресурса-ссылки и значением, при выборе значения работает полнотекстовый поиск по содержимому ресурса-ссылки.

    Реквизиты композитных типов могут быть свернуты с подсветкой количества возможных и заполненных подчиненных реквизитов или развернуты — с демонстрацией содержимого. При клике на имя/тип/описание реквизита срабатывает полная глубокая свертка/развертка содержимого, при клике на подсветку количество полей — развертка реквизитов следующего уровня. Реквизиты-коллекции (с произвольным количеством значений) имеют пиктограмму + справа от описания реквизита — для добавления новых значений в коллекцию. Свертка/развертка элемента коллекции (если он в свою очередь является значением композитного типа) осуществляется по клику на крайнюю правую часть рамки, ограничивающей элемент коллекции. При клике на крестик в правом верхнем углу рамки элемент коллекции удаляется.

    Данный интерфейс позволяет редактировать содержимое любого ресурса. В нижней части страницы находится кнопка сохранения ресурса в отредактированном состоянии. При записи ресурса FHIR-сервер производит валидацию его содержимого, и при наличии ошибок не записывает ресурс, а возвращает список ошибок валидации. В этом случае текст этих ошибок выводится красным ниже кнопки сохранения. Структура объекта ошибок валидации определяется реализацией сервера, поэтому был выбран вариант его универсального текстового представления. При отсутствии ошибок происходит запись элемента ресурса и редирект обратно на страницу списка элементов.

    И наконец, обещанные ссылки:

    Онлайн-демо проекта

    Гитхаб проекта — кот не выставочное чучело, а живой рабочий, поэтому присутствуют закомментированные участки, строительные леса и прочие элементы, необходимые для проведения строительно-монтажных работ, надевайте каски )
    Поделиться публикацией

    Комментарии 14

      +2
      Но почему на скриншоте шрифт Papyrus?..
        +3
        Месть врачам за почерк.
        +1
        Спасибо! Я знал что такой проект должен был появиться!
          0
          Какие медицинские системы поддерживают стандарт FHIR?
          А в России?
            0

            Насчет систем не скажу, но насколько я помню доклады на конференции — в Австралии и Канаде муниципальные структуры используют FHIR, в США также, но у них ситуация немного отличается — проще в чатике по ссылке из статьи напрямую спросить, там присутствуют участники, непосредственно занимающиеся этими вопросами. Россия традиционно не спешит быть в авангарде, присматривается к чужому опыту )

            0
            Логичнее было бы совместить отображение данных ресурса с его описанием аналогично профайлам в Simplifier. Также, не совсем понятно по какому принципу отображаются поля. Например, некоторые из них пользователь не может менять и, по идее, вообще не должен о них знать (Resource.id, Resource.impliciteRule и т.д.).

            Для конечного пользователя ресурс предоставляет Narrative, причём его содержимое не обязано совпадать с остальной частью (если status не generated). Для разработчика же возможно важнее видеть сам ресурс и иметь возможность его валидации design time с помощью XML/JSON Schema, профайлов и прочих подобных вещей (см. 7.5 Validating Resources).
              0

              Поля (как на верхнем уровне, так и в любом вложенном объекте) отображаются в алфавитном порядке (для унификации, хотя можно сортировать по условному ордеру в схеме структуры ресурса). Список полей ресурса (вместе с их типами, списками допустимых значений, флагами обязательности и множественности значения) берется из StructureDefinition конкретного типа — независимо от наличия/отсутствия значений этих полей в выбранном элементе. Если при этом в элементе ресурса присутствуют поля, не перечисленные в StructureDefinition, то они показываются ниже и всегда как строки — потому что без метаданных я не знаю их тип, но как честный человек должен показать их наличие и дать некоторую возможность редактирования ) Насчет того, что не может менять — я конечно могу сделать id и часть других реквизитов недоступными для редактирования, но тот же id во многих ресурсах может нести некоторую семантику (например при синхронизации со сторонней системой или при загрузке стандартного каталога с четко определенными айдишниками). Поэтому, а еще для универсальности, я не стал запрещать их редактирование.
              То, что вы говорите про "видеть сам ресурс"… Конечно, мне самому привычнее смотреть содержимое ресурса в форматированном ямле или жсоне ) Но так можно дойти до того, что — используйте Postman или просто браузер, кидайте запросы вручную и смотрите ответы текстом ) Но имхо это не лишает данный проект права на жизнь. Наглядная визуализация структуры ресурса, типов и описаний полей, демонстрация возможно незаполненных реквизитов, да и просто — почему бы и нет? В комментарии выше даже предвосхищали появление визуализатора подобного рода )

                0
                Не очень convincing, но это твой проект. Ещё пара замечаний:
                — сходу не вспомню где, но в некоторых случаях порядок может иметь значение, возможно в Slices или что-то подобное
                — неизвестные элементы, такие как extension, в бОльшей степени должны браться из StructureDefiniton которые в свою очередь определяются профайлом
                — когда делаешь PUT вероятно нужно использовать If-Match
                  0

                  Спасибо за интерес и замечания. Не написал ввиду очевидности, но порядок элементов внутри коллекций у меня сохраняется (хотя и пока отсутствуют стрелки для его изменения). А порядок полей объекта… Он приходит в виде жсона, там уже он теряется. Хотя, если в структуре полей есть ордер, по нему можно восстановить. Про легальные extension — если они определены в StructureDefiniton — я их вывожу с типами и как надо — в части ресурсов на демо-примере это видно. Про бестиповый текст я имел в виду т.н. нелегальные экстеншены, которые нарушают стандарт но могут присутствовать.


                  Про PUT и If-Match, простите, не понял. Не могли бы раскрыть мысль подробнее?

                    0
                    «Нелегальный» extension правильнее назвать локальными, они имеют право на существование и по хорошему должны быть описаны в StructureDefintion. Если пойти дальше, то url в этом StructureDefinition должен указывать на реальное описание локального extension, что позволило бы тебе взять его описание.

                    Если пользователей больше чем один, то отредактированный ресурс обратно на сервер лучше отправить как PATCH или Conditional Update (c HTTP header If-Match).
                      0

                      Спасибо, понятно.

              0
              Большое спасибо за статью Андрей! Подскажите еще ресурсы, если не сложно, по опыту имплементации FHIR и c2s в проектах? Про чатик я понял — зарегистрировался. В данный момент я изучаю проекты от SAMHSA и все в этой области очень интересно. Заранее благодарен!

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое