При сквозном прогоне 13 дней Андрея (#37, PR #55) нашлись пробелы движка диалога (#10). Демо они не ломают, но на защите их может увидеть жюри, а одно затрагивает безопасность.
Желаемый результат
C-SSRS не дублируется в тот же день. Сейчас после п.9 PHQ-9 ≥ 1 C-SSRS снова ставится в очередь, даже если его только что прошли по сигналу «возможный риск». Пауза 24 ч должна действовать и для пути через п.9, если C-SSRS завершён меньше 24 ч назад. Кризисный скрипт и алерт при этом остаются.
Вступление у отложенных шкал._start_pending передаёт intro только для C-SSRS. Нужно брать вступление из реестра (хук или словарь intro по scale_id), чтобы флоу не обходили это через @on_enter("scale.<id>").
Данные в кнопках. Сделать сопоставление триггеров по префиксу (button:med:taken:<dose_id>), чтобы dose_id передавался в кнопке, а не через data.med_pending.
Тайм-аут незавершённого шага. Если чек-ин или шкала брошены, через N часов (настройка в config/texts/dialog.yaml) движок возвращается в idle, и шкалы из очереди могут стартовать. Ответы, данные до тайм-аута, остаются сохранёнными.
Как проверить
make lint test; новые тесты в tests/test_dialog_engine_gaps.py. Тест 13 дней Андрея по-прежнему проходит, на 13-й день C-SSRS задаётся один раз.
Ограничения
Флаг intake (#53) выключен по умолчанию; при включённом флаге поведение не меняется. Замороженные контракты не трогать.
Лог выполнения
2026-10-03
2026-10-03
Что сделано
C-SSRS не повторяется: если скрининг риска (cssrs_screen) завершён меньше cssrs_cooldown_hours (24 ч) назад, после п.9 PHQ-9 ≥ 1 он не ставится в очередь — ни из сессии шкалы, ни из действия правила phq9_item9 (schedule_scale), ни по scale_due. Проверка по scale_results (только завершённые), центрально в Engine._start_pending + в scale_session._finish. Кризисный скрипт, план безопасности и алерт без изменений.
Реестр вступлений шкал: scale_session.register_scale_intro(scale_id, text | fn(ctx, scale_id, scheduled, return_state)); start_scale(intro=None) берёт вступление из реестра (явный intro важнее). veteran_daily перешёл на реестр, enter-хуки scale.<id> удалены.
Префиксные триггеры: @trigger("button:med:taken:*"), хвост данных кнопки — fsm.trigger_payload(event); точный ключ важнее префикса, длинный префикс важнее короткого. Кнопки лекарств несут дозу: med:taken|skip|side_effects:<dose_id>, med:skip_reason:<r>:<id>, med:se:<code>:<id>, med:dose:yes|no:<id> (без строки doses — дата напоминания). data.med_pending больше не используется (очищается); старые кнопки без payload и кнопки чужой/вчерашней дозы → «устарело», ничего не пишется. Ответы без кнопки (шаг чек-ина, свободный текст) → сегодняшняя доза из БД.
Тайм-аут брошенного шага: settings.abandon_after_hours (по умолчанию 6 ч, 0 = выкл.) в config/texts/dialog.yaml. Состояния помечаются fsm.abandonable(prefix, when=None): daily, meds, scale (кроме шкал онбординга). Время шага — data.step_at (по app.core.clock; системные события, которые шаг игнорирует, его не продлевают). Следующее событие сначала возвращает в idle (ответы в item_responses/daily_checkins остаются, незавершённая шкала без scale_results), затем обрабатывается; очередь шкал стартует. Не действует в кризисе и при включённом intake.
Результат
tests/test_dialog_engine_gaps.py — 23 теста; тест 13 дней Андрея: на 13-й день C-SSRS ровно один раз, состояние crisis.
make lint test: 684 passed, 1 skipped.
Заметки/решения
Изменены существующие тесты (намеренно): test_dialog_meds.py и test_dialog_veteran_days.py — кнопки лекарств теперь с dose_id; д.13 — ожидается crisis вместо scale.cssrs_screen.
Брошенная плановая шкала не ставится в очередь заново — повтор отправит шедулер (scale_due).
Код intake (#53) не трогал; при включённом флаге поведение прежнее.
## Описание
При сквозном прогоне 13 дней Андрея (#37, PR #55) нашлись пробелы движка диалога (#10). Демо они не ломают, но на защите их может увидеть жюри, а одно затрагивает безопасность.
## Желаемый результат
1. **C-SSRS не дублируется в тот же день.** Сейчас после п.9 PHQ-9 ≥ 1 C-SSRS снова ставится в очередь, даже если его только что прошли по сигналу «возможный риск». Пауза 24 ч должна действовать и для пути через п.9, если C-SSRS завершён меньше 24 ч назад. Кризисный скрипт и алерт при этом остаются.
2. **Вступление у отложенных шкал.** `_start_pending` передаёт intro только для C-SSRS. Нужно брать вступление из реестра (хук или словарь intro по scale_id), чтобы флоу не обходили это через `@on_enter("scale.<id>")`.
3. **Данные в кнопках.** Сделать сопоставление триггеров по префиксу (`button:med:taken:<dose_id>`), чтобы `dose_id` передавался в кнопке, а не через `data.med_pending`.
4. **Тайм-аут незавершённого шага.** Если чек-ин или шкала брошены, через N часов (настройка в `config/texts/dialog.yaml`) движок возвращается в `idle`, и шкалы из очереди могут стартовать. Ответы, данные до тайм-аута, остаются сохранёнными.
## Как проверить
`make lint test`; новые тесты в `tests/test_dialog_engine_gaps.py`. Тест 13 дней Андрея по-прежнему проходит, на 13-й день C-SSRS задаётся один раз.
## Ограничения
Флаг intake (#53) выключен по умолчанию; при включённом флаге поведение не меняется. Замороженные контракты не трогать.
---
## Лог выполнения
### 2026-10-03
### 2026-10-03
**Что сделано**
- C-SSRS не повторяется: если скрининг риска (`cssrs_screen`) завершён меньше `cssrs_cooldown_hours` (24 ч) назад, после п.9 PHQ-9 ≥ 1 он не ставится в очередь — ни из сессии шкалы, ни из действия правила `phq9_item9` (`schedule_scale`), ни по `scale_due`. Проверка по `scale_results` (только завершённые), центрально в `Engine._start_pending` + в `scale_session._finish`. Кризисный скрипт, план безопасности и алерт без изменений.
- Реестр вступлений шкал: `scale_session.register_scale_intro(scale_id, text | fn(ctx, scale_id, scheduled, return_state))`; `start_scale(intro=None)` берёт вступление из реестра (явный `intro` важнее). `veteran_daily` перешёл на реестр, enter-хуки `scale.<id>` удалены.
- Префиксные триггеры: `@trigger("button:med:taken:*")`, хвост данных кнопки — `fsm.trigger_payload(event)`; точный ключ важнее префикса, длинный префикс важнее короткого. Кнопки лекарств несут дозу: `med:taken|skip|side_effects:<dose_id>`, `med:skip_reason:<r>:<id>`, `med:se:<code>:<id>`, `med:dose:yes|no:<id>` (без строки `doses` — дата напоминания). `data.med_pending` больше не используется (очищается); старые кнопки без payload и кнопки чужой/вчерашней дозы → «устарело», ничего не пишется. Ответы без кнопки (шаг чек-ина, свободный текст) → сегодняшняя доза из БД.
- Тайм-аут брошенного шага: `settings.abandon_after_hours` (по умолчанию 6 ч, 0 = выкл.) в `config/texts/dialog.yaml`. Состояния помечаются `fsm.abandonable(prefix, when=None)`: `daily`, `meds`, `scale` (кроме шкал онбординга). Время шага — `data.step_at` (по `app.core.clock`; системные события, которые шаг игнорирует, его не продлевают). Следующее событие сначала возвращает в `idle` (ответы в `item_responses`/`daily_checkins` остаются, незавершённая шкала без `scale_results`), затем обрабатывается; очередь шкал стартует. Не действует в кризисе и при включённом intake.
**Результат**
- `tests/test_dialog_engine_gaps.py` — 23 теста; тест 13 дней Андрея: на 13-й день C-SSRS ровно один раз, состояние `crisis`.
- `make lint test`: 684 passed, 1 skipped.
**Заметки/решения**
- Изменены существующие тесты (намеренно): `test_dialog_meds.py` и `test_dialog_veteran_days.py` — кнопки лекарств теперь с dose_id; д.13 — ожидается `crisis` вместо `scale.cssrs_screen`.
- Брошенная плановая шкала не ставится в очередь заново — повтор отправит шедулер (`scale_due`).
- Код intake (#53) не трогал; при включённом флаге поведение прежнее.
Eleboba
added this to the Этап 1 · Ветераны СВО milestone 2026-10-03 18:49:20 +03:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Описание
При сквозном прогоне 13 дней Андрея (#37, PR #55) нашлись пробелы движка диалога (#10). Демо они не ломают, но на защите их может увидеть жюри, а одно затрагивает безопасность.
Желаемый результат
_start_pendingпередаёт intro только для C-SSRS. Нужно брать вступление из реестра (хук или словарь intro по scale_id), чтобы флоу не обходили это через@on_enter("scale.<id>").button:med:taken:<dose_id>), чтобыdose_idпередавался в кнопке, а не черезdata.med_pending.config/texts/dialog.yaml) движок возвращается вidle, и шкалы из очереди могут стартовать. Ответы, данные до тайм-аута, остаются сохранёнными.Как проверить
make lint test; новые тесты вtests/test_dialog_engine_gaps.py. Тест 13 дней Андрея по-прежнему проходит, на 13-й день C-SSRS задаётся один раз.Ограничения
Флаг intake (#53) выключен по умолчанию; при включённом флаге поведение не меняется. Замороженные контракты не трогать.
Лог выполнения
2026-10-03
2026-10-03
Что сделано
cssrs_screen) завершён меньшеcssrs_cooldown_hours(24 ч) назад, после п.9 PHQ-9 ≥ 1 он не ставится в очередь — ни из сессии шкалы, ни из действия правилаphq9_item9(schedule_scale), ни поscale_due. Проверка поscale_results(только завершённые), центрально вEngine._start_pending+ вscale_session._finish. Кризисный скрипт, план безопасности и алерт без изменений.scale_session.register_scale_intro(scale_id, text | fn(ctx, scale_id, scheduled, return_state));start_scale(intro=None)берёт вступление из реестра (явныйintroважнее).veteran_dailyперешёл на реестр, enter-хукиscale.<id>удалены.@trigger("button:med:taken:*"), хвост данных кнопки —fsm.trigger_payload(event); точный ключ важнее префикса, длинный префикс важнее короткого. Кнопки лекарств несут дозу:med:taken|skip|side_effects:<dose_id>,med:skip_reason:<r>:<id>,med:se:<code>:<id>,med:dose:yes|no:<id>(без строкиdoses— дата напоминания).data.med_pendingбольше не используется (очищается); старые кнопки без payload и кнопки чужой/вчерашней дозы → «устарело», ничего не пишется. Ответы без кнопки (шаг чек-ина, свободный текст) → сегодняшняя доза из БД.settings.abandon_after_hours(по умолчанию 6 ч, 0 = выкл.) вconfig/texts/dialog.yaml. Состояния помечаютсяfsm.abandonable(prefix, when=None):daily,meds,scale(кроме шкал онбординга). Время шага —data.step_at(поapp.core.clock; системные события, которые шаг игнорирует, его не продлевают). Следующее событие сначала возвращает вidle(ответы вitem_responses/daily_checkinsостаются, незавершённая шкала безscale_results), затем обрабатывается; очередь шкал стартует. Не действует в кризисе и при включённом intake.Результат
tests/test_dialog_engine_gaps.py— 23 теста; тест 13 дней Андрея: на 13-й день C-SSRS ровно один раз, состояниеcrisis.make lint test: 684 passed, 1 skipped.Заметки/решения
test_dialog_meds.pyиtest_dialog_veteran_days.py— кнопки лекарств теперь с dose_id; д.13 — ожидаетсяcrisisвместоscale.cssrs_screen.scale_due).