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>
82 lines
3.0 KiB
Dart
82 lines
3.0 KiB
Dart
import '../../domain/entities/parse_rule.dart';
|
|
import 'rule_lookup.dart';
|
|
|
|
/// Откуда взят разрешённый счёт (для диагностики / подсветки).
|
|
enum AccountSource { senderRule, appDefault, globalDefault, none }
|
|
|
|
/// Результат разрешения счёта из уведомления (шаг 3 pipeline, §B).
|
|
class AccountResolution {
|
|
const AccountResolution({
|
|
required this.accountId,
|
|
required this.score,
|
|
required this.trusted,
|
|
required this.source,
|
|
});
|
|
|
|
/// Разрешённый счёт (null, если ничего не нашлось).
|
|
final String? accountId;
|
|
|
|
/// Confidence по счёту 0..100 (§8.2) — для подсветки «?», НЕ для gate.
|
|
final int score;
|
|
|
|
/// Можно ли доверять счёту для авто-применения. Именно это поле (а не score)
|
|
/// решает судьбу в gate: правило и дефолт приложения — осознанный выбор
|
|
/// пользователя → true; глобальный дефолт — лишь догадка → false → Inbox
|
|
/// с префиллом (первый Confirm выучит его как дефолт приложения).
|
|
final bool trusted;
|
|
|
|
final AccountSource source;
|
|
}
|
|
|
|
/// Разрешает `accountId` по лестнице источников (§B): правило senderToAccount
|
|
/// (паттерн в теле → счёт) → дефолтный счёт приложения → глобальный дефолтный
|
|
/// счёт (без доверия) → ничего.
|
|
AccountResolution resolveAccount({
|
|
required String body,
|
|
String? merchantRaw,
|
|
List<ParseRule> senderRules = const [],
|
|
String? appDefaultAccountId,
|
|
String? globalDefaultAccountId,
|
|
}) {
|
|
// #1 — senderToAccount-правило (матч по телу).
|
|
final senderRule =
|
|
findSenderRule(senderRules, body: body, merchantRaw: merchantRaw);
|
|
if (senderRule?.accountId != null) {
|
|
return AccountResolution(
|
|
accountId: senderRule!.accountId,
|
|
score: 90,
|
|
trusted: true,
|
|
source: AccountSource.senderRule,
|
|
);
|
|
}
|
|
|
|
// #2 — дефолтный счёт приложения (выбран пользователем / авто-обучен).
|
|
if (appDefaultAccountId != null) {
|
|
return AccountResolution(
|
|
accountId: appDefaultAccountId,
|
|
score: 75,
|
|
trusted: true,
|
|
source: AccountSource.appDefault,
|
|
);
|
|
}
|
|
|
|
// #3 — глобальный Account.isDefault: префилл для Inbox, но НЕ trusted —
|
|
// авто-применение требует явно подтверждённого счёта приложения.
|
|
if (globalDefaultAccountId != null) {
|
|
return AccountResolution(
|
|
accountId: globalDefaultAccountId,
|
|
score: 40,
|
|
trusted: false,
|
|
source: AccountSource.globalDefault,
|
|
);
|
|
}
|
|
|
|
// #4 — ничего.
|
|
return const AccountResolution(
|
|
accountId: null,
|
|
score: 15,
|
|
trusted: false,
|
|
source: AccountSource.none,
|
|
);
|
|
}
|