#Ваш API: контекст приёма (GET)

Этот endpoint реализует ваша МИС. Smartica вызывает его, когда врач открывает приём, и спрашивает: «какой это бланк и какие в нём поля?»

От качества ответа напрямую зависит качество заполнения протокола, поэтому статью стоит прочитать целиком, а не только посмотреть на пример JSON.

#Когда этот endpoint нужен

Только в сценарии Launch и только если вы не передаёте бланк прямо в SSO-запросе.

Ваша ситуация Нужен ли GET
Launch, бланк не передаётся в SSO Да
Launch, template и fields переданы в SSO Нет
Import Нет, никогда — бланк передаётся при привязке

Если endpoint вам не нужен, переходите сразу к PUT: запись протокола.

#Адрес

Выбираете вы, согласовываете при онбординге. Рекомендуемый вид:

https://mis.example.ru/api/for-smartica/encounters/{encounter_id}

#Что присылает Smartica

GET /api/for-smartica/encounters/12345 HTTP/1.1
Host: mis.example.ru
Authorization: Bearer <токен>
Accept: application/json

Заголовок зависит от схемы, выбранной при подключении: по умолчанию это Bearer, для совместимости возможен HTTP Basic. Подробнее: Авторизация, ошибки и повторная доставка.

12345 — тот самый encounter_id, который ваш бэкенд передал в SSO-запросе.

Мы ждём ответ не дольше 30 секунд. Это верхний предел, а не ориентир: endpoint должен просто прочитать готовые данные приёма. Не запускайте здесь тяжёлые вычисления и не дёргайте внешние сервисы.

#Что должна вернуть ваша МИС

Код 200, заголовок Content-Type: application/json; charset=utf-8 и JSON-объект:

{
  "encounter_id": "12345",
  "user_email": "doctor@clinic.ru",
  "template": "Кардиолог",
  "fields": [
    { "id": "complaints", "label": "Жалобы" },
    { "id": "anamnesis_life", "label": "Anamnesis vitae (анамнез жизни)" },
    { "id": "diagnosis_primary", "label": "Диагноз (основной)" }
  ]
}
Поле Тип Что в нём
encounter_id строка Должен совпадать с ID из адреса и из SSO-запроса
user_email строка Email врача, который ведёт этот приём
template строка Непустое стабильное название бланка или специальности
fields массив Непустой список полей протокола
fields[].id строка Непустой, уникальный, неизменный технический ключ
fields[].label строка Непустое однозначное название поля

Технически Smartica умеет обойтись без encounter_id в теле (возьмёт из адреса) и без user_email — это оставлено для совместимости со старыми подключениями. Для новой интеграции возвращайте оба поля: они позволяют вовремя заметить, что приём и врач не совпадают с ожидаемыми.

Здесь особенно важна уникальность fields[].id: на этот запрос мы отвечаем не вам, а врачу, поэтому предупредить в ответе нам некуда. Если в бланке встретятся поля с одинаковым id, мы возьмём первое вхождение и молча пропустим остальные — см. Дубликаты id в бланке.

#Пример обработчика

Код без привязки к фреймворку: repository — ваш слой доступа к данным, urlId приходит из маршрута. Авторизацию считаем уже проверенной.

$encounter = $repository->findByExternalId($urlId);

if ($encounter === null) {
    return jsonResponse(404, ['message' => 'Приём не найден']);
}

$fields = [];

foreach ($encounter->form->fields as $field) {
    $fields[] = [
        'id' => $field->key,      // стабильный и уникальный
        'label' => $field->title, // так, как поле называет врач
    ];
}

return jsonResponse(200, [
    'encounter_id' => (string) $encounter->externalId,
    'user_email' => $encounter->doctor->email,
    'template' => $encounter->form->name,
    'fields' => $fields,
]);
const encounter = await repository.findByExternalId(urlId);

if (!encounter) {
  return res.status(404).json({ message: 'Приём не найден' });
}

res.json({
  encounter_id: String(encounter.externalId),
  user_email: encounter.doctor.email,
  template: encounter.form.name,
  fields: encounter.form.fields.map((field) => ({
    id: field.key,      // стабильный и уникальный
    label: field.title, // так, как поле называет врач
  })),
});
encounter = repository.find_by_external_id(url_id)

if encounter is None:
    return json_response(404, {"message": "Приём не найден"})

return json_response(200, {
    "encounter_id": str(encounter.external_id),
    "user_email": encounter.doctor.email,
    "template": encounter.form.name,
    "fields": [
        {
            "id": field.key,      # стабильный и уникальный
            "label": field.title, # так, как поле называет врач
        }
        for field in encounter.form.fields
    ],
})
var encounter = await repository.FindByExternalIdAsync(urlId);

if (encounter is null)
{
    return Results.Json(new { message = "Приём не найден" }, statusCode: 404);
}

var fields = encounter.Form.Fields
    .Select(field => new
    {
        id = field.Key,     // стабильный и уникальный
        label = field.Title // так, как поле называет врач
    })
    .ToArray();

return Results.Json(new
{
    encounter_id = encounter.ExternalId.ToString(),
    user_email = encounter.Doctor.Email,
    template = encounter.Form.Name,
    fields
});
Encounter encounter = repository.findByExternalId(urlId);

if (encounter == null) {
    return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(Map.of("message", "Приём не найден"));
}

List<Map<String, String>> fields = encounter.getForm().getFields().stream()
        .map(field -> Map.of(
                "id", field.getKey(),     // стабильный и уникальный
                "label", field.getTitle() // так, как поле называет врач
        ))
        .toList();

return ResponseEntity.ok(Map.of(
        "encounter_id", String.valueOf(encounter.getExternalId()),
        "user_email", encounter.getDoctor().getEmail(),
        "template", encounter.getForm().getName(),
        "fields", fields
));
Функция EncounterGET(Запрос)

    ИдентификаторИзURL = Запрос.ПараметрыURL["id"];
    Приём = Приёмы.НайтиПоВнешнемуИдентификатору(ИдентификаторИзURL);

    Если Приём = Неопределено Тогда
        Возврат ОтветСОшибкой(404, "Приём не найден");
    КонецЕсли;

    Поля = Новый Массив;
    Для Каждого Поле Из Бланки.ПолучитьПоля(Приём.Бланк) Цикл
        Элемент = Новый Структура("id, label", Поле.Ключ, Поле.Наименование);
        Поля.Добавить(Элемент);
    КонецЦикла;

    Результат = Новый Структура;
    Результат.Вставить("encounter_id", ИдентификаторИзURL);
    Результат.Вставить("user_email", Приём.Врач.Email);
    Результат.Вставить("template", Приём.Бланк.Наименование);
    Результат.Вставить("fields", Поля);

    Запись = Новый ЗаписьJSON;
    Запись.УстановитьСтроку();
    ЗаписатьJSON(Запись, Результат);

    Ответ = Новый HTTPСервисОтвет(200);
    Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
    Ответ.УстановитьТелоИзСтроки(Запись.Закрыть());

    Возврат Ответ;

КонецФункции

Обратите внимание на приведение encounter_id к строке: если в вашей базе это число, без явного преобразования в JSON уйдёт 12345 вместо "12345".

#Правила encounter_id и user_email

  • encounter_id в теле и в адресе должны совпадать посимвольно.
  • Передавайте ID строкой, даже если в базе это число.
  • user_email должен относиться к тому же врачу, который вошёл через SSO.
  • Регистр email не учитывается.
  • При несовпадении email мы фиксируем предупреждение, а в строгой конфигурации блокируем обработку.

Несовпадение email — распространённая ошибка. Она возникает, когда в карточке приёма записан один врач, а кнопку нажал другой — например, ординатор или дежурный. Решите на своей стороне, чей email считать правильным, и передавайте его в SSO и в GET одинаково.

#Правила template

template задаёт клинический контекст: по нему Smartica понимает, протокол какой специальности заполняется, и какие формулировки уместны в осмотре и профильных разделах.

Хорошие примеры:

Значение Когда использовать
"Кардиолог" Один общий бланк кардиолога
"Первичный приём педиатра" Отдельный бланк первичного приёма
"Повторный приём педиатра" Другой набор или другой смысл полей
"УЗИ органов брюшной полости" Специализированный протокол исследования

Два правила:

  1. Для одного бланка всегда возвращайте одно и то же значение. Не меняйте регистр, язык и синонимы от приёма к приёму.
  2. Разделяйте разные бланки разными названиями. Если первичный и повторный приём отличаются по составу полей, это два разных template, а не один.

#Правила fields

Разберитесь с разницей: id нужен вам, label нужен нам.

id — технический ключ. По нему вы разложите пришедшие значения обратно по своим полям. Smartica просто вернёт его без изменений.

label — то, по чему Smartica определяет смысл поля. Мы читаем название как инструкцию: что именно из разговора должно оказаться в этом поле. Плохое название — плохое заполнение, других источников информации о поле у нас нет.

Требования:

  1. Возвращайте минимум одно поле.
  2. Все id уникальны внутри ответа.
  3. Не меняйте id существующего поля после запуска интеграции — сломается сопоставление.
  4. Передавайте только текстовые поля: отдельного указания типа в контракте нет.
  5. Формулируйте label так, как поле называет врач.

Примеры:

Оценка id label Почему
Хорошо "complaints" "Жалобы" Назначение однозначно
Хорошо "objective_status" "Объективный статус (status praesens)" Есть медицинский контекст
Хорошо "diagnosis_primary" "Диагноз (основной)" Не спутать с сопутствующим
Плохо "field_3" "Статус" Ни ключ, ни название не объясняют смысл
Плохо "1" "Данные" Невозможно понять, что класть в поле

Если в бланке есть похожие поля, различайте их в названии явно. Не "Диагноз" и "Диагноз 2", а "Диагноз (основной)" и "Диагноз (сопутствующий)".

Максимум 80 полей в одном бланке. Остальные ограничения — в статье Лимиты и ограничения.

#Каких данных быть не должно

Не добавляйте в ответ ФИО пациента, номер карты, телефон, адрес, дату рождения и другие демографические поля, если это не согласовано отдельно.

Smartica работает с содержанием разговора. Привязка к пациенту полностью остаётся у вас — через ваш собственный encounter_id. Дополнительные персональные данные ничего не улучшат, но расширят объём передаваемой чувствительной информации.

Подробнее: Защита данных.

#Ошибки

Возвращайте подходящий код и JSON с безопасным сообщением:

{
  "message": "Приём не найден"
}
Код Когда использовать
401 Учётные данные отсутствуют или неверны
403 Учётные данные верны, но доступ к этому ресурсу запрещён
404 Приём с таким ID не найден
409 Приём существует, но находится в состоянии, несовместимом с интеграцией
422 ID корректен по формату, но запрос нельзя обработать
5xx Временная ошибка на вашей стороне

Любой ответ не из диапазона 2xx, а также timeout, пустое тело или невалидный JSON означают, что подготовка приёма не удалась и врач не сможет начать запись.

Автоматический повтор не гарантирован. После того как вы исправите endpoint, врачу достаточно открыть приём заново.

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

#Проверка через curl

curl --fail-with-body --silent --show-error \
  --header 'Authorization: Bearer ЗАМЕНИТЕ-НА-ТОКЕН' \
  --header 'Accept: application/json' \
  'https://mis.example.ru/api/for-smartica/encounters/12345'

Убедитесь, что ответ:

  • имеет код 200;
  • содержит Content-Type: application/json;
  • разбирается как JSON-объект;
  • содержит непустые template и fields;
  • не меняет id и названия полей между двумя одинаковыми запросами.

Последний пункт важен: если состав полей плавает от запроса к запросу, сопоставление результата сломается.

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

#Связанные статьи

← Все статьи: Интеграция с МИС Поиск по базе знаний