# Парсинг push-уведомлений — статус реализации > Рекап по спецификации [notification_parsing.md](notification_parsing.md). > Актуально на **2026-05-30**. Ветка `master`, `schemaVersion = 5`. > > ⚠️ **2026-07:** `account_bindings` удалена (миграция v1→v2): счёт теперь резолвится > через правило `senderToAccount` → `source_apps.defaultAccountId` → глобальный дефолт > (не trusted). Упоминания bindings ниже — историческое состояние. Легенда: ✅ сделано · 🟡 частично · ❌ не начато. --- ## Краткий итог **Phase 1 (ядро флоу: capture-заглушка → Inbox → правила) реализована и покрыта тестами.** Источник сообщений на время разработки — **debug-инжектор** (ручной ввод); нативный Android-listener (Phase 0) **намеренно отложен в самый конец** — пока фичу не используем «вживую», а разрабатываем. AI (Phase 2), переводы (Phase 3) и калибровка (Phase 4) — не начаты. Все unit/widget/controller тесты фичи зелёные; `flutter analyze` чист (3 досессионных info — в чужих тест-файлах). --- ## Что сделано (Phase 1) ✅ ### Данные и схема (§4) - ✅ Миграция `schemaVersion 4 → 5` в [app_database.dart](../lib/src/core/database/app_database.dart): `note → merchant`, новые колонки `raw_message_id` / `auto_applied` / `applied_by_rule_id`. - ✅ 5 новых таблиц + DAO: `raw_messages`, `parse_rules`, `rule_candidates`, `account_bindings`, `transfer_pairing_blocklist` (под `data/drift/`). Уникальный индекс по `(userId, packageName, cardLast4)`. - ✅ 5 confidence-колонок живут на `raw_messages` (не в `transactions`). - ✅ Domain-сущности (freezed): `RawMessage`, `ParseDraft`, `ParseRule`, `RuleCandidate`, `RuleSuggestion`, `AccountBinding`, `BankTemplate`. - ✅ Репозитории (интерфейс + Drift-impl + mapper) для всех 4 сущностей + `@Riverpod(keepAlive)` провайдеры ([notification_parsing_providers.dart](../lib/src/features/notification_parsing/application/notification_parsing_providers.dart)). ### Pipeline парсинга (§5–§8) - ✅ [bank_templates_catalog.dart](../lib/src/features/notification_parsing/data/bank_templates/bank_templates_catalog.dart) — RU-шаблоны: покупка/списание с last4, зачисление, СБП-перевод, комиссия, возврат. (По ходу тестирования исправлен баг: `\b` после `₽`/`р` не матчился — формат «5000 ₽» не парсился.) - ✅ [regex_parser.dart](../lib/src/features/notification_parsing/data/parser/regex_parser.dart) — этап 1; `null`, если шаблон не совпал. - ✅ [account_resolver.dart](../lib/src/features/notification_parsing/data/parser/account_resolver.dart) — резолв счёта (binding → bankKey → phone → эвристика) + score. - ✅ [rule_lookup.dart](../lib/src/features/notification_parsing/data/parser/rule_lookup.dart) — `findMerchantRule` / `findIgnoreRule`, разрешение конфликтов (§9.4: длина → matchCount → priority). - ✅ [rule_suggester.dart](../lib/src/features/notification_parsing/data/parser/rule_suggester.dart) + [merchant_normalizer.dart](../lib/src/features/notification_parsing/data/parser/merchant_normalizer.dart) — наблюдение кандидатов, предложение «merchant → category». - ✅ [confidence_scorer.dart](../lib/src/features/notification_parsing/data/parser/confidence_scorer.dart) — 5 per-field оценок + sanity-checks (capping, `category ≤ merchant`, `looksLikeNonTransaction`). - ✅ [decision_gate.dart](../lib/src/features/notification_parsing/data/parser/decision_gate.dart) — gate §8.7: правило + sanity + `min(amount,account,type) ≥ строгость` → auto-apply, иначе Inbox; крупная сумма → всегда Inbox. - ✅ [dedup.dart](../lib/src/features/notification_parsing/data/parser/dedup.dart) — `dedupHash(packageName, body)` (FNV-1a, без времени) + окно `kDedupWindow` (±3 мин по `receivedAt`) при вставке: тот же текст вне окна — новое сообщение (§15). - ✅ [draft_codec.dart](../lib/src/features/notification_parsing/data/parser/draft_codec.dart) — сериализация draft+предложения в `raw_messages.draftJson`. - ✅ [parsing_worker.dart](../lib/src/features/notification_parsing/application/parsing_worker.dart) — Riverpod-stream воркер над `pending`, идемпотентный; non-transaction → `ignored`, иначе Inbox. ### Контроллеры (§9) - ✅ [inbox_controller.dart](../lib/src/features/notification_parsing/application/inbox_controller.dart) — «Создать правило», «Подтвердить разово», «Игнорировать», `ignoreWithRule`, «Учесть все», «Скрыть разово». Авто-binding карты → счёт. - ✅ [rules_controller.dart](../lib/src/features/notification_parsing/application/rules_controller.dart) — CRUD правил, enable/disable, live-превью матчей за 30 дней. - ✅ [parsing_settings_controller.dart](../lib/src/features/notification_parsing/application/parsing_settings_controller.dart) — флаги `parsing_enabled` / `auto_apply_strictness` в `app_preferences` (без правки схемы). ### UI (§12) - ✅ Бэдж `✉N` на Home (month_header) → `/inbox`, счётчик из `inboxCount`. - ✅ [inbox_screen.dart](../lib/src/features/notification_parsing/presentation/screens/inbox_screen.dart) + [inbox_card.dart](../lib/src/features/notification_parsing/presentation/widgets/inbox_card.dart) + [confidence_badge.dart](../lib/src/features/notification_parsing/presentation/widgets/confidence_badge.dart) — карточки с 3 действиями, подсветка «?», «Учесть все»/«Скрыть разово». - ✅ [rules_list_screen.dart](../lib/src/features/notification_parsing/presentation/screens/rules_list_screen.dart) + [rule_card.dart](../lib/src/features/notification_parsing/presentation/widgets/rule_card.dart) — фильтр-чипы, свайп-выключение, тап → редактор. - ✅ [rule_editor_screen.dart](../lib/src/features/notification_parsing/presentation/screens/rule_editor_screen.dart) — contains/exact/regex, мерчант/категория/счёт, «Дополнительно», live-превью. - ✅ [parsing_settings_screen.dart](../lib/src/features/notification_parsing/presentation/screens/parsing_settings_screen.dart) — тумблер, слайдер строгости, счётчики; AI-секция как disabled-заглушка. - ✅ [debug_inject_screen.dart](../lib/src/features/notification_parsing/presentation/screens/debug_inject_screen.dart) — ручной ввод `RawMessage(pending)` (вход под `kDebugMode`). - ✅ Routes (`/inbox`, `/settings/parsing[/rules[/:id|/new]]`, `/settings/parsing/debug`) + вход из Profile. ### Транзакции (§16) - ✅ `note → merchant`, `extraInfo` как «Комментарий», поля `rawMessageId` / `autoApplied` / `appliedByRuleId` в сущности, репо, форме, tx_row. --- ## Что не сделано / осталось ❌🟡 ### Phase 2 — AI fallback (OpenRouter) ❌ - ❌ `ai_parser.dart` + `openrouter_client.dart`, tolerant-parse, `categorySuggestion`, `status=parsed_partial`/`pending_ai`. - ❌ API key (flutter_secure_storage), выбор модели, token usage, daily-limit. - ❌ Privacy-consent экран в onboarding (§7, §12.6), `connectivity_plus` offline-очередь + retry. - В UI настроек секция AI уже есть как заглушка. ### Phase 3 — Переводы между счетами ❌ - ❌ `transfer_pairing.dart` (точное совпадение сумм, окно ±10 мин, задержка только при подозрении, §10.1a). - ❌ Плашка «Объединить?» в Inbox, «Разъединить» в деталях + запись в `transfer_pairing_blocklist`. - ❌ Manual-transfer + SMS линковка (§10.2). - Таблица `transfer_pairing_blocklist` уже создана, но не используется. - Примечание: агрегация переводов в `month_summary` уже корректна (§10) — правки не нужны. ### Phase 4 — Калибровка ❌ - ❌ Логирование исправлений auto-applied транзакций (по `appliedByRuleId` / `rawMessageId`). - ❌ Экран «Точность» (F5), авто-подстройка порога строгости. ### Хвосты Phase 1 (полировка) 🟡 - ❌ **Откат правила (§9.5):** кнопка «Это правило сработало неправильно» в деталях транзакции + `weight -= 1`, `enabled=false` при `weight ≤ 0`. Нет экрана деталей транзакции (есть только форма), логика `weight` в `rules_controller` не реализована. - ❌ **Onboarding-notice фичи (§11):** «Подтвердите каждый магазин один раз…». - ❌ **Свои шаблоны парсинга (§6, Advanced):** список с тумблерами + «+ Свой шаблон» (нужна таблица пользовательских шаблонов). - ❌ **Меню «⋮» в Inbox (§12.2):** «Показать игнорированные» / «Архив raw_messages». - ❌ **Правила `senderToAccount` / редактор binding'ов:** enum есть, отдельного UI создания привязки счёта нет (binding создаётся только авто при подтверждении). - 🟡 **Дизайн-расхождение (осознанное):** non-transaction сообщения (баланс/реклама) воркер тихо помечает `ignored` и в Inbox не показывает — поэтому карточки «Не транзакция» с кнопкой «правило-исключение» (макет F1, 3-я карточка) в Inbox нет. Решено: для нераспознанных без суммы правило не предлагаем (по нему всё равно ничего не распарсится). ### Phase 0 — Нативный capture (Android) ❌ ← отложено в самый конец Делаем последним: пока фичу не используем «вживую», на разработке достаточно debug-инжектора. Это «риск-фундамент» из спеки (§2, §17) — без него фича не «живая», но и не блокирует разработку остального. - ❌ `NotificationListenerService` на Kotlin (Android) + platform channel (`MethodChannel` + очередь/`EventChannel`) → Dart-обёртка `data/notification/notification_listener_service.dart`. - ❌ Живучесть: foreground service + `BOOT_COMPLETED`; проверка под OEM-киллерами (Xiaomi/Samsung/Huawei) и после ребута. - ❌ Мост в pipeline: из listener'а → `RawMessage(status: pending)` через `RawMessagesRepository` (та же точка, что у debug-инжектора). Дедуп по `dedupHash`, обновление по `Notification.tag/id` (§15). - ❌ Allow-list пакетов банков + добавление своих в Settings. - ❌ Permission `BIND_NOTIFICATION_LISTENER_SERVICE`, баннер «доступ потерян» (§13); manifest (`FOREGROUND_SERVICE`, `RECEIVE_BOOT_COMPLETED`). - **Acceptance:** реальный push банка → авто-появление в Inbox/ленте без debug-инжектора; сервис переживает ребут и сворачивание приложения. --- ## Следующий этап (рекомендация) **Phase 2 — AI fallback (OpenRouter).** На разработке источник остаётся debug-инжектор, а pipeline получает AI-ветку для сообщений, которые regex не осилил, — это даёт максимум пользы здесь и сейчас, не завися от нативного capture. В UI настроек уже есть disabled-заглушка под AI. Конкретные шаги: 1. **Клиент.** `data/openrouter/openrouter_client.dart` — POST `chat/completions` с `response_format: json_schema` **и** дублированием требования структуры в system-промпте (дешёвые модели игнорируют schema). 2. **Парсер.** `data/parser/ai_parser.dart` — tolerant-parse (вытащить JSON из произвольного текста) + валидация по схеме (§7). Невалидный ответ → `status = parsed_partial` (в Inbox с сырым текстом), не падаем. 3. **Вплести в воркер.** В [parsing_worker.dart](../lib/src/features/notification_parsing/application/parsing_worker.dart) после неудачи regex (сейчас `parsed == null`) звать `ai_parser`; `categorySuggestion` → в предложение правила (не применять авто). Offline → `status = pending_ai`, retry воркером (`connectivity_plus`), после 5 неудач → `failed`. 4. **Настройки/секьюрность.** API key в `flutter_secure_storage`; выбор модели (динамический список из OpenRouter); token usage за день; опц. daily-limit → fallback в regex-only. Заменить заглушку AI-секции в [parsing_settings_screen.dart](../lib/src/features/notification_parsing/presentation/screens/parsing_settings_screen.dart). 5. **Privacy-consent (обязательно до первого вызова, §7/§12.6).** Экран согласия: «текст уведомлений уходит выбранной модели»; кнопка «Только regex» полностью отключает AI. 6. **Acceptance:** сообщение незнакомого формата (regex не справился) → AI-draft в Inbox с предложением категории; без согласия AI не вызывается; offline → корректный retry. ### Порядок дальше Phase 2 (AI) → Phase 3 (переводы) → Phase 4 (калибровка) → **Phase 0 (нативный capture) — в самом конце**. Хвосты Phase 1 — по мере надобности (откат правила §9.5 логично делать вместе с экраном деталей транзакции).