7.9 KiB
План: видимость ошибок парсинга уведомлений
Контекст
Phase 1 парсинга push-уведомлений реализована, но при отладке через debug-инжектор непонятно, что пайплайн сделал с сообщением:
- Сообщения со статусом
ignored(сработалlooksLikeNonTransaction, ignore-правило или AI сказал «не транзакция») иapplied(авто-применено) не показываются ни на одном экране — молча исчезают. failed-сообщения попадают в Inbox (watchInboxвключаетfailed), но inbox_card.dart рендерит только_RecognizedBody(draft декодировался) или_UnrecognizedBody(«Неопознано»). ПолеlastParseErrorнигде не выводится — причина ошибки не видна, кнопки повтора нет.
Решение: (A) полноценный экран-журнал всех raw_messages в настройках парсинга (покрывает и «показать игнорированные»), (B) человеческий вид failed-карточки в Inbox с текстом ошибки и кнопкой «Попробовать снова». Оба — часть продукта (не только debug), локализованы.
Это закрывает «хвосты Phase 1»: «Архив raw_messages / показать игнорированные» и non-transaction карточки из notification_parsing_status.md.
A. Экран «Журнал парсинга» (все raw_messages)
Данные (data/domain)
Добавить реактивный стрим «все сообщения пользователя» + сброс на повтор. Зеркалить существующий watchInbox.
- raw_messages_dao.dart:
Stream<List<RawMessagesTableData>> watchAll(String userId, {int limit = 200})—select(rawMessagesTable)..where(userId)..orderBy(receivedAt desc)..limit(limit).Future<void> resetForRetry(String id)— companion:status: Value(pending),parseAttemptCount: Value(0),lastParseError: Value(null)(чтобы воркер переобработал и AI-лимит>= 5не блокировал повтор сразу).
- raw_messages_repository.dart (интерфейс) + raw_messages_repository_impl.dart: добавить
watchAll(через тот же row→entity mapper, что иwatchInbox) иresetForRetry.
Провайдер (application)
В inbox_controller.dart рядом с inboxMessages:
@riverpod
Stream<List<RawMessage>> parsingLog(Ref ref, String userId) =>
ref.watch(rawMessagesRepositoryProvider).watchAll(userId);
И метод в InboxController: Future<void> retry(RawMessage m) => ref.read(rawMessagesRepositoryProvider).resetForRetry(m.id);
Экран (presentation)
Новый presentation/screens/parsing_log_screen.dart (ConsumerStatefulWidget, локальный фильтр-стейт):
- AppBar
l10n.parsingLogTitle, фонp.paper— как уParsingSettingsScreen. - Фильтр-чипы по статусу — зеркалить чип-паттерн
RulesListScreen(_RuleFilter):_LogFilter { all, inbox, applied, ignored, failed }. Подallпоказывать всё (включаяpending/pendingAi/parsedPartial). - Список карточек-строк. Каждая строка:
- статус-бэйдж (цвет из
Palette:failed→negative,applied→positive,ignored→ink2, прочее→accent), - время
receivedAt,packageName, сниппетbody, - распознанное: декодировать
decodeDraftBundle(message.draftJson)(draft_codec.dart) → мерчант + сумма (MoneyText), - при
lastParseError != null— строка ошибки красным, - для
failed/ignored— кнопка «Попробовать снова» →inboxController.retry(message).
- статус-бэйдж (цвет из
- Цвета только через
context.palette/Theme.extension<Palette>()(без хардкода).
Навигация
- app_routes.dart:
static const parsingLog = '/settings/parsing/log'; - app_router.dart:
GoRoute(path: AppRoutes.parsingLog, builder: (_, __) => const ParsingLogScreen())(рядом с остальными parsing-роутами, ~строка 133). - parsing_settings_screen.dart:
_NavTile(icon: Icons.receipt_long_outlined, title: l10n.parsingLogTile, onTap: → AppRoutes.parsingLog)— отдельной_Card, всегда видна (не подkDebugMode).
B. Человеческий failed в Inbox
- В
buildдобавить третью ветку перед текущим тернарником:message.status == RawMessageStatus.failed(илиlastParseError != null) →_FailedBody. - Новый
_FailedBody: заголовок «Ошибка распознавания»,message.lastParseErrorкрасным (p.negative), сниппетbody, две кнопки —_SecondaryButton«Попробовать снова» (inboxController.retry(message)) и «Игнорировать» (существующийignore).
Локализация
Добавить ключи в app_en.arb и app_ru.arb, затем flutter gen-l10n:
parsingLogTile, parsingLogTitle, parsingLogEmpty, parsingLogError, inboxRetry, inboxParseErrorTitle,
статус-лейблы parsingStatusPending/Inbox/Applied/Ignored/Failed/PendingAi/ParsedPartial,
фильтр-лейблы parsingLogFilterAll/Inbox/Applied/Ignored/Failed.
Команды после правок
dart run build_runner build --delete-conflicting-outputs # новый @riverpod parsingLog
flutter gen-l10n # новые ARB-строки
flutter analyze
Проверка (e2e через debug-инжектор)
flutter run, Profile → Parsing settings → Debug Inject.- Инжектить не-транзакцию (напр.
Баланс: 5000 ₽) → в Inbox её нет, а в Журнале строка со статусомignored. - Инжектить нормальную покупку → строка
applied/inboxс распознанным мерчантом и суммой. - Проверить
failed: сообщение, дающее исключение → в Inbox карточка с текстомlastParseErrorи кнопкой «Попробовать снова»; тап → статус сбрасывается вpending, воркер переобрабатывает. - В Журнале фильтр-чипы корректно фильтруют по статусу; «Попробовать снова» возвращает
ignored/failedв обработку. flutter analyzeчист; существующие тесты фичи зелёные (flutter test).