#Первый приём

Соберите конфигурацию Launch + push. В карточке приёма есть кнопка «smartica.ai». smartica.ai возвращает протокол в МИС.

Сначала прочитайте Обзор.

Если у вас есть ИИ-агент с доступом к репозиторию, поручите агенту черновик. Промпт и план приведены в статье Промпты для ИИ-агента.

#Что понадобится

  • Кнопка в карточке приёма вашей МИС.
  • HTTP endpoint, доступный серверам smartica.ai по HTTPS.
  • Параметры подключения. smartica.ai выдаёт параметры на шаге 1.

#Шаг 1. Получите параметры подключения

Пока smartica.ai не зарегистрирует вашу клинику, любой запрос вернёт 404.

Напишите на support@smartica.ai или откройте статью Поддержка. Передайте:

  • название МИС и клиники
  • адреса тестового и рабочего окружений вашей МИС
  • контакт разработчика
  • схему авторизации для входящих запросов от smartica.ai

В ответ вы получите:

Что Зачем
platform Идентификатор клиники в адресах запросов к smartica.ai
Точный SSO endpoint Адрес запроса ссылки для входа врача
API-ключ клиники Секрет для авторизации в smartica.ai
Параметры окружения Адрес тестового стенда и сетевые требования

Подробности смотрите в статье Подключение.

smartica.ai выдаёт ключ на тестовый контур. Адреса контуров указаны в статье Тестовая площадка. Примеры ниже написаны для рабочего адреса. На тестовом контуре отличается только хост.

Пока ждёте ответ, пишите код шагов 2 и 3. Этот код не зависит от выданных параметров.

#Шаг 2. Сделайте endpoint в своей МИС

Для старта smartica.ai вызывает у вас только PUT. Бланк уходит в запросе ссылки на шаге 3. GET нужен, если в этом запросе нет template и fields.

PUT  /api/for-smartica/encounters/{encounter_id}   → принять заполненный протокол
GET  /api/for-smartica/encounters/{encounter_id}   → отдать бланк, если он не ушёл в запросе ссылки

Адрес выбираете вы. Согласуйте адрес со smartica.ai. По умолчанию защищайте эти методы Bearer-токеном поверх HTTPS. Для совместимости допустим HTTP Basic. mTLS и подпись запроса требуют доработки. Согласуйте mTLS и подпись до начала разработки. Сравнение вариантов смотрите в разделе Какую схему выбрать.

#GET — отдаёте контекст приёма

Этот метод нужен, только если бланк не передаёте в запросе ссылки. smartica.ai запрашивает название бланка и список полей.

Ответ должен быть 200 и JSON:

{
  "encounter_id": "12345",
  "user_email": "doctor@clinic.ru",
  "template": "Кардиолог",
  "fields": [
    { "id": "complaints", "label": "Жалобы" },
    { "id": "diagnosis_primary", "label": "Диагноз (основной)" }
  ]
}

id — технический ключ поля в вашей МИС. label — заголовок для заполнения smartica.ai. Обычно это подпись поля на бланке врача. Если подпись понятна только вместе с разделом выше, опишите поле целиком. «Жалобы» уже полный заголовок. «Норма» без раздела не объясняет поле.

Полный контракт описан в статье Бланк приёма.

#PUT — принимаете результат

Когда протокол готов, smartica.ai присылает:

{
  "encounter_id": "12345",
  "fields": [
    { "id": "complaints", "value": "Жалобы на головную боль..." },
    { "id": "diagnosis_primary", "value": "Гипертоническая болезнь I ст." }
  ],
  "transcript": "Полная стенограмма разговора..."
}

Разложите value по полям с теми же id. Сохраните стенограмму.

Три правила, которые чаще всего нарушают:

  1. Поля, которых нет в массиве, не очищайте. smartica.ai присылает только поля, для которых нашлось содержание. Пустой массив fields валиден. Стенограмму всё равно сохраните.
  2. Повторный такой же запрос не должен создавать вторую карточку. Обновляйте существующий приём по encounter_id. Повторы приходят. Врач может дописать приём или нажать «отправить заново».
  3. Отвечайте 200 с JSON-телом. Не отвечайте 204, пустым телом или HTML.

Полный контракт описан в статье Запись протокола в карту.

#Пример реализации

Ниже минимальный рабочий вариант на Node.js с Express. Логика переносится на любой стек.

const express = require('express');
const app = express();

app.use(express.json({ limit: '5mb' }));

const crypto = require('crypto');

// Проверка Bearer-токена. Токен вы генерируете сами и сообщаете нам при подключении.
function requireSmarticaAuth(req, res, next) {
  const header = req.headers.authorization || '';
  const expected = `Bearer ${process.env.SMARTICA_INBOUND_TOKEN}`;

  // Сравнение за постоянное время, чтобы токен нельзя было подобрать по таймингу.
  const a = Buffer.from(header);
  const b = Buffer.from(expected);

  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).json({ message: 'Неверный токен' });
  }
  next();
}

// GET нужен, только если бланк не ушёл в запросе ссылки.
app.get('/api/for-smartica/encounters/:id', requireSmarticaAuth, async (req, res) => {
  const encounter = await db.findEncounter(req.params.id);

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

  res.json({
    encounter_id: String(encounter.id),
    user_email: encounter.doctorEmail,
    template: encounter.formName,
    fields: encounter.formFields.map((f) => ({ id: f.key, label: f.title })),
  });
});

// PUT — принимаем заполненный протокол.
app.put('/api/for-smartica/encounters/:id', requireSmarticaAuth, async (req, res) => {
  const { encounter_id: bodyId, fields = [], transcript } = req.body;

  if (bodyId !== req.params.id) {
    return res.status(422).json({ message: 'ID в адресе и в теле не совпадают' });
  }

  const encounter = await db.findEncounter(req.params.id);
  if (!encounter) {
    return res.status(404).json({ message: 'Приём не найден' });
  }

  await db.transaction(async (tx) => {
    // Обновляем только пришедшие поля. Остальные не трогаем.
    for (const field of fields) {
      await tx.upsertProtocolField(encounter.id, field.id, field.value);
    }
    await tx.saveTranscript(encounter.id, transcript);
  });

  res.json({ encounter_id: String(encounter.id), status: 'saved' });
});

app.listen(3000);

До подключения smartica.ai проверьте GET курлом, если бланк не передаёте в запросе ссылки:

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

#Шаг 3. Добавьте кнопку «smartica.ai» и серверный SSO

Кнопка не ведёт на smartica.ai напрямую. Сделайте так:

  1. Браузер передаёт вашему бэкенду только ID текущего приёма.
  2. Бэкенд проверяет, что этот врач имеет доступ к этому приёму.
  3. Бэкенд берёт email и ФИО врача из своей базы. Не принимайте email и ФИО от браузера.
  4. Бэкенд запрашивает у smartica.ai одноразовую ссылку.
  5. Бэкенд перенаправляет браузер на полученную ссылку.

Не передавайте ключ клиники в JavaScript.

Запрос за ссылкой:

POST https://app.smartica.ai/api/v1/integrations/{platform}/sso/token
Authorization: Bearer <API-ключ клиники>
Content-Type: application/json
Accept: application/json

{
  "user_email": "doctor@clinic.ru",
  "user_full_name": "Иванов Иван Иванович",
  "encounter_id": "12345",
  "template": "Кардиолог",
  "fields": [
    { "id": "complaints", "label": "Жалобы" },
    { "id": "diagnosis_primary", "label": "Диагноз (основной)" }
  ]
}

Передавайте user_full_name всегда. Для уже существующего врача поле не обязательно. При первом входе без user_full_name smartica.ai не создаст аккаунт и вернёт 422.

Ответ:

{
  "launch_url": "https://app.smartica.ai/launch?platform=…&encounter_id=12345&t=…",
  "expires_in": 60
}

Сразу откройте launch_url врачу. Используйте редирект 302 или новую вкладку. Ссылка одноразовая. Ссылка живёт столько секунд, сколько указано в expires_in.

Не разбирайте этот URL. Не собирайте URL самостоятельно. Не сохраняйте параметр t. Не логируйте ссылку целиком.

Пример на Node.js:

app.post('/encounters/:id/smartica-launch', requireDoctorAuth, async (req, res) => {
  const encounter = await db.findEncounter(req.params.id);

  // Врач берётся из сессии МИС, а не из тела запроса.
  if (!encounter || !canAccess(req.user, encounter)) {
    return res.status(403).json({ message: 'Нет доступа к приёму' });
  }

  const response = await fetch(process.env.SMARTICA_SSO_ENDPOINT, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SMARTICA_SSO_KEY}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify({
      user_email: req.user.email,
      user_full_name: req.user.fullName,
      encounter_id: String(encounter.id),
      template: encounter.formName,
      fields: encounter.formFields.map((field) => ({ id: field.key, label: field.title })),
    }),
  });

  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    return res.status(502).json({ message: error.message || 'Smartica недоступна' });
  }

  const { launch_url: launchUrl } = await response.json();
  res.redirect(302, launchUrl);
});

Коды ошибок и остальные поля запроса описаны в статье Ссылка для входа врача.

#Если бланк не передать в запросе ссылки

Уберите template и fields из запроса выше и реализуйте GET из шага 2. smartica.ai заберёт бланк сама.

#Шаг 4. Прогоните один контрольный приём

Проверяйте по порядку. Так видно, на каком шаге произошла ошибка.

  1. Создайте тестовый приём с уникальным encounter_id.
  2. Если бланк не в запросе ссылки, вызовите свой GET курлом. Убедитесь, что ответ 200 и JSON корректен.
  3. Откройте карточку приёма в МИС. Нажмите «smartica.ai».
  4. Убедитесь, что врач попал в smartica.ai без повторного ввода логина и пароля.
  5. Запишите короткий разговор. Остановите запись. Дождитесь обработки.
  6. Проверьте, что пришёл PUT. В PUT есть значения полей и полная стенограмма.
  7. Отправьте тот же PUT повторно курлом. Убедитесь, что в МИС остался один обновлённый приём.
  8. Добавьте ещё одну запись в тот же приём. Проверьте, что результат обновился и не продублировался.

Если что-то не сработало, откройте Если запросы не проходят.

#Чеклист готовности

  • Получены platform, точный SSO endpoint и API-ключ клиники
  • encounter_id передаётся строкой. encounter_id не переиспользуется для другого приёма
  • SSO вызывается только с бэкенда. Ключ отсутствует во фронтенде и в логах
  • В запросе ссылки есть template и непустой fields. Если бланка в запросе нет, GET возвращает стабильный template, уникальные id и понятные label
  • Email врача в SSO относится к врачу этого приёма. Если есть GET, email в нём тот же
  • PUT обновляет существующий приём. PUT не создаёт дубликаты
  • Поля, отсутствующие в PUT, не затираются
  • Успешные ответы содержат JSON. 204 No Content не используется
  • Тексты ошибок не содержат стектрейсов, SQL и секретов

Расширенный чеклист перед боевым запуском приведён в статье Подключение.

#Что дальше

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