← назад в блог
daily12 августа 2026 г./6 мин чтения

Что на самом деле осталось на вашей медицинской карте

Корпоративная медицинская страховка существовала в личном кабинете только как название — и больше ничего. Я разработал endpoint для получения данных и подключил кабинет к нему, чтобы страница теперь отвечала на вопросы, которые действительно интересуют людей: что покрывает страховка, сколько уже потрачено и что будет дальше.

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

В личном кабинете ситуация была ненамного лучше. Полис отображался в списке — и на этом всё. Открытие полиса не давало человеку никакой новой информации.

Поэтому задача состояла в том, чтобы сделать страницу — и сначала разработать endpoint, который сможет предоставить для неё нужные данные.

Ответы находятся в двух разных местах

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

Первый знает кто вы в рамках полиса: номер медицинской карты, программу, работодателя, даты действия договора и — если это семейная программа — родственников, которые также застрахованы. Второй знает что происходило по этой карте: страховую сумму, сколько уже потрачено и все обращения в клиники.

Проблема в том, что второй сервис не разговаривает с нами, пока мы сначала не обратимся к первому. Он не принимает имя или номер карты — ему нужен внутренний ID объекта, который может предоставить только первый сервис. Поэтому endpoint специально работает последовательно: сначала находим человека, и только если это удалось — запрашиваем информацию о расходах. Если человек не найден, второй запрос вообще не выполняется.

Чей именно PINFL?

Первый сервис находится во внутренней сети и практически ничего не проверяет. Передайте ему национальный ID — и он вернёт имя человека, его работодателя и медицинскую историю. Причём данные можно получить для любого человека.

Поэтому одно архитектурное решение стало принципиальным: идентификатор пользователя берётся из авторизованной сессии, а не из запроса. Браузер вообще не может указать, данные какого человека он хочет получить. Он не отправляет никакой идентификатор — сервер сам получает профиль текущего пользователя и использует его данные.

Сначала используется национальный ID, а паспорт — только как fallback, потому что национальный ID не меняется, а паспорт можно переоформить. Небольшая деталь, которая не позволяет медицинской карте внезапно исчезнуть после того, как человек получил новый паспорт.

Арифметика, которую легко посчитать неправильно

Самая интересная часть этой работы была связана с деньгами — и именно здесь очевидный подход оказывается неправильным.

У медицинского полиса есть общая страховая сумма, а внутри неё — список покрываемых рисков, у каждого из которых есть собственный лимит: амбулаторное лечение до X, стоматология до Y, лекарства до Z. Интуитивный способ показать «сколько у меня осталось?» — сложить все эти лимиты.

Если сделать это, вы получите примерно в два раза больше реального покрытия. Лимиты отдельных рисков входят в общую страховую сумму, а не складываются с ней. Это ограничения на то, какую часть общего бюджета можно потратить на конкретную категорию, а не дополнительные деньги.

Поэтому для расчёта используются общие показатели самого полиса: страховая сумма, уже выплаченная сумма, сумма, зарезервированная для обращений, которые ещё обрабатываются, и остаток — то есть страховая сумма минус выплаченная и зарезервированная суммы, но не ниже нуля. Зарезервированные деньги нельзя считать доступными для использования, поэтому незавершённое обращение уменьшает сумму, которую пользователь видит как доступный остаток. Это честнее и защищает человека от планирования расходов на деньги, которые уже фактически заняты.

Лимиты отдельных рисков при этом всё равно отображаются, потому что они важны. Например, человек должен видеть, что у стоматологии есть отдельный лимит, даже если общая страховая сумма ещё полностью доступна. Но эти лимиты не складываются в общий баланс.

Два списка, потому что ноль не означает «ничего»

Некоторые покрываемые риски приходят с лимитом, равным нулю. Это не означает «не покрывается». Это означает «покрывается без денежного лимита» — например, ежегодный профилактический осмотр или прививка от гриппа.

Если просто показать пользователю «0 сум», это выглядит как отсутствие покрытия, хотя смысл совершенно противоположный. Поэтому endpoint возвращает два списка: риски с конкретным денежным лимитом и включённые услуги без денежного лимита. Данные остаются теми же, но разделяются в зависимости от смысла числа. Благодаря этому интерфейс никогда не создаёт впечатление, что бесплатная услуга — это бесполезная услуга.

Названия, которые кто-то когда-то написал вручную

Названия рисков приходят от страховщика как обычный русский текст, который вводился отдельно для каждой корпоративной программы. Отсюда всё: разная пунктуация, разный регистр букв, случайные грамматические ошибки. Разные работодатели могут описывать одно и то же покрытие разными словами, а отдельного кода, по которому можно было бы однозначно сопоставить их, нет.

Переводить такие названия по отдельным словам оказалось худшим вариантом: половина строки могла оказаться на узбекском, а половина — на русском. Поэтому сопоставление теперь работает с полной нормализованной фразой: вся фраза сопоставляется с определённым набором понятий, а для каждого понятия есть корректный перевод на трёх языках. Если встречается программа, которую мы раньше никогда не видели, она ни с чем не сопоставляется и показывается ровно так, как пришла от страховщика — на русском языке, без изменений. Непереведённый текст лучше, чем наполовину переведённый.

Страница, которая наконец начала отвечать

После этого страница перестала быть просто информационной. Теперь прямо из неё можно отправить заявку на запись к врачу: выбрать регион, клинику, специалиста, описать проблему и указать время. Заявка отправляется операторам, которые занимаются её обработкой.

Но сразу возник очевидный вопрос: а что происходит с заявкой после отправки? Форма, которую можно отправить, но потом нигде не увидеть, — это не полноценная функция.

Поэтому endpoint теперь возвращает и эти заявки вместе с медицинскими посещениями. При этом заявки привязаны именно к медицинской карте, а не к аккаунту, с которого их отправили — потому что одна карта может покрывать целую семью, и заявка, созданная для родственника, всё равно должна отображаться в рамках этой карты. Уже состоявшиеся посещения и ожидающие обработки заявки объединяются в один список, отсортированный от новых к старым, с отдельной отметкой типа записи.

Один запрос, одно хранилище

На frontend всё это загружается одним запросом, а полученные данные хранятся в одном общем store. Если пользователь переходит сюда из списка полисов, дополнительный запрос не нужен — данные уже загружены. Если пользователь сразу открывает страницу напрямую, данные загружаются один раз. Всё, чему нужна медицинская карта — сама страница, форма записи и проверка возможности записаться — использует одну и ту же копию данных.

Два правила помогают сохранять это безопасным. Store очищается при выходе из аккаунта, потому что кеш, переживший сессию, может показать медицинскую историю предыдущего пользователя. А для блока медицинской карты установлен жёсткий timeout: это всего лишь одна часть страницы, и она никогда не должна блокировать загрузку всего кабинета.

Ошибки должны быть пропорциональны проблеме

Отсутствие корпоративной медицинской страховки — это нормальная ситуация, большинство пользователей её не имеют. Поэтому «медицинской карты нет» — это не ошибка. Это успешный ответ, в котором просто нет данных. Страница так и сообщает пользователю, вместо того чтобы показывать сообщение об ошибке.

Если сервис с информацией о расходах временно недоступен, но сервис с данными пользователя работает, страница всё равно отображается: медицинская карта, программа, даты действия. Блок с балансом заменяется коротким сообщением о том, что цифры временно недоступны, а само страховое покрытие продолжает действовать. Если недоступны цифры, пользователь должен потерять только цифры, а не всю страницу.

Почему я доволен результатом

Страница превратилась из просто названия в списке в полноценный инструмент: что покрывает страховка, сколько уже использовано, сколько осталось с учётом обращений, которые ещё обрабатываются, и как записаться к врачу.

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

Что на самом деле осталось на вашей медицинской карте · Axror