DriftLedger/docs/账单识别及账户分类设计.md
fengmengqi 6767dd538a feat: 领域/组件/服务三层目录重构 + 微信无障碍绕过方案 + 新 UI 组件体系 + 账本增删改增强
### 架构重构:三层分层目录化
  - domain 拆分为 8 个子目录(core/pipeline/rules/finance/stats/taxonomy/transaction/platform)
  - components 拆分为 6 个子目录(form/ui/layout/account/category/stats/transaction)
  - services 拆分为 5 个子目录(automation/data/ocr/security),accessibilityParser 从 automationPipeline 提取
  - 新增 ruleConfig.ts — 规则配置唯一数据源(关键词/方向/OCR 模式),与业务逻辑解耦

  ### 微信账单抓取:节点混淆绕过(核心突破)
  - BillingAccessibilityService 重命名为 SelectToSpeakService,完整伪装为系统服务
  - 同时伪装包名+类名为 com.google.android.accessibility.selecttospeak,规避微信 8.0.52+ 白名单校验
  - Config Plugin 重写:8 个 kt 文件整体复制+package 正则替换+Manifest/import 联动
  - 支付宝/微信无障碍文本解析器全面增强(方向推断/账单分类提取/付款方式提取/容错)
  - 补充文档 accessibility-wechat-guide.md(伪装原理、踩坑全记录)

  ### 新 UI 组件体系
  - Toast:全局轻量 toast(Context Provider + 入场动画 + 操作按钮 + 自动消失)
  - ErrorBoundary:React class 错误边界(降级 UI + 重试)
  - EmptyState / ConfirmDialog / SegmentedControl / Skeleton / TimePicker
  - BottomSheet 重写:SafeAreaProvider 修复、手势下滑关闭、键盘响应式避让
  - FormModal 重构:拆出 FormFields 子组件(TextField/SelectField/DropdownField)
  - 新增 PeriodSwitcher、RangeStatsCard 独立组件

  ### 新 Hooks & 工具
  - useBottomInset — 统一底部安全区留白
  - useKeyboardAvoiding — 键盘高度响应式 hook(替代 translateY 方案)
  - sanitize.ts — 日志脱敏工具提取

  ### 账本增删改增强
  - 写锁增加代际计数器(lockGeneration),reset 后旧链 pending 任务自动跳过 set
  - 新增 restoreTransaction — 撤销删除(重新追加 raw 文本到 mobile.bean)
  - 删除交易时清除去重缓存(buildTxKeyFromRaw 重建去重键),支持「删了重记」
  - editTransaction/deleteTransaction 改用 dr-id 精确定位交易块(避免同名交易定位错误)
  - appendTransactionsBatch 改从存储直接读取,避免 zustand state 不一致

  ### OCR 原生模块增强
  - 异步 initEngine 增加 CountDownLatch 等待(最多 15s),解决竞态导致的「引擎未就绪」
  - setModelDir 增加去重判断 + file:// 前缀剥离,避免冗余 reload
  - 推理链路增加分阶段耗时日志(det 推理/det 后处理/rec 识别)
  - 图片缩放策略重命名(scaleDownForOcr → capLongEdge)

  ### OCR 模型按需下载
  - 移除了启动时自动下载 ~30MB OCR 模型的逻辑
  - 改为首次使用 OCR 时才触发下载

  ### 设置页重设计
  - ScrollView → SectionList 分组卡片布局(iOS 风格分组圆角行+右侧箭头)
  - 移除 Card 组件包装,直接使用独立分组头+底部关于卡片

  ### 首页优化
  - ScrollView → FlatList(ListHeaderComponent 承载净资产卡片+待办条)
  - 日期/金额格式化增加 locale 感知(zh/en)

  ### 通知管道增强
  - NotificationChannel MD5 去重改为批量淘汰(80% 阈值),替代逐个删除
  - 增加 debug 日志输出(过滤原因/包名)

  ### ESLint
  - 新增 eslint.config.mjs(typescript-eslint + react-hooks + react-native 规则集)
  - package.json 新增 lint/lint:fix 脚本,引入 5 个 devDependencies

  ### 文档
  - accessibility-wechat-guide.md — 微信无障碍伪装完整方案
  - modal-keyboard-guide.md — 弹窗键盘避让方案
  - ocr-pipeline-guide.md — OCR 三层层级管线
  - OCR及文本模型测试 / 账单元识别及账户分类设计 / 账户分类模型测试
2026-07-28 20:57:37 +08:00

11 KiB
Raw Blame History

账单识别与 Beancount 账户分类:合并 vs 解耦架构对比文档

在双式记账Beancount / DriftLedger离线移动客户端开发中“原始账单识别Bill Recognition“Beancount 账户分类Account Classification 是两个核心处理阶段。

本文档深度对比分析将这两个阶段进行 合并(单步一体求值)解耦(双阶段责任链) 的架构差异,并结合 DriftLedger 源码实现,分别涵盖 传统规则渠道Rule-based大模型/AI 渠道LLM/VLM 的表现。


目录

  1. 架构定义与处理模式
  2. 传统规则渠道Rule-based Channel对比
  3. 大模型/AI 渠道AI/LLM Channel对比
  4. 多维性能与工程指标全景对比表
  5. DriftLedger (浮记) 混合架构落地推荐

一、 架构定义与处理模式

在多通道系统架构中,无论采用合并还是解耦,多通道原始内容采集Ingestion Adapters 始终是前置的:

  • 通道 1拍照/图库 OCR(获取账单图像或 OCR Markdown 文本)
  • 通道 2无障碍服务 UI 节点提取(解析微信/支付宝支付成功页的 node 文本,参见 accessibilityParser.ts
  • 通道 3短信与系统通知监控(解析银行/支付软件推送通知字符串)

合并与解耦的核心区别,在于拿到账单原始内容 (Raw Bill Text) 之后,“事实字段识别”“Beancount 账户分配” 是在单次操作中完成,还是拆分为多个阶段:

========================================================================================
【多通道多源输入】
 (通道A: 拍照OCR文本 / 通道B: 无障碍UI节点文本 / 通道C: 短信通知文本)
                          │
                          ▼
【合并架构 (Merged Mode / 一体化文本求值)】
  单次求值 (AI Prompt 或 规则引擎):
  输入: 账单原始内容 + 用户 Beancount 动态账户列表
  输出: 直接一步得到【Beancount 交易草稿 TransactionDraft】(含时间、金额、商户、资金账户、支出账户)
========================================================================================

========================================================================================
【多通道多源输入】
 (通道A: 拍照OCR文本 / 通道B: 无障碍UI节点文本 / 通道C: 短信通知文本)
                          │
                          ▼
【解耦架构 (Decoupled Mode / 责任链流水线)】
  阶段 1事实识别: 仅识别事实输出无账户关联的【ImportedEvent】(时间、金额、商户、备注)
                          │
                          ▼
  阶段 2账户分类: 送入 BillPipeline由规则引擎 RuleEngine 或轻量 AI 匹配 Beancount 账户
========================================================================================

二、 传统规则渠道Rule-based Channel对比

在规则匹配渠道下,账户名均来自用户动态配置的规则库 (Rule[]),绝非代码硬编码:

1. 动态规则合并模式Single-Pass Rule Evaluation / 一体化动态匹配)

  • 处理逻辑 拿到多通道的原始内容后,解析器直接调用用户配置的动态规则库 Rule[]。一条规则同时包含了事实判定条件双向账户分配
    // 用户在 APP 中配置的动态规则对象(非代码硬编码)
    const rule: Rule = {
      id: "rule-101",
      counterpartyContains: "星巴克",
      sourceAccount: "Assets:Alipay:Balance",      // 动态资金来源账户
      categoryAccount: "Expenses:Food:Coffee",    // 动态分类支出账户
      narration: "星巴克咖啡"
    };
    
    // 单次求值:一步构造出带有账户的完整 TransactionDraft 草稿
    if (matches(rawText, rule)) {
      return createDraftDirectly(rawText, rule.sourceAccount, rule.categoryAccount);
    }
    
  • 特点 单次求值即产生最终 TransactionDraft。如果入口预填了 sourceAccount / categoryAccount(参考 DriftLedger 代码 billPipeline.ts:L169-L180),流水线直接接受该草稿。

2. 规则解耦模式Two-Stage Evaluation / 责任链流水线匹配)

  • 处理逻辑
    1. 阶段 1 (事实化):所有通道(通知、无障碍、短信、账单 CSV统一输出仅包含事实的 ImportedEvent(无账户关联)。
    2. 阶段 2 (责任链评估)BillPipeline 依次执行:转账识别 ➔ 批次去重 ➔ 历史去重 ➔ 送入 RuleEngine 匹配规则库中的账户。
  • 特点 解析器只需关心文本事实提取,账户分配归口给 BillPipelineRuleEngine 统一调度。在分配账户前可先过滤重复事件,避免无效计算。

三、 大模型/AI 渠道AI/LLM Channel对比

利用大语言模型LLM或多模态视觉模型VLM处理多通道获取的原始内容

1. AI 文本级合并模式(单次 LLM 提示词一步直出草稿)

  • 处理逻辑 多通道(拍照 OCR / 无障碍节点文本 / 短信通知)提取到账单原始内容字符串后,在单次 LLM 请求中将“账单原始内容”与“用户本地 Beancount 候选账户列表”一同作为 Prompt 提交给大模型(如 glm-4-flash-250414Qwen2.5-7B-Instruct)。
  • 流程
    [多通道原始内容文本] + [用户动态账户列表] ───(单次 LLM 求值)───> [Beancount TransactionDraft]
    
  • 优势
    • 通道统一:不管是图片 OCR、微信支付成功的节点文本、还是银行扣款短信都使用同一种“文本级合并 Prompt”直接返回填充好 sourceAccountcategoryAccount 的 JSON 草稿。
    • 响应极快:纯文本大模型(如 glm-4-flash-250414)处理文本合并请求耗时仅 ~2.2 秒
  • 劣势
    • Token 开销:每次请求都需携带用户账户列表。
    • 确定性风险:可能偶发生成不存在的账户名。

2. AI 解耦模式(内容识别提取事实 ➔ 独立分类器)

  • 处理逻辑
    1. 阶段 1事实识别:模型仅负责解析多通道原始内容,输出不含账户的 ImportedEvent(时间、金额、商户、备注)。
    2. 阶段 2账户分类:优先走本地 RuleEngine;若未命中,再调用轻量 LLM耗时 ~2.2 秒)或向量 Embedding 模型(如 bge-m3,耗时 0.3 秒)在单独的 Prompt / 向量空间中挑选账户。
  • 优势
    • 绝对确定性:已知商户 100% 走本地规则引擎0 延迟、0 错误率)。
    • 离线优先 (Offline-first):本地识别出事实后,断网状态下本地规则引擎依然能完成账户分类。

四、 多维性能与工程指标全景对比表

评估维度 规则合并模式 (单次一体匹配) 规则解耦模式 (责任链流水线) AI 文本级合并模式 (单次LLM求值) AI 解耦模式 (双阶段链式)
原始内容来源 多通道 (OCR/无障碍/短信) 多通道 (OCR/无障碍/短信) 多通道 (OCR/无障碍/短信) 多通道 (OCR/无障碍/短信)
识别与分类点 文本解析时同步求值 分两阶段:解析事实 ➔ 匹配账户 单次 LLM 同时提取事实+账户 分两阶段:提取事实 ➔ LLM/向量选账户
响应延迟 (Latency) < 1ms < 1ms ~2.2s (glm-4-flash-250414) ~4.3s (OCR + 分类)
API 调用次数 0 次 0 次 1 次 1 ~ 2 次
断网/离线鲁棒性 完全支持 完全支持 不支持 (需在线 LLM) 半支持 (文本提取后离线规则分类)
准确率与确定性 100% 100% 高 (约 95%,受 Prompt 引导) 100% (已知规则优先覆写)
代码可维护性 良好 极佳 (领域分层极清) 优秀 (通道无缝复用 Prompt) 极佳 (管道可随意组装)

五、 DriftLedger (浮记) 混合架构落地推荐

结合多通道采集OCR / 无障碍 / 短信通知)与 DriftLedger 的代码实现 billPipeline.ts,推荐采取 “多通道输入 ➔ AI 文本级合并直出 ➔ 本地流水线预填覆写” 的最佳落地架构:

  【通道 A: 拍照 OCR 文本】     【通道 B: 无障碍 UI 文本】     【通道 C: 短信通知文本】
            │                            │                            │
            └────────────────────────────┼────────────────────────────┘
                                         ▼
                   【AI 文本级合并求值 (glm-4-flash-250414)】
              单次 LLM 传入多通道文本 + 用户 Beancount 动态账户列表
              2.2 秒内一步生成带预填账户的 `ImportedEvent` (含 sourceAccount/categoryAccount)
                                         │
                                         ▼
                     【领域核心层 / BillPipeline 责任链】
        ┌────────────────────────────────┴────────────────────────────────┐
        ▼                                                                 ▼
【1: 本地规则覆写 (RuleEngine)】                                 【2: 多通道去重 (Dedup)】
若匹配到用户定义的 100% 精确 Rule 规则,                          自动过滤历史账本已存在的重复交易,
本地规则强行覆写 AI 预测的账户,保障零差错。                       避免多次拍照或无障碍重复记账。

总结:

  1. 多通道采集OCR / 无障碍 / 短信) 是解耦的前置适配层。
  2. AI 合并模式 指的是在获取到账单文本后,用单次 LLM 请求同时完成事实提取与账户选择2.2 秒极速返回)。
  3. 架构落地:多通道文本输入 ➔ AI 文本级合并一步预填 ➔ 本地流水线校验覆写。