TL;DR

Врач за 30 минут приема не успевает разобраться в пачке из 247 PDF за 12 лет, и принимает решения по тому, что вы вспомнили распечатать. Решение - приносить врачу не пачку, а структурированный список проблем (problem list, по методике POMR Лоренса Уида, 1968): тренды, текущие проблемы с приоритетами и привязкой к гайдлайнам, что показано и что нет, что уже сделано. Под капотом: SQLite-архив с полным raw_text и структурированными показателями, загрузка через pdfplumber + резервный OCR, параллельный запуск исследовательских агентов для протоколов и логистики, единый YAML-источник для финального документа. Claude как аналитик данных: гайдлайн + Class/Level на каждое утверждение. Все медицинские примеры синтетические; технические детали - из боевого репо.

1. Проблема: не «данных нет», а «врачу некогда»

У среднего взрослого человека за десять лет накапливается стопка: выписки из трех-четырех клиник, результаты анализов в почте и в личных кабинетах лабораторий, снимки в мессенджере, КТ на диске, который некуда вставить. Информация есть. Не работает не она. Не работает формат, в котором она попадает к врачу.

Когда вы приходите на прием, у врача есть 15 минут. За это время он не успевает прочитать вашу пачку из 247 PDF за 12 лет (мой кейс), не успевает заметить, что ваш ГГТ ползет вверх восемь лет подряд, что ферритин держится на верхней границе с 2017-го, что узел щитовидки вырос на 60% за два года. Он видит срез: сегодняшние жалобы и, если повезет, пару последних анализов, которые вы вспомнили распечатать. Это не «врач плохой», это «формат визита не предусматривает анализ архива». Между разрозненной документацией и решением стоит человек, у которого нет на анализ времени.

И вот это уже инженерная задача. Не «как нейросеть будет лечить вместо врача» - наоборот. Как принести врачу не пачку, а один документ, который он прочитает за две минуты и по которому примет решение точнее. Один документ - структурированный список проблем: что у пациента происходит, какие тренды, что уже сделано, что показано по гайдлайнам и что показано не делать. Врач мыслит проблемами (это формальный стандарт медицинской записи со времен Лоренса Уида, 1968), а значит, и приносить ему нужно проблем-лист, а не сырой архив.

Я решил собрать все в одну локальную базу и научить LLM-агента работать с ней по инженерным правилам, так, чтобы на выходе получался именно такой документ. Получился проект, который я для себя называю «личный медицинский архив». Ниже описано как он устроен и, главное, какие правила удерживают LLM от того, чтобы превратиться во вредного «ИИ-доктора».

2. Правила

Это самый важный раздел, и он не про код. Прежде чем подпускать LLM к подготовке документа, который увидит врач, нужно жестко определить, кем он не является.

В моем проекте есть файл-конституция (про него ниже), и первая же его строка: «Claude - аналитик данных, не клиницист». Из этого выводятся правила, которые агент обязан соблюдать в каждой сессии:

  • Никаких диагнозов. Формулировка всегда «это стоит обсудить с врачом такого-то профиля», а не «у вас X».

  • Опираемся на то, что говорят данные. «ГГТ вырос с 171 до 65 за период наблюдения» - это факт. «У вас болезнь печени» - это трактовка, не его компетенция.

  • Каждая значимая рекомендация - со ссылкой на клинический гайдлайн и, где есть, с указанием Class/Level доказательности.

  • Нейтральный регистр. Запрещены «СРОЧНО», «критично», «единственное, что спасает», цифры рисков «из воздуха». Вместо срочности - приоритеты P1/P2/P3. Иными словами - без drama queen.

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

Зачем такие правила? Затем, что LLM уверенно галлюцинирует, и в медицине цена галлюцинации высокая. Особенно, если это касается моего здоровья. Привязка каждого утверждения к источнику и к данным - это не формальность, а архитектурное ограничение, которое ловит выдумки. Если модель не может подкрепить рекомендацию гайдлайном - рекомендация не проходит. Это превращает «спросил у нейросети» в «прогнал данные через аналитический конвейер с проверками».

3. Архитектура

Два решения на старте.

Почему локально. Медицинские данные - самые чувствительные из всех персональных. Я не хочу, чтобы они жили в чьем-то облаке «на всякий случай». Весь архив - это папка на диске, которую можно зашифровать, забэкапить и унести.

Почему SQLite. Одним файлом, без сервера, к нему можно обратиться SQL-запросом из чего угодно, переживет любые миграции инструментов. Идеальный формат для персонального архива, который должен пролежать десятилетия.

Структура - мультипациентная, с полной изоляцией: у каждого члена семьи своя БД и свое дерево файлов.

data/
├── member_a/
│   ├── medic.db
│   ├── raw/         ← оригиналы PDF/JPG
│   ├── extracted/   ← <sha256>.txt после распознавания
│   └── rendered/    ← PNG страниц для визуального чтения
└── member_b/
    └── ...

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

Схема. Семнадцать таблиц, сгруппированных: ядро (документы, аналиты, измерения, события, лекарства, открытые вопросы, заметки), плюс отдельные группы под носимые устройства и под показания давления. Сердцевина - три таблицы:

-- документ = один распознанный файл
CREATE TABLE documents (
    id INTEGER PRIMARY KEY,
    file_hash TEXT UNIQUE,          -- sha256, дедуп по содержимому
    doc_date TEXT,                  -- дата документа (детектится)
    doc_type TEXT, source_org TEXT,
    raw_text TEXT,                  -- ВЕСЬ распознанный текст
    extraction_method TEXT,         -- pdfplumber | ocr | manual
    page_count INTEGER
);

-- справочник показателей с алиасами («Гамма-ГТ» = «ГГТ» = ggt)
CREATE TABLE analytes (
    id INTEGER PRIMARY KEY,
    code TEXT UNIQUE,               -- ggt, ldl, tsh, ...
    name_ru TEXT, canonical_unit TEXT, category TEXT
);

-- одно измерение = одно число из одного документа
CREATE TABLE measurements (
    id INTEGER PRIMARY KEY,
    document_id INTEGER REFERENCES documents(id),
    analyte_id  INTEGER REFERENCES analytes(id),
    value_num REAL, unit TEXT,
    ref_low REAL, ref_high REAL,    -- референсный коридор
    flag TEXT,                      -- H/L пометка лаборатории
    measured_at TEXT
);

Справочник analytes + таблица алиасов решают вечную боль: одна и та же «гамма-глутамилтрансфераза» в трех лабах называется тремя способами. Нормализуем к code, и тренд по показателю собирается одним SELECT … ORDER BY measured_at.

4. Загрузка: из PDF/JPG в структуру

Конвейер загрузки выглядит так:

файл → sha256 (дедуп) → pdfplumber
                            ↓ (если текста нет - скан)
                        OCR fallback
                            ↓
                raw_text в documents
                            ↓
            парсер лабов → measurements
                            ↓
                детектор дат → doc_date

Ключевая логика извлечения - «сначала пытаемся вытащить текстовый слой, при неудаче падаем в OCR»:

def extract_text(path: Path) -> tuple[str, str]:
    with pdfplumber.open(path) as pdf:
        text = "\n".join(p.extract_text() or "" for p in pdf.pages)
    if len(text.strip()) > MIN_CHARS:          # текстовый PDF
        return text, "pdfplumber"
    return ocr_pages(path), "ocr"              # скан → распознаем

Грабли №1, они же главные. Когда вы скармливаете LLM «структурированные данные», велик соблазн распарсить таблицу анализов в аккуратные строки и работать только с ними. Но при OCR и при сложной верстке таблицы проигрывают: теряются сноски, единицы измерений, комментарии лаборатории, мелкие находки в описании КТ. Самое ценное в выписке часто лежит не в табличке, а в абзаце «в S4 печени сохраняется кальцинат до 3 мм».

Вывод, который я вынес в методологию отдельным шагом: всегда держать полный raw_text и грепать по нему. Перед сборкой плана агент прогоняет «свип сырого текста» - ищет по ключевым словам прямо в documents.raw_text, а не только в нормализованных measurements. На синтетическом примере: в таблицах у «пациента Н.» все чисто, а греп по raw_text поднимает строку «семейный анамнез: рак желудка у деда» из абзаца анамнеза, которую табличный парсер просто не видел. Эта находка меняет интервал скрининга.

Мораль для любого RAG-над-документами: структурированный слой - для трендов, сырой текст - для полноты. Нужны оба.

5. Носимые устройства

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

Подтягивание - через OAuth по одному шаблону для каждого источника (фитнес-трекер, тонометр с синхронизацией, выгрузка из «здоровья» телефона). Токены лежат в системном Keychain, раздельно по пациенту:

medic.tracker.member_a
medic.bp.member_a

Ежедневный pull складывает данные в свои таблицы (wearable_daily, bp_readings и т.п.). Зачем это в медицинском контексте: когда обсуждается, есть ли смысл в антигипертензивной терапии, 7-дневное среднее домашних измерений «утро/вечер» весит больше, чем одно разовое 155/97 на приеме (которое могло быть от кофе и недосыпа). Данные носимых - это та самая «вторая фаза», которой не хватает на коротком визите.

6. Claude как движок

Теперь собственно про LLM. Я использую Claude Code - агентного ассистента в терминале, который умеет читать файлы, запускать команды, редактировать код и порождать суб-агентов. Три приема делают его пригодным для такой работы.

Прием 1: CLAUDE.md как исполняемая конституция. Claude Code автоматически читает файл CLAUDE.md в корне проекта в начале каждой сессии. Туда я вынес все правила из раздела 2 плюс операционные: схему БД, список скриптов, профили пациентов, стиль артефактов. Это не документация «для людей» - это инструкции, которым агент следует. Фрагмент:

## Правила работы
1. Я - аналитик данных, не врач. Никаких диагнозов.
2. Свежий аудит при каждой большой задаче - не рециклить
   выводы из памяти, перечитывать данные из БД заново.
6. Нейтральный регистр. Запрещены «СРОЧНО», «критично».
7. Приоритеты P1/P2/P3 вместо срочности.
8. Каждая рекомендация - со ссылкой на guideline (+ Class/Level).
   Минимум 3 источника в значимом блоке.
9. Конфликты гайдлайнов - показывать оба варианта.

Прием 2: команда briefing - свежее состояние из БД, а не из памяти. Соблазн вести «заметки о пациенте» прямо в markdown и читать их в начале сессии. Это антипаттерн: заметки устаревают. Вместо этого - скрипт, который генерирует стартовый брифинг из самой БД на лету:

$ MEDIC_PATIENT=member_a python db.py briefing

counts: documents=135, measurements=1069, notes=57 ...
актуальный план:  plans/member_a_2026_v3.md
последние 5 notes / 3 events / top-5 P1 open_questions
давление (7-дн среднее): утро 128/82, вечер 134/86

Агент всегда стартует от актуального состояния. Это прямое следствие правила «свежий аудит, не рециклить память».

Прием 3: суб-агенты для распараллеливания. Про это - отдельный раздел.

7. Методология чек-апа

Самое интересное - не хранилище, а процесс. «Сходить провериться» обычно выглядит как «сдам, что в голову придет, и принесу врачу». Я переписал это как конвейер. Упрощенно шаги такие:

Шаг

Что делает

1. Конфигурация

Окно, бюджет, локация, приоритет (экспертиза/удобство/цена), что не включать

2. Анамнез

Агент диалогом добивает «белые пятна»: семья, перелеты, образ жизни, терапия - по одному вопросу

3. Аудит + свип raw_text

Перечитать БД с нуля, грепнуть сырой текст, прогнать линтер открытых вопросов

4. Исследовательские агенты

Параллельно: протоколы / коды анализов+клиники / специалисты

5. Кросс-референс

Сопоставить, что требует гайдлайн, с тем, когда это делалось → что просрочено

6. Что снять

Убрать лишнее (недавно сделано, не показано по возрасту, дублирует программу клиники)

7. Черновик плана

P1/P2/P3, развилки «если→то», сметы

8. Самопроверка

Прогон по чек-листу качества

9. Сохранение

Находки → в БД (notes, open_questions), печатные документы

Два шага стоит выделить.

Шаг 2, анамнез диалогом. LLM здесь силен именно как собеседник: он держит контекст всей БД и задает точечные вопросы, которые закрывают пробелы с наибольшей диагностической ценностью. На синтетике: «в архиве нет частоты авиаперелетов - а это критично при вашей коагулопатии; сколько раз в месяц летаете?» Один вопрос - и в плане появляется конкретная профилактическая мера.

Шаг 6, «что снять». Контринтуитивно, но половина ценности - в вычитании. Хороший чек-ап - не «сдать все подряд» (это ведет к ложноположительным и тревоге), а сделать ровно показанное. Поэтому в плане есть обязательный раздел «что не делаем и почему»: «маммография - была год назад, биеннале, следующая в N»; «скрининг, который имеет grade D у USPSTF, - не делаем».

8. Параллельный запуск исследовательских агентов

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

  • Агент A - протоколы. «Вот профиль пациента. Собери актуальные рекомендации (гайдлайн + год + Class/Level). Где источники расходятся - покажи оба варианта».

  • Агент B - логистика. Коды конкретных лабораторных тестов, цены, центры для инструментальных исследований в нужном городе.

  • Агент C - специалисты. Лучшие профильные врачи с критериями выбора (степень, аффилиация, публикации).

Запуск из агента-оркестратора - буквально три вызова инструмента «породить суб-агента» с run_in_background: true. Дальше оркестратор продолжает работу, а по готовности каждого приходит уведомление. Промпт агента A (сокращенно, синтетический профиль):

Ты - медицинский исследовательский агент. Профиль: Ж, 52, погранич. АД,
субклинич. гипотиреоз, дислипидемия, аденома толстой кишки удалена.
По каждому пункту дай рекомендацию со ссылкой (guideline, год,
Class/Level). Где гайдлайны расходятся - ОБА варианта.
Вопросы: A) интервал колоноскопии после аденомы;
B) старт статина по SCORE2; C) тактика при субклин. гипотиреозе...
Финальный текст - это результат для другого агента, без обращений
к человеку.

Что это дает сверх «спросить одну модель»: охват и свежесть. Реальный кейс из боевого прогона (содержание честное, конкретные действующие вещества намеренно опускаю, чтобы не превращать инженерную статью в фарм-обзор): агент с веб-поиском поймал, что один из стандартных препаратов второй линии, который год назад был основным выбором в обсуждаемой ситуации, к моменту чек-апа уже не рекомендовался в обновленном протоколе профильного общества. Это новость пары последних месяцев, свежее чем граница обучения основной модели, и ее не было ни в одном моем конспекте. Один агент в одном контексте такое пропустил бы; специализированный агент с актуальным поиском - нет.

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

9. Список проблем как центральный артефакт

Финальный документ, который мы приносим врачу - список проблем (problem list). Это не мое изобретение. Это методика проблемно-ориентированной медицинской записи, POMR, которую предложил Лоренс Уид еще в 1968 году и которая до сих пор формальный стандарт медицинской записи в большинстве клинических школ. Врач шесть лет учился думать через problem list: проблема - оценка - план. Когда мы приносим ему документ в его собственной структуре, время приема расходуется не на расшифровку нашей пачки, а на разговор по существу.

Технически это широкая таблица (проблема + данные + протокол + действие + специалист), плюс сводка по специалистам/анализам/исследованиям. Наивная реализация - вести этот документ руками. Через две правки он рассыпается: в таблице обновили, в сводке внизу забыли, появились осиротевшие пункты.

Решение - единый YAML-источник + генератор:

problem_list.yaml  ──render──▶  problem_list.md
                                + landscape DOCX (для печати)

Каждая проблема в YAML несет свои атомы: analyses, investigations, specialists. «Общая сводка» в конце документа не пишется руками - она агрегируется генератором из этих же атомов. Дрейф структурно невозможен: нельзя добавить анализ в проблему и забыть его в сводке, потому что сводка - это и есть проекция атомов.

# сводка специалистов выводится из проблем, не ведется отдельно
specialists = sorted({s for p in problems for s in p["specialists"]})

DOCX генерируется из того же .md через pandoc, затем python-docx разворачивает страницу в landscape (широкая 8-колоночная таблица в портрете не помещается):

for s in doc.sections:
    s.orientation = WD_ORIENT.LANDSCAPE
    s.page_width, s.page_height = Cm(29.7), Cm(21.0)

Принцип «один источник, все остальное - производные» - не медицинский, а инженерный, и он здесь окупается каждый прогон: руками .md и DOCX не трогаем вообще, правим только YAML.

10. Дисциплина и качество

Что отличает этот конвейер от «накидал промпт - получил простыню».

Гайдлайн + Class/Level на каждое утверждение. Не «начните принимать статин», а «при таком профиле по SCORE2 обсуждается старт статина (ESC/EAS 2021); решение - за кардиологом».

Конфликты - оба варианта. Синтетический пример: профилактика венозного тромбоза в длинном перелете у носителя тромбофилии. Один свод рекомендаций (CHEST) консервативен - только механическая профилактика, против антикоагулянтов. Другой (экспертные/ISTH-ориентированные) допускает разовую инъекцию у группы высокого риска. Правильный документ показывает оба и объясняет, чем они различаются, а не выбирает за врача.

P1/P2/P3 вместо срочности. «В текущем цикле / по возможности / опционально». Без эмоционального давления.

Самопроверка по чек-листу. Перед финалом агент прогоняет план по фиксированному списку: дисклеймер на месте? конфликты помечены? есть развилки «если→то»? данные взяты из raw_text, а не только из таблиц? Это ловит пропуски.

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

11. Как меняется визит к врачу

Тот же синтетический «пациент Н.» из раздела 8 (Ж, 52, погранич. АД, субклин. гипотиреоз, дислипидемия, аденома удалена). За 12 лет в архиве накопилось 47 документов: 18 выписок от четырех клиник, 22 листка анализов из трех лабораторий, 4 КТ, 3 эхо-исследования. Старый сценарий приема: пациент приходит к терапевту с пакетом бумаг, кладет на стол, говорит «посмотрите, пожалуйста, я хочу провериться». Врач тратит 7 минут из 15 на пролистывание сверху, видит фрагмент, делает выводы по нему, назначает «общий чек-ап» на 35 анализов и направления к 7 специалистам по принципу «давайте посмотрим, чтобы спокойно».

Новый сценарий: пациент приходит к тому же терапевту с одним документом на 3 страницы - проблем-листом. Четыре строки в широкой таблице:

  • проблема (погранич. АД / субклин. гипотиреоз / дислипидемия / контроль после удаленной аденомы);

  • данные (тренды за 12 лет с цифрами, из БД);

  • протокол (актуальный гайдлайн с Class/Level, конфликтующие точки помечены);

  • действие (что сдать, что сделать, к кому идти);

  • специалист (с критериями выбора, не фамилиями).

Плюс сводка: 17 анализов на одном заборе крови (вместо 35), 4 специалиста под четыре проблемы (кардиолог, эндокринолог, гастроэнтеролог, гематолог), 2 инструментальных исследования на 6 месяцев + развилки «если→то» (например, если по эхо нашли что-то новое - тогда дополнительно X). И отдельный раздел «что не делаем и почему» на 5 пунктов: маммография была год назад, биеннале; колоноскопия после удаленной аденомы по сроку через 3 года, не сейчас; скрининг рака легких grade D у USPSTF при отсутствии анамнеза курения; общий онкоскрининг (CEA, CA-125) - не показан вне групп риска; ферритин повторно через 3 месяца, не сейчас.

Что меняется в кабинете. Врач читает документ за две минуты - потому что он написан в его собственной структуре, проблема за проблемой, со ссылками. Оставшиеся 13 минут уходят не на расшифровку пачки, а на содержательный разговор: уточняем сомнения, согласовываем приоритеты, обсуждаем развилки. На выходе - чек-ап вдвое меньше по числу процедур и втрое содержательнее по результату. Это не «нашли больше», это нашли точнее. И главное - разговор с врачом меняется качественно: не «найдите у меня что-нибудь», а «вот мои проблемы и гипотезы - давайте проверим эти, а вот эти не будем по таким-то причинам».

12. Приватность, этика, ограничения

Еще раз про границы.

  • Данные держим локально. Архив - на своем диске. Облачную LLM мы используем как «процессор» над выжимками, но сам архив не загружаем целиком и бездумно. Решите для себя, какой уровень приватности приемлем, прежде чем что-либо отправлять.

  • LLM - не диагност. Все, что выходит из конвейера, - это «обсудить с врачом», а не «диагноз». Это не оговорка для юристов, а реальное ограничение модели.

  • Галлюцинации. Якорение на гайдлайны и самопроверка снижают риск, но не обнуляют. Любую конкретную рекомендацию имеет смысл перепроверить по первоисточнику.

  • Где агент не должен лидировать. Острые состояния, неотложка, психиатрия, интерпретация снимков - не сюда. Это аналитика хронического архива для подготовки к плановому визиту, не более.

13. Как повторить у себя

Минимальный старт:

  1. SQLite + таблицы documents/analytes/measurements. Не усложняйте схему раньше времени.

  2. Загрузка: pdfplumber с резервным OCR; храните полный raw_text.

  3. CLAUDE.md с правилами-конституцией (особенно «аналитик, не врач»).

  4. Скрипт briefing, генерирующий состояние из БД.

  5. Дальше - методология чек-апа поверх.

Собранные грабли:

  • OCR-таблицы плохи → всегда грепайте сырой текст, не только нормализованные поля.

  • Один и тот же показатель - три названия → справочник аналитов с алиасами обязателен.

  • Песочница не пишет в реальную папку загрузок между вызовами → генерацию печатных файлов выполняйте без песочницы и проверяйте, что файл реально на месте (ls), а не «команда отработала».

  • LLM уверенно врет → ни одного медицинского утверждения без источника; обязательная самопроверка по чек-листу перед выдачей.

  • Мультипациентность → изолируйте БД и проверяйте активный профиль перед каждой записью.

14. Итоги

Это не история про «ИИ-доктора». Это история про то, что личные данные + дисциплина аналитика + LLM-агент как исполнитель дают на порядок больше, чем стопка PDF - и про то, что приносить специалисту нужно артефакт в его системе мышления, а не сырое содержимое архива. И будьте здоровы.


Дмитрий Волошин, сооснователь и генеральный директор OTUS, основатель Клуба менторов, основатель Школы бизнеса Ninox. Заметки про управление, найм и фаундерский опыт: t.me/coffee_notes