#Ваш 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 понимает, протокол какой специальности заполняется, и какие формулировки уместны в осмотре и профильных разделах.
Хорошие примеры:
| Значение | Когда использовать |
|---|---|
"Кардиолог" |
Один общий бланк кардиолога |
"Первичный приём педиатра" |
Отдельный бланк первичного приёма |
"Повторный приём педиатра" |
Другой набор или другой смысл полей |
"УЗИ органов брюшной полости" |
Специализированный протокол исследования |
Два правила:
- Для одного бланка всегда возвращайте одно и то же значение. Не меняйте регистр, язык и синонимы от приёма к приёму.
- Разделяйте разные бланки разными названиями. Если первичный и повторный приём отличаются по составу полей, это два разных
template, а не один.
#Правила fields
Разберитесь с разницей: id нужен вам, label нужен нам.
id — технический ключ. По нему вы разложите пришедшие значения обратно по своим полям. Smartica просто вернёт его без изменений.
label — то, по чему Smartica определяет смысл поля. Мы читаем название как инструкцию: что именно из разговора должно оказаться в этом поле. Плохое название — плохое заполнение, других источников информации о поле у нас нет.
Требования:
- Возвращайте минимум одно поле.
- Все
idуникальны внутри ответа. - Не меняйте
idсуществующего поля после запуска интеграции — сломается сопоставление. - Передавайте только текстовые поля: отдельного указания типа в контракте нет.
- Формулируйте
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и названия полей между двумя одинаковыми запросами.
Последний пункт важен: если состав полей плавает от запроса к запросу, сопоставление результата сломается.
Проверяйте с внешней машины, а не из внутренней сети — иначе легко не заметить проблему с доступностью или сертификатом.