传统 provider 需要在 DEG 内部新增 Go 包、注册 provider、维护 analyser/config 逻辑。随着 provider 数量增加,这种方式会让贡献和维护成本持续升高:每个 PR 都可能带来一套新逻辑,维护者需要重新 review、测试和理解 provider 私有行为,开发者也需要先理解 DEG 内部结构才能贡献。

通用模板 Provider 的目标是把“账单格式”和“导入规则”从 DEG 本体中拆出来。DEG 本体只负责读取账单、解释模板、执行规则并输出 beancount/ledger;具体账单格式由模板仓库维护。这样新增和更新 provider 时,更多工作可以集中在模板和规则文件上,DEG 开发可以更专注于规则引擎、导入体验和输出能力本身。

基本流程

账单文件
  -> 模板解析字段
  -> 统一中间结构
  -> 模板规则
  -> 个人规则
  -> beancount / ledger

模板规则用于解释账单本身,例如收支方向、退款、手续费、状态等。个人规则用于表达用户自己的账户分类习惯,例如某个商户对应哪个支出账户。个人规则后执行,因此可以覆盖模板规则的结果。

查看模板

查看模板仓库中可用的模板:

double-entry-generator template list

按关键字搜索模板:

double-entry-generator template search wechat
double-entry-generator template search 支付

如果搜索结果只有一个,命令会展开显示模板分类、标签和可 pin 的版本。

生成个人规则骨架

从模板仓库生成个人规则文件:

double-entry-generator config init wechat -o wechat-rules.yaml

也可以 pin 到指定模板版本:

double-entry-generator config init wechat@2026-04-28 -o wechat-rules.yaml

导入账单

只使用模板规则导入:

double-entry-generator import wechat bill.csv -o output.bean

使用个人规则导入:

double-entry-generator import wechat bill.csv --rules wechat-rules.yaml -o output.bean

使用指定版本的模板:

double-entry-generator import wechat@2026-04-28 bill.csv --rules wechat-rules.yaml -o output.bean

使用本地模板文件:

double-entry-generator import ./wechat.yaml bill.csv --rules wechat-rules.yaml -o output.bean

补全

生成 shell 补全脚本:

double-entry-generator completion zsh
double-entry-generator completion bash
double-entry-generator completion powershell

补全支持:

  • double-entry-generator import <TAB>:查询线上模板。
  • double-entry-generator import wechat@<TAB>:查询模板版本。
  • double-entry-generator import wechat <TAB>:补全账单文件。
  • double-entry-generator import wechat --rules <TAB>:补全个人规则 YAML。
  • double-entry-generator template <TAB>:补全模板命令。

--rules 是选填项。只有在用户输入 --- 并触发补全时,才会作为 flag 候选出现。

模板文件

模板文件描述账单如何被读取。常见字段如下:

schema: https://deg.dev/template-profile/v2
id: htsec
name: htsec
template:
    fileFormat: xlsx          # csv / xlsx / xls
    encoding: gb18030         # 可选,CSV/XLS 文本编码
    delimiter: ','            # CSV 分隔符;tab 可写 "\t"
    skipLeadingRows: 1        # 可选,跳过说明行
    skipInvalidRows: true     # 可选,跳过无法解析的汇总/空行
    sourceHeaders:
        - 交易日期
        - 交易时间
        - 金额
        - 摘要
    defaultCurrency: CNY

导入时,sourceHeaders 会成为规则可引用的字段。字段引用写作 <字段名>,例如 <金额><交易日期>

规则文件

规则文件通常包含三块:

options:
    title: 我的账本
    operatingCurrency: CNY

templateRules:
    - id: 模板基础交易
      actions:
          date: <交易日期> <交易时间>
          payee: <交易对方>
          narration: <摘要>

personalRules:
    - id: 餐饮支出
      when: <交易对方> ~ "美团"
      actions:
          to:
              account: Expenses:Food
  • options 控制输出账本标题和本位币。
  • templateRules 描述账单格式自身,通常由模板维护者提供。
  • personalRules 描述个人账户分类,通常由用户维护。
  • 规则按顺序执行;后命中的规则可以覆盖前面设置的字段或变量。

条件语法

when 用来判断规则是否命中:

when: <收/支> == "支出"
when: <金额>.number >= 10
when: <交易对方> ~ "美团"
when: <交易对方> !~ "微信"
when: (<方向> == "买入" || <方向> == "卖出") && <交易类型> == "币币交易"

支持的比较符:

  • ==!=
  • >>=<<=
  • ~ 包含
  • !~ 不包含
  • &&
  • ||

字段方法

字段可以串联方法:

<金额>.number
<金额>.+
<金额>.-
<金额>.!
<证券代码>.format("%06.0f")
<手续费>.extract("^([.0-9]+)")
raw[交易创建时间].time

常用方法:

  • .number:清理金额字符串,例如货币符号、千分位。
  • .+:强制为正数。
  • .-:强制为负数。
  • .!:反转正负。
  • .extract("regex"):用正则提取文本。
  • .format("..."):使用格式模板输出,例如 %.2f%06.0f
  • .date.time.timestamp:从时间文本中提取日期、时间或 Unix 时间戳。

金额表达式支持简单算术:

<数量>.number * <价格>.number
<手续费>.number + <印花税>.number + <过户费>.number

Actions

当前通用 runtime 的核心 actions 是:

actions:
    date: <交易时间>
    payee: <交易对象>
    narration: <商品/说明>
    note: <备注>
    amount: <金额>.number
    currency: CNY
    from:
        account: Assets:Bank
    to:
        account: Expenses:Food
    metadata:
        orderId: <订单号>
    tags:
        - Food
    vars:
        cash: Assets:Broker:Cash
    postings:
        - <var.cash> -<金额>.format("%.2f") CNY
    ignore: true

普通流水可以用 from / to 快捷生成双分录:

- id: 默认支出
  when: <收/支> == "支出"
  actions:
      from:
          account: Assets:FIXME
      to:
          account: Expenses:FIXME
      amount: <金额>.number
      currency: CNY

复杂交易建议直接写 postings,这样规则表达的是最终账本分录,而不是 provider 或 IR 私有字段:

- id: 证券买入
  when: <业务类型> == "证券买入"
  actions:
      narration: 证券买入-<证券代码>.format("%06.0f")-<证券名称>
      postings:
          - <var.cash> -<成交金额>.format("%.2f") CNY
          - <var.position> <成交数量>.format("%.2f") <var.security> {<成交价格>.format("%.3f") CNY} @@ <成交金额>.format("%.2f") CNY
          - <var.cash> -<手续费>.format("%.2f") CNY
          - <var.feeExpense> <手续费>.format("%.2f") CNY

Vars

vars 是规则变量,用来复用或覆盖账户、币种和中间值。它不是 IR 字段,只在规则渲染期间存在。

模板规则可以提供默认变量:

templateRules:
    - id: 证券默认变量
      actions:
          vars:
              cash: Assets:Broker:Cash
              position: Assets:Broker:Positions
              feeExpense: Expenses:Broker:Commission
              security: SH<证券代码>.format("%06.0f")

个人规则可以覆盖同名变量:

personalRules:
    - id: HS300ETF账户覆盖
      when: <证券名称> ~ "HS300ETF"
      actions:
          vars:
              cash: Assets:Rule1:Cash
              position: Assets:Broker:Positions:沪深300
              feeExpense: Expenses:Rule1:Commission

后续 postings 中的 <var.cash><var.position> 会使用覆盖后的值。

忽略和辅助行

辅助行可以用 ignore: true 跳过。例如某些账单把一笔业务拆成“金额行”和“数量价格行”,可以把金额-only 行忽略,在数量价格行上用规则计算金额:

- id: 拆分成交目标
  when: <成交数量> != "0" && <成交价格> != "0" && <成交金额> == "0"
  actions:
      vars:
          amount: <成交数量>.number * <成交价格>.number

- id: 拆分成交金额来源
  when: <成交金额> != "0" && <成交数量> == "0" && <成交价格> == "0"
  actions:
      ignore: true

使用建议

  • 模板规则负责解释账单格式,个人规则负责账户分类。
  • 规则 id 应描述“这段规则做什么”,例如 午餐支出HS300ETF账户覆盖
  • 普通收支优先使用 from / to
  • 投资、手续费、换汇、币币交易等复杂场景优先使用 postings
  • 不要把 provider 私有概念放进 runtime;能用 varspostings 表达的逻辑,应写在规则里。