Files
PTSRaiAgent/docs/DECISIONS.md
Arxip222andClaude Opus 5.5 290b0d6151 feat(web): two-stage questionnaires, screening result and diary by result
- Sign-in: centred ЕМИАС card without the sidebar; anonymous entry asks «Депрессия / ПТСР».
- Stage 1 is the same for everyone and follows the fixed order of the MVP questionnaire:
  PHQ-2 → trauma filter/TSQ → RUS-AUDIT-S → C-SSRS → context; stage 2 only where stage 1
  is positive. Starts without a profile.
- After stage 1 the patient sees a preliminary result (screening, not a diagnosis), stored
  with the record; it switches on the diary modules (depression / PTSD).
- Stage 2 diary: morning (sleep, sleep quality, alcohol) and evening (mood, medication,
  3 PHQ-9 items in rotation, short PCL-5); C-SSRS since last contact only under risk;
  flags on weekly means against the personal norm; scheduled full scales with change
  (remission, response, worsening).
- Questionnaire log: GET /me/{token}/results and the «Мои результаты» screen.
- Questionnaires centred without the sidebar and without scale names; «Продолжить позже»
  goes home; confirmations are toasts; free-text comments are optional.
- Demo policy config/questionnaire_demo.yaml (INTAKE_DEMO only, not a clinical approval).

ADR-012.

Refs #77

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:14:09 +03:00

17 KiB
Raw Permalink Blame History

Архитектурные решения (ADR)

Живой журнал: что решили и почему. Новые записи — сверху. Одна запись = одно решение. При конфликте с docs/info/SPEC.md действует ADR.

2026-10-04 — ADR-012: Два этапа опросов — общий скрининг, предварительный результат пациенту, дневник по результату

Контекст: PDF «Опросники для MVP» (этап 1) и «Этап 2: наблюдение после диагноза». Пациент должен видеть, что у него всё в порядке или есть признаки депрессии/ПТСР, и дальше вести дневник под свой результат. Клиническая политика (config/questionnaire.yaml) закрыта до решений врача C01–C09, поэтому в демо опрос всегда заканчивался остановкой «нужна оценка специалиста». Решение: этап 1 общий для всех и идёт в фиксированном порядке документа: PHQ-2 → фильтр травмы/TSQ → RUS-AUDIT-S → C-SSRS → общие вопросы, затем этап 2 только там, где этап 1 положительный. Пункты внутри шкал не переставляются. Стоп-правила риска срабатывают после каждого ответа. После этапа 1 правила (app/core/interpretation.py, тексты в config/texts/results.yaml) показывают пациенту предварительный результат: «не выявлено», «предположение депрессии» (PHQ-9 ≥10), «склонность к ПТСР» (PCL-5 ≥31), алкоголь, ЧМТ, маршрут. Это всегда «результат скрининга, не диагноз». Результат включает модули дневника: depression и veteran (ПТСР). Без врача их определяет скрининг, модули врача сохраняются и дополняются. Дневник этапа 2 состоит из двух частей: утро (часы сна, качество сна, порции алкоголя) и вечер (настроение, лекарства + модули: 3 пункта PHQ-9 в день по кругу, короткая PCL-5 из 4 пунктов). C-SSRS «с последнего контакта» задаётся каждый вечер только при суицидальном риске. Флаги считаются по недельным средним против личной нормы за первые 14 дней и выносят полную шкалу раньше срока. Каждая запись хранит интерпретацию, показанную при завершении; журнал отдаёт /me/{token}/results. Демо (INTAKE_DEMO=1 + демо-часы) читает отдельный config/questionnaire_demo.yaml, помеченный «не клиническое согласование», а реальная политика не меняется. Причина: пациенту нужен понятный итог и путь дальше, а не тупик; решения о риске и маршруте при этом остаются детерминированными (ADR-007). Последствия: отступления от присланного PDF сохранены по docs/clinical/README.md: TSQ вместо PC-PTSD-5 (v2 PDF), RUS-AUDIT-S — пункты 3/9/10 (ВОЗ). Открыто: номера 4 пунктов короткой PCL-5 (Price et al., 2016), официальная форма C-SSRS Since Last Contact (сейчас — тексты Recent с периодом «с последнего ответа»), пороги «роста» модулей дневника — согласовать с врачом.

2026-10-04 — ADR-011: База знаний (RAG) — лексический поиск BM25 и экстрактивные ответы с цитатами

Контекст: агенту нужны ответы по клиническим рекомендациям (КР ПТСР 2023, КР Депрессия 2024) со ссылкой на источник (#60). Эмбеддинги требуют либо сети (GigaChat Embeddings), либо тяжёлых зависимостей (torch, модель на сотни МБ). При этом демо должно работать офлайн (ADR-004), а pyproject.toml заморожен (#34). Решение: PDF переводятся в txt один раз, офлайн (docs/source/rag/pdf_to_txt.py: таблицы → markdown, маркеры страниц, текст блок-схем перенесён вручную). Корпус лежит в config/rag/. Поиск — BM25 по основам слов (свой стеммер Snowball) с синонимами и переранжированием по самому плотному отрывку. Ответ экстрактивный: показываются только предложения из найденного фрагмента с цитатой «[КР ПТСР 2023, стр. N]». LLM может лишь упростить их для пациента, а перефразировка с новым содержанием отбраковывается. Если отрывки покрывают слишком малую долю вопроса (min_coverage), ответ — «ответа нет, уточните у врача». Пациенту не показываются предложения о назначении и дозах. Внутренние документы команды (audiences: [team]) не доходят ни до пациента, ни до врача. Причина: работоспособность офлайн и детерминизм важнее recall на перефразированных вопросах. Каждое показанное утверждение можно проверить по странице КР. Последствия: разговорные формулировки без общих слов с КР находятся хуже — это лечится словарём synonyms в config/rag/sources.yaml. Качество поиска контролирует набор контрольных вопросов в tests/test_llm_rag.py (hit@3 ≥ 15/18). Гибридный поиск с эмбеддингами можно добавить за тем же интерфейсом BM25Index.search.

2026-10-03 — ADR-010: Фокус — ветераны СВО; разработчики делят слои, а не направления

Контекст: три направления (ветераны, семьи, общая популяция с депрессией) и три разработчика. Если каждый ведёт своё направление, все трое правят одни и те же файлы: движок, API, дашборд. Решение: на хакатоне делаем только ветеранов (эпик #35); семьи (#40) и общая популяция (#43) — опциональные направления на будущее, без срока. Разработчики делят слои: A — бот (диалог, каналы, LLM), B — backend (API, сервисы, шедулер, адаптеры, БД, ядро), C — дашборд. Между слоями — замороженные контракты (#34) с заглушками, поэтому все трое стартуют одновременно. Направления подключаются как app/dialog/flows/<module>.py + config/modules.yaml. Причина: ноль коллизий по файлам при параллельной работе; общее ядро переиспользуется направлениями, как и задумано в ADR-001. Главный герой демо — Андрей (решение #9). Последствия: изменения контрактов — только мелкими PR [contract] с пингом всех. Владение файлами — в docs/dev/WORKFLOW.md.

2026-10-03 — ADR-009: Процесс — регламент TenVex в облегчённом режиме хакатона

Контекст: 3 дня, сдача в пн 05.10 в 13:00 МСК. Два разработчика и Claude Code-агенты работают параллельно. Опыт TenVex/vobot: без issue и изоляции агенты конфликтуют. Решение: берём ядро регламента vobot: задача = issue, одна зона на issue, worktree + ветка от origin/dev, PR в dev, Conventional Commits, лог в теле issue, ADR сюда. Облегчения: PR в dev можно мержить самому после зелёного make lint test, без обязательного ревью; contract-first/orval нет; main = сдаваемая версия, промоушн dev → main делает человек. Последствия: регламент — docs/dev/WORKFLOW.md.

2026-10-03 — ADR-008: Конфиги в config/, данные в data/

Контекст: требования к оформлению: «конфигурационные файлы — в отдельной папке». В SPEC пороги лежали в data/scales. Решение: config/ — шкалы (пункты, диапазоны, градации), расписания модулей, правила алертов, лексикон риска, тексты для пациента (YAML, вычитывает психиатр). data/ — синтетика, выдержки ГРЛС, eval-разметка. Причина: соответствие чек-листу жюри; психиатр правит только config/, не трогая код.

2026-10-03 — ADR-007: «Действия агента» — это выход движка правил, а не tool-calling LLM

Контекст: в демо-сценарии действия записаны как вызовы инструментов (raise_alert, notify_doctor, crisis_protocol, book_visit). Это выглядит так, будто их решает LLM, а это противоречит правилу «алерты детерминированы». Решение: правила (config/rules.yaml) по структурированным данным возвращают список действий; оркестратор их исполняет. LLM только формулирует реплику вокруг уже принятого действия. Причина: юридическая атрибуция ответственности: пороги задаёт врач, ИИ решений не принимает. Это ключевой аргумент для жюри и для специалистов.

2026-10-03 — ADR-006: Цвета в UI ↔ уровни алертов

Решение: 🔴 = acute (кризис, п.9 ≥ 1, C-SSRS с намерением/планом, доступ к оружию при риске); 🟡 = high + moderate (врачу ≤ 24 ч / флаг к визиту); 🟢 = нет открытых алертов; info (молчание, нет ответа) — серый флаг. В БД хранится уровень, цвет вычисляется.

2026-10-03 — ADR-005: Демо-часы (виртуальное время)

Контекст: демо — 13 дней терапии за 3 минуты. Шедулер на реальном времени это показать не даст. Решение: всё время в системе берётся из app.core.clock (реальное или виртуальное, DEMO_CLOCK=true). В демо-консоли есть кнопка «следующий день». Шедулер срабатывает по сдвигу часов. Правила считают окна (3 дня подряд, 7 дней) от виртуального «сейчас». Последствия: нигде не вызывать datetime.now() напрямую. Проверяется в ревью.

2026-10-03 — ADR-004: Канал-независимый диалоговый движок + веб-демо-консоль + офлайн-режим

Контекст: решение должно запускаться на технике организатора без нестандартного ПО. Telegram и GigaChat на площадке могут быть недоступны. Решение: диалог — чистая функция engine.handle(token, event) -> list[Reply] поверх FSM. Адаптеры: Telegram (aiogram) и веб-чат в демо-консоли (Streamlit). LLM за протоколом с двумя провайдерами: gigachat и mock (детерминированные ответы для демо-реплик). LLM_PROVIDER=mock по умолчанию в docker compose up. Причина: главный критерий жюри — работоспособность. Демо не должно зависеть от сети.

2026-10-03 — ADR-003: Один Postgres, две базы; без Redis и Alembic в MVP

Контекст: в SPEC два контейнера Postgres и Redis. Решение: один сервис postgres с двумя базами (identity, clinical; audit_log лежит в clinical) и двумя ролями: роль клинического модуля не имеет прав на identity. Связь — HMAC-токен. Схема создаётся через create_all при старте. FSM-состояние хранится в таблице dialog_state. Причина: изоляция сохраняется (разные БД и разные роли), а операционной сложности меньше. Alembic для трёхдневного прототипа — лишнее трение. В проде — отдельные кластеры и миграции.

2026-10-03 — ADR-002: Ежедневный чек-ин = настроение + приём + модульный вопрос; шкалы — по расписанию

Контекст: в SPEC ежедневно собираются 2–3 пункта PHQ-9 в 14-дневном окне. В демо-персоне и в модулях — ежедневно настроение 0–10, приём, сон, алкоголь, а шкалы целиком по расписанию (PHQ-9 еженедельно/раз в 2 недели) и внепланово по триггеру. Решение: берём модель из модулей. Ежедневно: настроение 0–10, приём препарата, один модульный вопрос (сон/кошмары у ветерана, алкоголь при флаге AUDIT-C) и свободный текст (extract → побочки, намерение бросить, алкоголь). Полные шкалы — по расписанию модуля и внепланово (например, настроение ≤ 3 три дня → внеплановый PHQ-9). П.9 PHQ-9 — только кнопками. Последствия: новая таблица daily_checkins (mood, med_taken, sleep_bucket, alcohol_bucket, flags).

2026-10-03 — ADR-001: Общее ядро + три модуля, включаемые скринингом

Контекст: три источника требований (SPEC, демо-персона, концепция/CJM) по-разному ставят фокус: ветераны, депрессия, семьи. Решение: общее ядро (PHQ-9 → C-SSRS, ежедневный чек-ин, молчание как сигнал) и модули veteran, depression, family (CLINICAL_MODULES.md). Модули включает стартовый скрининг и/или врач при подключении пациента. Шкалы, расписания и триггеры модулей описаны декларативно в config/modules.yaml. Приоритет на хакатон: ядро, veteran и depression — P0; family — P1 (вход «Я близкий», чек-лист, кнопка «сообщить врачу»). Кейс: закрываем сценарии «мониторинг после обращения» и «управление приёмом лекарств». Скрининг/триаж — входная точка, а не отдельный третий сценарий.