73 lines
5.3 KiB
Markdown
73 lines
5.3 KiB
Markdown
# План изменений: расписание напоминаний
|
||
|
||
## Цели
|
||
|
||
1. Добавить расписание для напоминаний: бот должен писать только в явно разрешенные часы.
|
||
2. По умолчанию разрешенное окно: с 07:00 до 23:00 по МСК.
|
||
3. Если пользователь сам написал заметку, следующий reminder должен быть отложен минимум на 1 час.
|
||
|
||
## План
|
||
|
||
### 1. Расширить конфиг
|
||
|
||
- Добавить переменные в `.env.example` и `README.md`:
|
||
- `REMINDER_START_HOUR`
|
||
- `REMINDER_END_HOUR`
|
||
- `REMINDER_TIMEZONE`
|
||
- Значения по умолчанию:
|
||
- `REMINDER_START_HOUR=7`
|
||
- `REMINDER_END_HOUR=23`
|
||
- `REMINDER_TIMEZONE=Europe/Moscow`
|
||
- В `config.py` парсить часы как `int`, timezone через `zoneinfo.ZoneInfo`.
|
||
|
||
### 2. Изменить модель напоминаний
|
||
|
||
- Сейчас `send_reminders()` просто спит случайное число секунд между min/max.
|
||
- Нужно рассчитывать следующий момент отправки с учетом:
|
||
- разрешенного окна времени;
|
||
- случайного интервала `REMINDER_MIN_HOURS` / `REMINDER_MAX_HOURS`;
|
||
- timezone;
|
||
- ночного периода, когда отправка запрещена.
|
||
|
||
### 3. Добавить сброс после ручной заметки
|
||
|
||
- В `main.py` после успешного `journal.append(...)` сообщать планировщику, что пользователь сам написал заметку.
|
||
- Простой вариант: передать в `send_reminders()` общий объект состояния, например `ReminderScheduler` или `ReminderState`.
|
||
- В состоянии хранить `last_manual_entry_at`.
|
||
- При ручной записи следующий reminder должен быть не раньше `last_manual_entry_at + 1 hour`.
|
||
|
||
### 4. Сделать отдельную логику планирования
|
||
|
||
- Минимально оставить `send_reminders()` в `modules/reminders.py`, но добавить функции:
|
||
- `is_allowed_time(now, start_hour, end_hour)`
|
||
- `next_window_start(now, start_hour, timezone)`
|
||
- `calculate_next_reminder(...)`
|
||
- Не усложнять классами без необходимости, но небольшой объект состояния между handler и background task может быть оправдан.
|
||
|
||
### 5. Учесть edge cases
|
||
|
||
- Если сейчас ночь, следующий reminder переносится на 07:00 МСК.
|
||
- Если случайный интервал попадает за 23:00, reminder переносится на следующее 07:00.
|
||
- Если пользователь написал заметку в 22:30, минимальная задержка до 23:30, но окно уже закрыто, значит следующий reminder не раньше 07:00 следующего дня.
|
||
- Если пользователь написал ночью, reminder не раньше max(`+1 hour`, ближайшее 07:00).
|
||
- Если `REMINDER_START_HOUR >= REMINDER_END_HOUR`, лучше сразу падать с понятной ошибкой, чтобы не поддерживать ночные окна.
|
||
|
||
### 6. Обновить документацию
|
||
|
||
- README на русском: описать новое окно отправки.
|
||
- `.env.example`: добавить новые переменные.
|
||
- При необходимости обновить `AGENTS.md`, чтобы зафиксировать новые env vars и правило про timezone.
|
||
|
||
### 7. Проверка
|
||
|
||
- Запустить `uv run python -m compileall main.py config.py modules`.
|
||
- Если добавятся чистые функции расчета времени, можно добавить тесты позже, но сейчас отдельной тестовой инфраструктуры нет.
|
||
|
||
## Лог реализации
|
||
|
||
- Шаг 1: добавлены настройки `REMINDER_START_HOUR`, `REMINDER_END_HOUR`, `REMINDER_TIMEZONE` в `config.py`; часы валидируются как диапазон 0-23, окно должно быть start < end.
|
||
- Шаг 2: в `modules/reminders.py` добавлен минимальный `ReminderState`, расчет следующего reminder с учетом окна времени и пробуждение sleep через `asyncio.Event` при ручной записи.
|
||
- Шаг 3: `main.py` создает `ReminderState`, передает его в `send_reminders()` и вызывает `mark_manual_entry()` после успешного сохранения заметки.
|
||
- Шаг 4: обновлены `.env.example`, `README.md` и `AGENTS.md` с новыми настройками окна напоминаний, timezone и правилом задержки после ручной заметки.
|
||
- Проверка: `uv run python -m compileall main.py config.py modules` прошла успешно; `uv` создал локальную `.venv`, она игнорируется `.gitignore`.
|