Stage 1 sms

This commit is contained in:
2026-05-30 15:02:32 +03:00
parent 054bef832b
commit 1286012dda
+130
View File
@@ -0,0 +1,130 @@
# Парсинг push-уведомлений — статус реализации
> Рекап по спецификации [notification_parsing.md](notification_parsing.md).
> Актуально на **2026-05-30**. Ветка `master`, `schemaVersion = 5`.
Легенда: ✅ сделано · 🟡 частично · ❌ не начато.
---
## Краткий итог
**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, без времени).
- ✅ [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 логично делать вместе с экраном деталей транзакции).