Rules README

Rules Manifest

copy yaml code of a rule to clipboard

This directory uses a manifest to load rules and merchant mappings.

todo: Reiseversicherung, travel, work: AWS.EMEA, GOOGLE.CLOUD, ANTHROPIC Gemischtwaren, S.Bahn Ratskeller < Restaurant NANU.NANA CIAO.BELLA

Landeshochschulkasse < edu daniela FIELMANN C.A.Haus.0564/Berlin ?? Reformhaus

museum HUMANA

Register other accounts: trade republic DE23100123450351479101

Structure

  • manifest.yaml: entry point for loading files
  • merchants/*.yaml: merchant normalization + defaults
  • rules/system/*.yaml: built-in system rules
  • rules/user/*.yaml: not used here; user rules live in data/users/...
  • ml_taxonomy.yaml: optional validation metadata

Manifest Example

version: 1
profile: default
load:
  taxonomy: ml_taxonomy.yaml
  merchants:
    - merchants/base.yaml
  rules:
    system:
      - rules/system/10_transfers.yaml
      - rules/system/99_fallback.yaml

Precedence

  1. User rules from data/users/...
  2. Merchant defaults (manifest mode only)
  3. System rules (priority desc, then manifest order)
  4. Fallback (uncategorized)

V2 rules system overview (from code)

  • Rule files are YAML under data/rules and loaded via manifest.yaml when present (app/ingest/importer/pipeline/ruleset_loader.py).
  • Each file has slug, version, and a rules: list. Each rule has id, match (or if), and set (or then). Optional: enabled, scope, explain, stop (app/ingest/importer/pipeline/ rules_loader.py).
  • Rule evaluation order: user rules first, then merchant defaults (if manifest mode), then system rules in order; first matching system rule wins and stops (app/ingest/ importer/pipeline/rules.py).

How match.all works

  • match: { all: [ ... ] } requires every child match to succeed (app/ingest/importer/pipeline/rules_loader.py and _match_node in app/ingest/importer/pipeline/rules.py).
  • Each child can itself be another match block (with any/all/not) or a clause (text, amount, merchant, merchant_in).
  • If you don’t specify any/all/not and just list multiple fields inside match, they are implicitly combined as all (AND).

Match options (current) From _compile_clauses in app/ingest/importer/pipeline/rules_loader.py:

  • merchant: string or list
    • string β†’ equality
    • list β†’ membership
  • merchant_in: same behavior as merchant (just different field name)
  • text:
    • string β†’ contains (casefold)
    • { contains: "..." }
    • { matches: "regex" } (regex compiled with IGNORECASE; invalid regex errors on load)
  • amount:
    • number β†’ eq
    • { lt|lte|gt|gte|eq: number }

Logical wrappers:

  • any: list of match blocks, OR semantics
  • all: list of match blocks, AND semantics
  • not: list of match blocks; if multiple entries, they’re grouped as all and then negated

Set options (current) From _apply_rule in app/ingest/importer/pipeline/rules.py:

  • direction (money flow direction, see taxonomy below)
  • group
  • category
  • subcategory
  • tags (list or string)
  • type (stored as txn_type / cl_type, deprecated: use direction)
  • confidence
  • flags (dict; merged into cls.flags)

Also supported in user rules (_apply_user_rule):

  • recurring, recurring_period, visible, rule_active (mapped to flags)

Notes:

  • stop and explain are parsed into CompiledRule but not used in evaluation yet (app/ingest/importer/pipeline/rules_loader.py vs app/ingest/importer/pipeline/rules.py).
  • Text matching uses res.cls.full_text which is built during pipeline normalization; amount uses res.txn.amount.

Complete Taxonomy

Direction (Money Flow)

Direction Amount Meaning Example
income + External money in Salary, child benefit, dividends
expense βˆ’ External money out Groceries, rent, subscriptions
transfer_out βˆ’ To own account Savings deposit, investment
transfer_in + From own account Withdraw from savings
refund + Return from prior expense Amazon refund, insurance payout

Groups

Group Direction Purpose
fixed_costs expense Housing, insurance, tax, telecom
daily_life expense Groceries, transport, clothing, food
family expense Kids, education
fun_lifestyle expense Leisure, sports, gifts, vacation
finance_misc mixed Income, transfers, savings, refunds

Hierarchy

direction (flow)
└── group (budget category)
    └── category (specific area)
        └── subcategory (detail)

Example rule:

- id: groceries.supermarket.rewe
  match:
    merchant: REWE
  set:
    direction: expense
    group: daily_life
    category: groceries
    subcategory: supermarket
    confidence: 0.85
    tags:
      - food

Refunds

Refunds keep their original expense category:

- id: refund.amazon
  match:
    all:
    - text: { matches: amazon|amzn }
    - amount: { gt: 0 }
  set:
    direction: refund      # direction is refund
    group: daily_life      # same group as original
    category: shopping     # same category as original
    subcategory: refunds