### 架构重构:三层分层目录化 - 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及文本模型测试 / 账单元识别及账户分类设计 / 账户分类模型测试
11 KiB
11 KiB
账单识别与 Beancount 账户分类:合并 vs 解耦架构对比文档
在双式记账(Beancount / DriftLedger)离线移动客户端开发中,“原始账单识别(Bill Recognition)” 与 “Beancount 账户分类(Account Classification)” 是两个核心处理阶段。
本文档深度对比分析将这两个阶段进行 合并(单步一体求值) 与 解耦(双阶段责任链) 的架构差异,并结合 DriftLedger 源码实现,分别涵盖 传统规则渠道(Rule-based) 与 大模型/AI 渠道(LLM/VLM) 的表现。
目录
- 架构定义与处理模式
- 传统规则渠道(Rule-based Channel)对比
- 大模型/AI 渠道(AI/LLM Channel)对比
- 多维性能与工程指标全景对比表
- 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 (事实化):所有通道(通知、无障碍、短信、账单 CSV)统一输出仅包含事实的
ImportedEvent(无账户关联)。 - 阶段 2 (责任链评估):
BillPipeline依次执行:转账识别 ➔ 批次去重 ➔ 历史去重 ➔ 送入RuleEngine匹配规则库中的账户。
- 阶段 1 (事实化):所有通道(通知、无障碍、短信、账单 CSV)统一输出仅包含事实的
- 特点:
解析器只需关心文本事实提取,账户分配归口给
BillPipeline与RuleEngine统一调度。在分配账户前可先过滤重复事件,避免无效计算。
三、 大模型/AI 渠道(AI/LLM Channel)对比
利用大语言模型(LLM)或多模态视觉模型(VLM)处理多通道获取的原始内容:
1. AI 文本级合并模式(单次 LLM 提示词一步直出草稿)
- 处理逻辑:
多通道(拍照 OCR / 无障碍节点文本 / 短信通知)提取到账单原始内容字符串后,在单次 LLM 请求中将“账单原始内容”与“用户本地 Beancount 候选账户列表”一同作为 Prompt 提交给大模型(如
glm-4-flash-250414或Qwen2.5-7B-Instruct)。 - 流程:
[多通道原始内容文本] + [用户动态账户列表] ───(单次 LLM 求值)───> [Beancount TransactionDraft] - 优势:
- 通道统一:不管是图片 OCR、微信支付成功的节点文本、还是银行扣款短信,都使用同一种“文本级合并 Prompt”,直接返回填充好
sourceAccount和categoryAccount的 JSON 草稿。 - 响应极快:纯文本大模型(如
glm-4-flash-250414)处理文本合并请求耗时仅 ~2.2 秒。
- 通道统一:不管是图片 OCR、微信支付成功的节点文本、还是银行扣款短信,都使用同一种“文本级合并 Prompt”,直接返回填充好
- 劣势:
- Token 开销:每次请求都需携带用户账户列表。
- 确定性风险:可能偶发生成不存在的账户名。
2. AI 解耦模式(内容识别提取事实 ➔ 独立分类器)
- 处理逻辑:
- 阶段 1(事实识别):模型仅负责解析多通道原始内容,输出不含账户的
ImportedEvent(时间、金额、商户、备注)。 - 阶段 2(账户分类):优先走本地
RuleEngine;若未命中,再调用轻量 LLM(耗时 ~2.2 秒)或向量 Embedding 模型(如bge-m3,耗时 0.3 秒)在单独的 Prompt / 向量空间中挑选账户。
- 阶段 1(事实识别):模型仅负责解析多通道原始内容,输出不含账户的
- 优势:
- 绝对确定性:已知商户 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 预测的账户,保障零差错。 避免多次拍照或无障碍重复记账。
总结:
- 多通道采集(OCR / 无障碍 / 短信) 是解耦的前置适配层。
- AI 合并模式 指的是在获取到账单文本后,用单次 LLM 请求同时完成事实提取与账户选择(2.2 秒极速返回)。
- 架构落地:多通道文本输入 ➔ AI 文本级合并一步预填 ➔ 本地流水线校验覆写。