Notification parsing: - Drop the account_bindings table/DAO/repo/entity/controller/screen; account resolution now goes senderToAccount rule -> source_apps.defaultAccountId (trusted) -> global default (untrusted -> Inbox), via v1->v2 migration. - Add defaultAccountId to source_apps; per-app settings consolidated into source_app_detail_screen (/settings/parsing/apps/:pkg). - Inbox auto-learns an app default on first Confirm/CreateRule; account picker on the card instead of a disabled button; parse_error_labels extracted. Analytics: - Replace placeholder screen with fl_chart cards (chart_card, chart_theme, month_stepper, month_math domain helper); slim down habit_analysis_screen. Android: add launcher icon (adaptive foreground + colors.xml) and app_name. Tests: migration_v2, analytics (screen/month_math), AI retry, inbox visibility; update resolver/gate/inbox suites for the new resolution path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
15 KiB
Парсинг push-уведомлений — статус реализации
Рекап по спецификации 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: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).
Pipeline парсинга (§5–§8)
- ✅ bank_templates_catalog.dart —
RU-шаблоны: покупка/списание с last4, зачисление, СБП-перевод, комиссия, возврат.
(По ходу тестирования исправлен баг:
\bпосле₽/рне матчился — формат «5000 ₽» не парсился.) - ✅ regex_parser.dart — этап 1;
null, если шаблон не совпал. - ✅ account_resolver.dart — резолв счёта (binding → bankKey → phone → эвристика) + score.
- ✅ rule_lookup.dart —
findMerchantRule/findIgnoreRule, разрешение конфликтов (§9.4: длина → matchCount → priority). - ✅ rule_suggester.dart + merchant_normalizer.dart — наблюдение кандидатов, предложение «merchant → category».
- ✅ confidence_scorer.dart — 5 per-field оценок + sanity-checks (capping,
category ≤ merchant,looksLikeNonTransaction). - ✅ decision_gate.dart — gate §8.7: правило + sanity +
min(amount,account,type) ≥ строгость→ auto-apply, иначе Inbox; крупная сумма → всегда Inbox. - ✅ dedup.dart —
dedupHash(packageName, body)(FNV-1a, без времени) + окноkDedupWindow(±3 мин поreceivedAt) при вставке: тот же текст вне окна — новое сообщение (§15). - ✅ draft_codec.dart — сериализация draft+предложения в
raw_messages.draftJson. - ✅ parsing_worker.dart — Riverpod-stream воркер над
pending, идемпотентный; non-transaction →ignored, иначе Inbox.
Контроллеры (§9)
- ✅ inbox_controller.dart — «Создать правило», «Подтвердить разово», «Игнорировать»,
ignoreWithRule, «Учесть все», «Скрыть разово». Авто-binding карты → счёт. - ✅ rules_controller.dart — CRUD правил, enable/disable, live-превью матчей за 30 дней.
- ✅ parsing_settings_controller.dart — флаги
parsing_enabled/auto_apply_strictnessвapp_preferences(без правки схемы).
UI (§12)
- ✅ Бэдж
✉Nна Home (month_header) →/inbox, счётчик изinboxCount. - ✅ inbox_screen.dart + inbox_card.dart + confidence_badge.dart — карточки с 3 действиями, подсветка «?», «Учесть все»/«Скрыть разово».
- ✅ rules_list_screen.dart + rule_card.dart — фильтр-чипы, свайп-выключение, тап → редактор.
- ✅ rule_editor_screen.dart — contains/exact/regex, мерчант/категория/счёт, «Дополнительно», live-превью.
- ✅ parsing_settings_screen.dart — тумблер, слайдер строгости, счётчики; AI-секция как disabled-заглушка.
- ✅ 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_plusoffline-очередь + 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.
Конкретные шаги:
- Клиент.
data/openrouter/openrouter_client.dart— POSTchat/completionsсresponse_format: json_schemaи дублированием требования структуры в system-промпте (дешёвые модели игнорируют schema). - Парсер.
data/parser/ai_parser.dart— tolerant-parse (вытащить JSON из произвольного текста) + валидация по схеме (§7). Невалидный ответ →status = parsed_partial(в Inbox с сырым текстом), не падаем. - Вплести в воркер. В parsing_worker.dart
после неудачи regex (сейчас
parsed == null) зватьai_parser;categorySuggestion→ в предложение правила (не применять авто). Offline →status = pending_ai, retry воркером (connectivity_plus), после 5 неудач →failed. - Настройки/секьюрность. API key в
flutter_secure_storage; выбор модели (динамический список из OpenRouter); token usage за день; опц. daily-limit → fallback в regex-only. Заменить заглушку AI-секции в parsing_settings_screen.dart. - Privacy-consent (обязательно до первого вызова, §7/§12.6). Экран согласия: «текст уведомлений уходит выбранной модели»; кнопка «Только regex» полностью отключает AI.
- Acceptance: сообщение незнакомого формата (regex не справился) → AI-draft в Inbox с предложением категории; без согласия AI не вызывается; offline → корректный retry.
Порядок дальше
Phase 2 (AI) → Phase 3 (переводы) → Phase 4 (калибровка) → Phase 0 (нативный capture) — в самом конце. Хвосты Phase 1 — по мере надобности (откат правила §9.5 логично делать вместе с экраном деталей транзакции).