Files
OnBudget/docs/notification_parsing_status.md
SandersandClaude Opus 4.8 4f99b80169 Replace account_bindings with per-app default account; build analytics charts
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>
2026-07-18 00:09:12 +03:00

15 KiB
Raw Permalink Blame History

Парсинг push-уведомлений — статус реализации

Рекап по спецификации notification_parsing.md. Актуально на 2026-05-30. Ветка master, schemaVersion = 5.

⚠️ 2026-07: account_bindings удалена (миграция v1→v2): счёт теперь резолвится через правило senderToAccountsource_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.dartfindMerchantRule / 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.dartdedupHash(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_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 после неудачи 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.
  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 логично делать вместе с экраном деталей транзакции).