Привет, Хабр. В прошлых статьях рассказывал про Telegram-бота на Gemini и внедрение AI во все поля ввода Windows. Сегодня история другая: личная, экспериментальная. Я написал медицинский сервис для своей семьи, подключил к нему Claude Opus и честно не знаю чем всё закончится. Но пока результат интереснее чем я ожидал.

Быстрая предыстория зачем вообще. У сына задержка речевого развития, за два года мы прошли кучу специалистов: неврологи, психиатры, логопеды, дефектологи. У каждого своя карта, свои записи, своё мнение. И в какой-то момент я понял две вещи. Первая: жена, которая занимается этим 24/7, физически не может удержать всё в голове. Вторая: врачи между собой не разговаривают, и никто из них не видит полную картину.

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

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


Что на данный момент получилось

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

Ключевое отличие от «просто спросить ChatGPT» я понял на жене. Она пользуется бесплатной версией, говорила «мне хватает». И это правда, для простых вопросов хватает. Но когда у тебя накопилась папка документов за пять лет, а ты спрашиваешь про новый анализ, нейросеть в чате уже забыла что три приёма назад другой врач назначил другой препарат. Контекст не резиновый. По мере диалога нюансы отсеиваются.

У меня каждый раз Claude получает полный контекст: все диагнозы, все препараты, все анализы, всю хронологию, все связи между ними. За один запрос. Ничего не теряется между сеансами. Это не чат, это персистентная база знаний с нейросетью в роли аналитика. Разницу объясню подробнее чуть ниже, там интересный момент с архитектурой.

И ещё одно важное: AI в этом сервисе не ставит диагнозов. Он на языке фактов объясняет что было сделано правильно, что не было сделано, и почему это важно. Никаких «у вашего ребёнка скорее всего», только «врач N не назначил анализ X, хотя при диагнозе Y это стандартный контроль по международным гайдлайнам». Разница принципиальная.


Главный экран

Когда открываешь приложение, встречает PIN-экран. Если на этом устройстве настроена биометрия, можно войти через Face ID или Windows Hello за секунду, иначе 6 цифр. Про то как это всё устроено под капотом будет отдельная большая часть про безопасность.

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

Дашборд с AI-резюме: красным приоритеты, синим план действий, оранжевым что мониторить.
Дашборд с AI-резюме: красным приоритеты, синим план действий, оранжевым что мониторить.

AI-резюме это структурированная сводка которую нейросеть написала после анализа всей базы: текущие приоритеты, план действий, на что обратить внимание. Обновляется при загрузке нового документа.

Остальные страницы: план обследований (можно перетаскивать пункты мышкой чтобы менять приоритет), список врачебных ошибок и несостыковок, отдельная страница со всеми диагнозами, документы (визиты к врачам + standalone-документы типа лаб.результатов и МРТ в одной ленте с фильтрами), карта здоровья про которую будет отдельная часть, AI-чат, и раздел «ещё» где всё остальное — специалисты, прививки, рост-вес, глобальный поиск по базе.

План обследований: каждый пункт с приоритетом. Ошибки и несостыковки: что врачи или я пропустили, почему это важно, и что с этим делать
План обследований: каждый пункт с приоритетом. Ошибки и несостыковки: что врачи или я пропустили, почему это важно, и что с этим делать

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


Карта здоровья и почему связи важнее хранения

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

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

Я смотрел графовые БД: SurrealDB, Kuzu, Neo4j. Все красивые, все со своими фишками. И в какой-то момент понял: у меня 100 сущностей и 300 связей. Это не тот масштаб где графовая БД нужна. Проблема не в движке, проблема в схеме данных.

Решение оказалось простым: связующие таблицы в том же SQLite.

-- Одна строка описывает всю цепочку: врач → назначил → препарат → от диагноза → на приёме
CREATE TABLE prescriptions (
    medication_id  REFERENCES medications(id),
    diagnosis_id   REFERENCES diagnoses(id),
    specialist_id  REFERENCES specialists(id),
    timeline_id    REFERENCES timeline(id),
    prescribed_date TEXT,
    dosage TEXT,
    rationale TEXT
);

Теперь у каждой связи есть явный ID. И стал возможен такой SQL:

-- Какие препараты назначил конкретный невролог, и какие из них потом отменили
SELECT m.name, m.status, m.stop_reason
FROM prescriptions p
JOIN medications m  ON m.id = p.medication_id
JOIN specialists s  ON s.id = p.specialist_id
WHERE s.id = 6
  AND m.status = 'cancelled';

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

А поверх реляционной базы я прикрутил визуализацию на Cytoscape.js. Узлы: диагнозы, врачи, препараты, приёмы, ошибки. Рёбра: кто что назначил, от какого диагноза, на каком приёме. Клик на узел подсвечивает только его связи, остальное тускнеет. Выглядит это как-то так:

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

AI-координатор: как это работает без вызовов API

В сервисе нет прямой интеграции с API нейросети. Вообще нет. Ни строчки fetch('https://api.anthropic.com/...'). Никакого @anthropic-ai/sdk в зависимостях. Никаких API-ключей в .env.

«А как тогда AI работает»?

На сервере лежит файл service_instructions.md: большой документ с полным протоколом работы нейросети с этой базой. Как оформлять оценку визита, как строить план обследований, в какой формат записывать результат анализа, как искать противоречия. Это, по сути, промпт для Claude, только в 50+ КБ и с конкретными SQL-правилами.

А дальше есть два механизма.

Первый: очередь запросов. В базе лежит таблица ai_requests. Когда я в интерфейсе нажимаю «Запросить AI-анализ» на любой сущности (диагноз, визит, документ, ошибка), в эту таблицу добавляется запись со статусом pending. Просто запись. Без отправки куда-либо.

Второй: единая точка контекста. Эндпоинт GET /api/patient-context. Он возвращает ВСЁ что знает база про пациента: диагнозы вместе с врачами которые их поставили, препараты с назначениями и причинами отмены, приёмы со всеми связанными документами, анализы с референсными значениями и отметками об отклонениях, ошибки с резолюциями, план со статусами, всю историю изменений за последние 30 дней. Один JSON на 200-500 килобайт. Одной строкой Claude получает полную картину которую ни один врач в принципе не способен охватить за 15 минут приёма.

Дальше цикл такой:

  1. Я загружаю новый документ или анализ

  2. Открываю Claude Code и говорю: «посмотри инструкцию, есть новый документ»

  3. Claude читает service_instructions.md, делает GET /api/patient-context, видит новый документ и очередь ai_requests

  4. Анализирует, сверяет с существующими данными, ищет противоречия и пропущенные обследования

  5. Пишет результаты напрямую через API: ai_assessment к сущностям, новые ошибки, обновления плана, выводы

  6. Помечает обработанные запросы как completed

Визуально весь цикл выглядит так:

┌──────────────────────────────────────────────────────────────────────┐
│                         МОЙ ТЕЛЕФОН / ДЕСКТОП                        │
│                                                                      │
│  1. Загружаю PDF/фото ──→  2. Нажимаю «Запросить AI-анализ»          │
│                                      │                               │
│                              ai_requests: status=pending             │
└──────────────────────────────────────┼───────────────────────────────┘
                                       │
                                       ▼
┌──────────────────────────────────────────────────────────────────────┐
│                         CLAUDE CODE (вкладка)                        │
│                                                                      │
│  3. Я: «посмотри инструкцию»                                         │
│         │                                                            │
│         ▼                                                            │
│  4. Claude читает service_instructions.md (~17K токенов)             │
│         │                                                            │
│         ▼                                                            │
│  5. GET /api/patient-context ──→ получает ВСЮ базу (~63K токенов)    │
│     7 диагнозов, 11 препаратов, 67 анализов, 14 приёмов,             │
│     12 документов, 11 ошибок, 21 пункт плана, все связи              │
│         │                                                            │
│         ▼                                                            │
│  6. Обрабатывает очередь ai_requests:                                │
│     • Читает новый документ (PDF → перерендер в 400 DPI → сверка)    │
│     • Сверяет с существующими данными в контексте                    │
│     • Ищет противоречия, пропущенные обследования                    │
│         │                                                            │
│         ▼                                                            │
│  7. Пишет результаты через API:                                      │
│     PUT  /api/diagnoses/3       → ai_assessment = "..."              │
│     POST /api/medical-errors    → новая ошибка severity=warning      │
│     PUT  /api/plan/12           → статус pending → done, outcome     │
│     POST /api/lab-results       → 15 строк из анализа                │
│     PUT  /api/ai-requests/7     → status = completed                 │
└──────────────────────────────────────────────────────────────────────┘

Полный цикл обработки нового документа: от загрузки до записи результатов в базу

И это работает. Работает гораздо лучше чем если бы я вкручивал туда вызовы API. Потому что у Claude в Claude Code уже есть инструменты (Read, Bash для запуска SQL, возможность делать HTTP-запросы), и он использует их гибко: увидел в PDF непонятный показатель, сам перерендерил его в высоком разрешении, сам прочитал, сам сверил с оригиналом.

Отдельно: в интерфейсе под любой сущностью (диагноз, визит, документ, ошибка) есть кнопка «Запросить AI-анализ». Нажимаешь, запись появляется в очереди ai_requests, и нейросеть при следующем обновлении видит что именно нужно проанализировать, не нужно объяснять словами. Та же история с комментариями: можно задать вопрос прямо под конкретным документом и получить ответ с учётом именно этого контекста.

Минус очевидный: это не автоматический режим. Мне нужно открыть Claude Code и сказать «посмотри инструкцию». Примерно раз в неделю, если появились новые документы. Можно было бы автоматизировать: поднять Claude CLI на сервере и повесить хук на загрузку документа, чтобы анализ запускался в реальном времени сразу после аплоада. Но пока мне спокойнее контролировать процесс вручную. Новые данные появляются не каждый день, открыть Claude Code раз в неделю не сложно.

Сколько это стоит

Раз уж мы про Claude, давайте про деньги. Я замерил реальный объём данных на примере карты сына (она сейчас самая заполненная):

  • service_instructions.md (инструкция): 51 КБ → ~17 000 токенов

  • patient-context (вся база одним JSON): 188 КБ → ~63 000 токенов

  • Итого на вход: ~80 000 токенов

На выходе Claude обычно генерирует 5-10 тысяч токенов: ai_assessment-ы, новые записи в план и ошибки, правки в анализах.

Если считать по ценам API через OpenRouter (Claude Opus 4.6: $5 за миллион входных токенов, $25 за миллион выходных):

  • Input: 80K токенов × 5 / M = 0.40$

  • Output: ~8K токенов × 25 / M = 0.20$

  • Итого: ~$0.60 за один полный анализ всей медкарты

Новые документы появляются 2-4 раза в месяц. То есть через API это обошлось бы в $1.5-2.5 в месяц.

Я использую подписку Claude Max за 100$/мес, но не ради этого сервиса, а потому что Claude у меня основной рабочий инструмент для всего: кода, текстов, анализа. Медицинский сервис просто едет в том же потоке.


Нейросеть ошибается

Однажды при обработке лабораторного отчёта Claude неправильно прочитал значение в PDF: строки шли плотно, число из соседней строки «перескочило», и в базу попало критическое отклонение которого на самом деле не было. Нейросеть честно построила на этих данных аналитику, завела ошибку, добавила пункт в план. Я заметил при проверке, попросил перепроверить, Claude сам нашёл расхождение и поправил все ошибочные выводы.

После этого я добавил в инструкцию обязательную двухэтапную проверку для всех документов с числами: первый проход в стандартном разрешении, второй с перерендером PDF в 400-500 DPI и построчной сверкой каждого числа с оригиналом. Только после полного совпадения данные уходят в базу.

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


Автоматическая история: 40 триггеров и ноль кода для логирования

Логирование изменений в сервисе устроено без единой строчки кода в роутах. Никаких await log('medication_added', ...). История изменений целиком работает через триггеры самой SQLite.

CREATE TRIGGER audit_medications_update
AFTER UPDATE ON medications
FOR EACH ROW
BEGIN
    INSERT INTO audit_log (patient_id, entity_type, entity_id, action, old_value, new_value)
    VALUES (NEW.patient_id, 'medications', NEW.id, 'update',
            json_object('name', OLD.name, 'status', OLD.status, 'dosage', OLD.dosage),
            json_object('name', NEW.name, 'status', NEW.status, 'dosage', NEW.dosage));
END;

Таких триггеров в базе 40 штук, на 13 медицинских таблицах, по три на каждую (insert/update/delete). Любая правка, хоть из React-фронтенда, хоть из админки, хоть прямо через sqlite3 danil.db в консоли: автоматически записывается в audit_log как старый JSON и новый JSON. Обойти нельзя, потому что триггер сидит на уровне базы, не на уровне приложения.

А дальше отдельный модуль changelog.js на сервере берёт сырой audit_log и превращает его в понятные человеку записи:

  • Если у ошибки status изменился с open на resolved, пишет «Решена ошибка: СОЭ 24, не учли»

  • Если правки одной сущности идут одна за другой в течение 60 секунд, группирует в одну запись

  • Группирует по дням: Сегодня / Вчера / 3 дня назад / 2026-04-05

На фронте это превращается в страницу «История». Создавалась она для того чтобы быстро увидеть что в базе изменилось после обработки очередного документа: какие напоминания добавились, какие выводы были переписаны, какие ошибки закрыты. Когда Claude правит базу, это тоже видно как отдельные записи с пометкой «автор: ai», и можно откатить если что-то пошло не так.

История изменений: любая правка в базе автоматически попадает в журнал через триггеры SQLite
История изменений: любая правка в базе автоматически попадает в журнал через триггеры SQLite

Стек и деплой

Сервис живёт на VPS: nginx + TLS снаружи, Node.js + better-sqlite3 внутри, systemd для автозапуска. Стандартная схема.

Про стек: фронт на React 19 + TypeScript strict + Vite, роутинг на React Router 7, данные через TanStack Query, формы на react-hook-form + zod, граф на Cytoscape, анимации на Motion, иконки из @tabler/icons-react, PWA через vite-plugin-pwa + Workbox. Бэкенд на Express + better-sqlite3, биометрия через @simplewebauthn/server. Без оверинжиниринга, без экзотики.

Все роутеры на бэкенде написаны в стиле PostgreSQL: pool.query('SELECT FROM patients WHERE id = $1', [id]). А в файле db.js живёт тонкая обёртка, которая на лету транслирует $1, $2 плейсхолдеры в ?, эмулирует RETURNING через отдельный SELECT, заменяет NOW() на datetime('now'). Формально база SQLite, но код написан так, что переключение на PostgreSQL заняло бы полчаса.


! Безопасность. Это стоит обсудить отдельно

Медицинские данные ребёнка это самое чувствительное что можно представить. Я не хочу чтобы кто-то их увидел, и не хочу чтобы база потерялась если что-то случится с сервером. Поэтому безопасность была приоритетом с первого дня.

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

Сеть и TLS

На VPS стоит UFW с default-deny: открыты только SSH (22), ACME challenge (80) и HTTPS (443). Fail2ban автоматически банит IP которые пытаются ломиться через SSH или сканируют сайт на предмет дыр. Три неудачные попытки за 10 минут → час бана, при повторных попытках срок растёт. Сам SSH принимает только ключи, никаких паролей (их нет), так что брутфорс в принципе невозможен.

TLS 1.2 и 1.3, старые протоколы выключены, сертификат от Let’s Encrypt с автопродлением. Плюс набор заголовков безопасности: HSTS на 2 года, CSP с запретом сторонних скриптов, X-Frame-Options: DENY, nosniff, Referrer-Policy, Permissions-Policy с отключением ненужных API (камера, микрофон, GPS и т.д.).

Три способа войти

В порядке предпочтения:

Биометрия (Face ID / Touch ID / Windows Hello). Работает через WebAuthn, тот же стандарт что у банков. Биометрический ключ хранится в Secure Enclave устройства, это специальный чип, к которому даже ядро ОС не имеет прямого доступа. Сервер видит только публичный ключ. Подписать запрос можно только физически разблокировав конкретное устройство конкретного человека.

PIN плюс контрольное слово. PIN это 4-10 цифр. В базе хранится не сам PIN, а scrypt-хеш. Восстановить PIN из хеша нельзя за разумное время даже теоретически. Сравнение constant-time, чтобы нельзя было угадать PIN по времени ответа сервера.

Контрольное слово спрашивается только когда заходишь с нового устройства. Устройство определяется по стабильному UUID в localStorage. На знакомых устройствах контрольное слово не спрашивают. После ответа устройство попадает в список доверенных, и в эту же секунду приходит уведомление в Telegram с IP, браузером и временем. Если зашёл не я, я узнаю в ту же секунду.

Просто PIN. Если устройство уже в списке доверенных, а биометрия не настроена.

Три пути входа: биометрия за секунду, PIN на знакомом устройстве, или PIN + контрольное слово на новом
Три пути входа: биометрия за секунду, PIN на знакомом устройстве, или PIN + контрольное слово на новом

Защита от брутфорса

Даже если кто-то знает что PIN шестизначный (миллион комбинаций) и начнёт перебирать, ничего не выйдет.

На сервере реализован экспоненциальный таймаут после трёх неудачных попыток: 3-я ошибка → минута ожидания, 4-я → две, 5-я → четыре, дальше удваивается до потолка в 24 часа. Начиная с 14-й попытки каждая следующая блокировка на сутки. Перебрать миллион PIN-комбинаций в такой схеме невозможно за разумное время. И счётчик живёт в базе, никакой очисткой кеша браузера это не обойти.

Отдельно есть лимит 20 попыток входа за 15 минут на IP и 1000 запросов на всё остальное. Плюс fail2ban на уровне файрвола. Плюс сами сессии живут 14 дней и при смене PIN мгновенно инвалидируются.

Серверный бэкенд и данные

Сервис работает не от root. Создан отдельный системный пользователь с минимальными правами. Даже если в Node-коде найдётся уязвимость которая позволит выполнить произвольные команды, атакующий получит только права этого пользователя. Он сможет прочитать мед.карту (печально, но факт), но не сможет лезть в другие сервисы на том же хосте, менять системные настройки, открывать новые порты.

Дополнительно systemd-unit настроен с изоляцией: NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp и прочий hardening. Файлы конфига и база данных лежат с правами 600, их видит только владелец.

Поиск по базе идёт через FTS5 (встроенный полнотекстовый индекс SQLite). Это значит что ни один пользовательский ввод не склеивается в SQL-запрос руками: SQL-инъекций архитектурно не бывает.

Загрузка документов проверяется дважды: и MIME-тип который прислал браузер, и расширение имени файла. Разрешены PDF, DOC/DOCX, XLS/XLSX, JPG, PNG, WebP, HEIC. SVG запрещён полностью, потому что SVG это XML и может содержать встроенный JavaScript, а открытие такого файла в браузере равно исполнению чужого кода в контексте сайта. Каждому загруженному файлу присваивается UUID-имя, угадать путь к чужому файлу невозможно. При скачивании проверяется что путь лежит внутри разрешённой папки, защита от path traversal.

Бэкапы по правилу 3-2-1

Это вторая ось безопасности: не «от плохих», а «если что-то случится». Три копии, две среды, одна оффсайт.

Копия 1. Горячие снимки каждые 6 часов через нативный .backup API SQLite. Хранятся последние 14 штук. Это защита от логических ошибок: случайно удалили что-то, Claude ошибся при обработке документа. Откатиться можно на любой из 14.

Копия 2. Полный ежедневный архив в 02:00. База плюс все медицинские файлы (PDF, фото, сканы), всё упаковывается в tar и шифруется AES-256-CBC с PBKDF2 (100 000 итераций, стандарт OWASP). Без ключа файл превращается в бессмысленный набор байтов. Хранятся 14 последних архивов.

Копия 3. Самое интересное. Сразу после создания ежедневного архива он отправляется в Telegram через бота. Да, Telegram. Копия физически лежит в облаке Telegram, отдельно от VPS, отдельно от GitHub, отдельно от моего ноутбука. История сообщений в Telegram хранится вечно, скачать архив можно с любого устройства за секунды. Файл при этом зашифрован, без ключа (а он в менеджере паролей) это бесполезный бинарник.

Получается так: код живёт на GitHub (private repo), данные на VPS с двумя уровнями локальных бэкапов, а зашифрованная копия данных улетает в Telegram. Даже если VPS сгорит, код клонируется из GitHub, данные скачиваются из Telegram, расшифровываются ключом из менеджера паролей и всё поднимается на новом сервере за час.

Аудит и уведомления

Кажое изменение в базе через триггеры уходит в audit_log, про это я писал выше. Отдельный auth_log пишет все события входа, выхода, смены PIN, регистрации биометрии, revoke устройства: с IP, user-agent и временем.

А в Telegram уходят мгновенные уведомления на события:

  • Новое устройство вошло в аккаунт

  • Настроена биометрия

  • Сменён PIN

  • Сервис перезапущен после деплоя

  • Ежедневный бэкап создан или (если вдруг) упал

Так я узнаю о подозрительной активности в ту же секунду. Если кто-то войдёт с незнакомого устройства, я получу пуш-уведомление.

Честно о слабых местах

Идеальной безопасности не бывает:

VPS не выделенный хост. Это виртуалка на общем железе, теоретически если в соседнем контейнере будет серьёзная уязвимость, это может аукнуться. Минимизирую непривилегированным пользователем и systemd-изоляцией, но полная физическая изоляция это другой класс бюджета. Для мед.данных одной семьи я считаю этот риск приемлемым; для сервиса на тысячу пациентов уже нет.

База на диске не зашифрована. Файл лежит в открытом виде. Если кто-то получит root-доступ к серверу, он её прочитает. Защита тут только через perms 600 и непривилегированного пользователя. Почему не зашифровал: SQLCipher ломает часть оптимизаций FTS5-поиска, которые критичны для работы AI-координатора. Смягчение: ежедневный бэкап в Telegram уже зашифрован, и единственное незашифрованное место это активный файл на самом сервере.

Сессионные токены в localStorage. Теоретически уязвимо к XSS. Я сильно снизил риск через CSP, запрет SVG, строгую валидацию пользовательского текста. Смягчение: короткий срок жизни токенов, мгновенное уведомление при входе, возможность разлогинить все устройства одним кликом и hard revoke.

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


Что AI реально нашёл

Без конкретных примеров это всё теория. Вот три реальных кейса.

Пропущенные базовые обследования

Claude прошёлся по всей истории сына за пять лет и нашёл что ряд базовых анализов ни разу не назначили. ТТГ (гормон щитовидки) последний раз проверялся больше трёх лет назад, а при нашем диагнозе это стандартный контроль. МРТ головного мозга ни разу за пять лет, хотя при имеющейся клинической картине это может показать структурные изменения. Ферритин (запасы железа) ни разу вообще, при том что у детей с подобными проблемами скрытый дефицит железа встречается часто и коррекция даёт заметное улучшение.

Анализы с цветовой индикацией: зелёный — норма, оранжевый — отклонение, красный — критическое. Рядом с каждым показателем срок годности — видно какие пора пересдать
Анализы с цветовой индикацией: зелёный — норма, оранжевый — отклонение, красный — критическое. Рядом с каждым показателем срок годности — видно какие пора пересдать

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

Три невролога, три схемы

За полтора года сын прошёл через трёх неврологов, и у каждого была своя схема лечения. Один назначил Когитум, Кортексин и Глицин. Через два месяца другой назначил Винпоцетин и Аминолон. Ещё через несколько месяцев третий назначил Ницерголин, Глиатилин и Нейробион. И только один из них вообще поинтересовался что ребёнок уже принимает.

Claude выстроил всю эту цепочку в хронологию и задал вопрос, который я сам себе не задавал: «А какая из этих схем работала? Почему меняли? Где записан ответ?». А нигде не записан. Система не говорит «кто прав». Она показывает факты и предлагает задать эти вопросы врачу при следующем визите.

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

Остеопат

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

Мне именно это и было нужно. Не «хороший врач или плохой», а «соответствует ли её подход тому что принято в доказательной медицине». Дальше решение моё.


Я добавил себя

Сын был первым пациентом. Но я добавил и себя, потому что мне 38 и я толком никогда не разбирался со своим здоровьем. Ходил на диспансеризацию, получал лист с результатами, «врач сказал всё норм значит норм».

Попросил Claude составить базовый набор анализов и обследований для мужчины 38 лет с сидячим образом жизни. Дал ему ссылку на сайт ближайшей лаборатории «Ситилаб», нейросеть подобрала полный список из их каталога с ценами, записала всё в план с приоритетами.

Получилось 35 пунктов. От базовых (ОАК, биохимия, ТТГ, глюкоза) до специализированных (гомоцистеин, онкомаркеры, тестостерон, витамин D). Плюс инструментальные обследования: флюорография, ЭКГ, УЗИ брюшной полости, щитовидной, почек. Плюс осмотры специалистов: терапевт, офтальмолог, стоматолог, уролог, дерматолог (с дерматоскопией), ревматолог.

35 пунктов чекапа с ценами из каталога лаборатории. Все покрываются корпоративным ДМС
35 пунктов чекапа с ценами из каталога лаборатории. Все покрываются корпоративным ДМС

Суммарно в лаборатории получилось бы больше 35 тысяч. А потом я решил уточнить что покрывает корпоративный ДМС, и выяснил что все 35 пунктов. То есть полный чекап, за который я бы заплатил 35+ тысяч, я могу пройти бесплатно. На следующей неделе начну сдавать.

Это и есть, пожалуй, главное для меня. Не «AI найдёт ошибки врачей», а «AI поможет не пройти мимо того что ты мог бы сделать для себя».

Процессом этого личного чекапа я буду делиться в Telegram-канале. Как настоящий эксперимент: какие анализы сдал, что нашлось, к каким врачам пошёл, что они сказали, что Claude сказал по поводу их заключений. Если интересно посмотреть на живой кейс использования такого сервиса, подписывайтесь. Я сам не знаю чем всё закончится и какие результаты получу.


Честный взгляд на то, что я сделал

Давайте без прикрас, потому что каждая такая статья должна заканчиваться трезвой оценкой.

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

Garbage in garbage out. Если входные данные кривые, анализ будет кривой. Двухэтапная проверка помогает, но не устраняет проблему.

Это не production-сервис.

Это эксперимент. Я не знаю чем он закончится. Может быть через год я напишу пост «сервис поменял моё отношение к здоровью». А может быть «я забил через три месяца, потому что поддержка оказалась больше чем польза». Пока ощущение первое, но подождём.


Про расшифровки приёмов

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

Транскрибирую я пока через NotebookLM от Google: загружаю аудио, получаю текст, вставляю в сервис. Whisper пока не рискую использовать, потому что переживаю что он будет ошибаться в медицинской терминологии и названиях препаратов на записях не всегда хорошего качества. NotebookLM справляется лучше.

Открытый код и что дальше

Код выложу на GitHub в ближайшие пару недель. Но мне кажется, самое ценное тут не сам код, а паттерн: своя база данных + structured context endpoint + AI-координатор через инструкцию. Это шаблон, который работает далеко за пределами медицины. Образование ребёнка, ведение проекта, юридическая история — везде, где данных много, а связей между ними больше, чем помещается в голове.

И вот что я понял за эти месяцы: мне теперь сложно представить поход к врачу без подготовки. Когда ты приходишь на приём с полной хронологией, со списком вопросов, разговор идёт совсем иначе. Врач видит что ты в теме, и уровень диалога меняется. Я скорее выберу врача, который сам использует такие инструменты, чем того, кто игнорирует прогресс. Но это моя гипотеза, проверяю на себе прямо сейчас. Собственно, весь этот эксперимент в реальном времени — какие анализы сдал, что нашлось, как реагируют врачи, что говорит Claude — я веду в Telegram-канале. Там же появится ссылка на GitHub, когда допилю. Если хотите посмотреть как это работает на живом примере, а не в теории — подписывайтесь.

А если у вас уже есть похожий опыт — собирали свои мед.данные, пробовали анализировать через AI, или строили что-то подобное для другой области — расскажите в комментариях. Интересно сравнить подходы.


И в заключении

Жена после первого знакомства с сервисом написала три сообщения подряд:

— Как интересно всё, конечно — Не то слово как полезно — Да конечно п* она умная

Для меня это была лучшая приёмка продукта из возможных. И если она открыла и сказала «полезно», значит сервис делает то ради чего я его писал.

Спасибо что дочитали. Telegram-канал про эксперимент. GitHub с кодом появится в ближайшее время в телеге. Увидимся в комментариях.