feat: enhance Telegram import functionality to support delays and cancellations
- Updated telegramCancellationParser to include parsing for delay notices. - Added new test cases for handling delay messages and stop-point cancellations. - Modified telegramImport integration tests to validate new delay handling. - Enhanced documentation to reflect the addition of delay notifications. - Updated TelegramImportPanel component to accommodate new alert types. - Introduced new fixture files for testing delay and cancellation messages.
This commit is contained in:
@@ -1,22 +1,22 @@
|
||||
# Отмены рейсов из @avp2Polotsk
|
||||
# Отмены и опоздания рейсов из @avp2Polotsk
|
||||
|
||||
Первый этап читает публичный канал через **отдельный существующий Telegram-аккаунт** и MTProto-клиент `teleproto`. Права управления каналом и Bot API не нужны. Получение выполняется раз в минуту; просматриваются публикации за текущий и два предыдущих дня, включая правки. Вложения не скачиваются. Расписания рейсов не изменяются: импорт создаёт предупреждения существующего типа `cancellation`.
|
||||
Первый этап читает публичный канал через **отдельный существующий Telegram-аккаунт** и MTProto-клиент `teleproto`. Права управления каналом и Bot API не нужны. Получение выполняется раз в минуту; просматриваются публикации за текущий и два предыдущих дня, включая правки. Вложения не скачиваются. Расписания рейсов не изменяются: импорт создаёт предупреждения существующих типов `cancellation` (отмена) и `delay` (опоздание).
|
||||
|
||||
## Распознавание и срок действия
|
||||
|
||||
Формат «Не будут выполняться рейсы: ПО МАРШРУТУ № 27 С ОСТАНОВКИ АВ/ВОКЗАЛ НА 15.43,16.43. С ОСТАНОВКИ ЗОДЧЕГО НА 15.58,16.58.» разбирается в четыре конкретных отправления. `АВ/ВОКЗАЛ` означает **Автовокзал**, точки в часах заменяются двоеточием. Отмена всего маршрута из такого объявления не выводится.
|
||||
|
||||
Поддерживаются формулировки **«С ОСТАНОВКИ»** и **«ОТ ОСТАНОВКИ»**, включая их сочетание в одном сообщении. Значения вроде `на 7.15` и `на 8.00` после остановки считаются временем отправления. Если отдельная дата не указана, день отмены берётся из оригинальной публикации. После обновления backend ранее сохранённые сообщения с ошибкой «Некорректная дата отмены» будут обработаны повторно при очередном чтении канала в пределах трёхдневного окна; сообщения с неизвестными остановками или номерами маршрутов останутся на проверке с уточнённой причиной.
|
||||
Поддерживаются формулировки **«С ОСТАНОВКИ»**, **«ОТ ОСТАНОВКИ»** и **«С/ОТ ОСТАНОВОЧНОГО ПУНКТА»**, включая их сочетание в одном сообщении. После общего заголовка можно перечислять остановки в кавычках на отдельных строках, например `«Мариненко» на 6.00, 7.30` и `«Боровуха» на 6.40, 8.15`. Заголовок может отделяться от списка пустыми строками. Значения вроде `на 7.15` и `на 8.00` после остановки считаются временем отправления. Если отдельная дата не указана, день отмены берётся из оригинальной публикации. После обновления backend ранее сохранённые сообщения с ошибкой «Некорректная дата отмены» будут обработаны повторно при очередном чтении канала в пределах трёхдневного окна; сообщения с неизвестными остановками или номерами маршрутов останутся на проверке с уточнённой причиной.
|
||||
|
||||
Номер маршрута относится ко всем его действующим направлениям в выбранном городе. Один пост создаёт одно уведомление, в том числе при нескольких номерах. Поддерживаются отдельные блоки «по маршруту №…» и общий список «по маршрутам №2, №27» с явно указанными остановками и временами. Неизвестные номера и остановки, разные даты, условия, периоды и изображения без текста требуют проверки администратором. Неизвестный номер блокирует автоматическую публикацию всего поста. Обычные новости не импортируются. Пока нет OCR, распознавания произвольных формулировок и автоматического редактирования расписаний.
|
||||
|
||||
Явная дата, «сегодня» или «завтра» определяют день отмены. Если дата отсутствует, используется **день оригинальной публикации по Europe/Minsk**, а не день импорта или правки. Дата без года выбирается ближайшей к публикации. Конец действия — **00:00 следующего дня по Минску**, граница исключительная. Например, для 02.10.2026 это `2026-10-02T21:00:00Z`. Будущие отмены активируются в начале указанного дня и получают push тогда же. Просроченные объявления сохраняются как завершённые без push.
|
||||
|
||||
Новые однозначные отмены публикуются автоматически. `TELEGRAM_AUTO_PUBLISH=false` переводит все распознанные отмены на проверку. В админке на странице **«Уведомления» → «Отмены из Telegram»** доступны состояние подключения, ручная проверка канала, исходные сообщения, ссылки на оригинал и история. При ручной публикации оператор выбирает один или несколько номеров маршрутов, проверяет текст и день отмены; срок до полуночи назначается сервером.
|
||||
Новые однозначные отмены публикуются автоматически. `TELEGRAM_AUTO_PUBLISH=false` переводит все распознанные отмены на проверку. В админке на странице **«Уведомления» → «Уведомления из Telegram»** доступны состояние подключения, ручная проверка канала, исходные сообщения, ссылки на оригинал и история. При ручной публикации оператор выбирает один или несколько номеров маршрутов, проверяет текст и день отмены; срок до полуночи назначается сервером.
|
||||
|
||||
### Словарь остановок и текст уведомления
|
||||
|
||||
В блоке **«Отмены из Telegram» → «Словарь названий остановок»** можно добавлять, изменять и удалять соответствия: название из канала → конкретная остановка из каталога выбранного города. Например, `ЗОДЧЕГО` → `Зодчего Иоанна`. Для разных сокращений одной остановки создайте несколько записей. Регистр, пробелы, знаки препинания и различие `ё/е` не учитываются; два эквивалентных сокращения в одном городе запрещены. Если есть несколько платформ с одним названием, выберите нужную остановку по адресу/ID. Отключённая остановка требует исправления соответствия и ручной проверки.
|
||||
В блоке **«Уведомления из Telegram» → «Словарь названий остановок»** можно добавлять, изменять и удалять соответствия: название из канала → конкретная остановка из каталога выбранного города. Например, `ЗОДЧЕГО` → `Зодчего Иоанна`. Для разных сокращений одной остановки создайте несколько записей. Регистр, пробелы, знаки препинания и различие `ё/е` не учитываются; два эквивалентных сокращения в одном городе запрещены. Если есть несколько платформ с одним названием, выберите нужную остановку по адресу/ID. Отключённая остановка требует исправления соответствия и ручной проверки.
|
||||
|
||||
Миграция **014** добавляет словарь и начальные соответствия `АВ/ВОКЗАЛ` → `Автовокзал`, `ЗОДЧЕГО` → `Зодчего Иоанна` только для Полоцка и только при единственной действующей остановке с таким названием. При неоднозначности соответствие задаёт администратор. Уже сохранённые соответствия при повторном запуске миграции не перезаписываются.
|
||||
|
||||
@@ -28,10 +28,9 @@
|
||||
02.10.2026: не выполняются рейсы:
|
||||
Автовокзал — 15:43, 16:43
|
||||
Зодчего Иоанна — 15:58, 16:58
|
||||
Источник: https://t.me/avp2Polotsk/1166
|
||||
```
|
||||
|
||||
При нескольких номерах создаётся одно уведомление с блоком отправлений для каждого номера. Исходный текст остаётся в истории импорта. В ручной проверке распознанный текст также подставляется в удобочитаемом виде; выбор номеров подставляет соответствующие отправления, пока оператор не изменил текст самостоятельно. Если сообщение не удалось разобрать, текст для ручной правки остаётся исходным.
|
||||
При нескольких номерах создаётся одно уведомление с блоком отправлений для каждого номера. Исходный текст остаётся в истории импорта. В ручной проверке можно выбрать тип «Отмена рейсов» или «Опоздание рейсов». Распознанный текст также подставляется в удобочитаемом виде; выбор номеров подставляет соответствующие отправления, пока оператор не изменил текст самостоятельно. Если сообщение не удалось разобрать, текст для ручной правки остаётся исходным.
|
||||
|
||||
## Установка
|
||||
|
||||
@@ -99,7 +98,7 @@ Compose монтирует каталог только для чтения, от
|
||||
|
||||
Идентичность публикации: город + числовой ID канала + ID сообщения. Снимок текста, результат распознавания и связи с алертами сохраняются одной транзакцией; повторный импорт и одновременные читатели не создают второй алерт. Более старая правка не заменяет свежую. Администратор публикует только проверенную версию по `content_hash`; изменившийся текст даёт `409` и требует нового просмотра.
|
||||
|
||||
Правка публикации обновляет связанные алерты. Если новая версия неоднозначна или больше не распознаётся как отмена, прежние алерты выключаются и сообщение возвращается на проверку. Отклонение сохраняется при последующих правках. Удалённый администратором алерт не воссоздаётся при правке исходного сообщения. Автоудаление сообщения в Telegram не отменяет предупреждение раньше полуночи; удаление не трактуется как восстановление движения.
|
||||
Правка публикации обновляет связанные алерты. Если новая версия неоднозначна или больше не распознаётся как отмена/опоздание, прежние алерты выключаются и сообщение возвращается на проверку. Отклонение сохраняется при последующих правках. Удалённый администратором алерт не воссоздаётся при правке исходного сообщения. Автоудаление сообщения в Telegram не отменяет предупреждение раньше полуночи; удаление не трактуется как восстановление движения.
|
||||
|
||||
Импорт помечает алерты как `notification_pending` в той же транзакции. Существующий scheduler отправляет их после начала действия, использует блокировки и до пяти попыток; Firebase отключён — ожидание сохраняется. Уже доставленный push при правке того же алерта не отправляется повторно. Остаётся обычное окно между приёмом FCM и записью результата в БД: при аварии в этот момент возможен повтор доставки. `alertId` позволяет клиенту показывать один экземпляр уведомления.
|
||||
|
||||
@@ -119,7 +118,7 @@ FCM TTL ограничен остатком срока действия алер
|
||||
|
||||
- `GET /api/v1/telegram-import` — безопасный статус и до 100 сообщений, сначала требующие проверки.
|
||||
- `POST /api/v1/telegram-import/sync` — чтение настроенного канала; запрещено в другом городе.
|
||||
- `POST /api/v1/telegram-import/:id/review` — `content_hash`, `action: publish|dismiss`; для публикации также `service_date`, `route_numbers: ["2", "27"]`, `title`, `message`; старое поле `route_id` также принимается.
|
||||
- `POST /api/v1/telegram-import/:id/review` — `content_hash`, `action: publish|dismiss`; для публикации также `service_date`, `route_numbers: ["2", "27"]`, `title`, `message`; необязательное `alert_type: cancellation|delay` (по умолчанию распознанный тип); старое поле `route_id` также принимается.
|
||||
- `GET /api/v1/telegram-import/stop-aliases` — словарь выбранного города.
|
||||
- `POST /api/v1/telegram-import/stop-aliases` — `alias`, `stop_id`; добавить соответствие.
|
||||
- `PUT /api/v1/telegram-import/stop-aliases/:id` — `alias`, `stop_id`; изменить соответствие.
|
||||
@@ -134,3 +133,21 @@ TELEGRAM_TEST_PGLITE_MODULE=/tmp/polotsk-telegram-test-runtime/node_modules/@ele
|
||||
```
|
||||
|
||||
Реальное чтение Telegram и доставка на устройство проверяются после локальной авторизации аккаунта, применения миграции и включения Firebase.
|
||||
|
||||
## Опоздания и обновление распознавания
|
||||
|
||||
Сообщения «будут выполняться рейсы с опозданием/с задержкой» распознаются как `delay`. Формулировки «могут выполняться/прибыть/прийти с опозданием» сохраняют неопределённость в тексте: «возможны опоздания рейсов». Время после названия остановки — время соответствующего рейса, а не обещанное время прибытия. Длительность опоздания автоматически не придумывается. Если в исходном тексте указана величина задержки, сообщение отправляется на ручную проверку, чтобы она не потерялась. Сообщения с отменами и опозданиями одновременно также требуют проверки.
|
||||
|
||||
Пример автоматически подготовленного уведомления:
|
||||
|
||||
```text
|
||||
Задержка отдельных рейсов №4
|
||||
|
||||
08.10.2026: с опозданием выполняются рейсы:
|
||||
Мариненко — 19:05
|
||||
Боровуха — 20:00
|
||||
```
|
||||
|
||||
Срок действия для опозданий совпадает с отменами: до 00:00 следующего дня по Минску. Истёкшие сообщения не создают новые алерты и не попадают в push-рассылку. Правка источника с отмены на опоздание обновляет тип существующего алерта; уже отправленный push не повторяется.
|
||||
|
||||
Для этого обновления новые миграции не нужны, если ранее применены 013–015. Пересоберите `backend frontend`: `docker compose up -d --build backend frontend`. При следующем чтении канала сообщения на проверке будут пересчитаны в пределах трёхдневного окна, даже без правки исходного текста. Сообщения за 8 октября при повторной обработке 9 октября получат статус «День уведомления завершён» и не вызовут новую рассылку. Уже опубликованные и отклонённые сообщения с неизменившимся исходным текстом не переписываются. Неизвестные названия остановок по-прежнему требуют пополнения словаря.
|
||||
|
||||
Reference in New Issue
Block a user