Rules System
Automatic transaction categorization via YAML rules.
How It Works
graph LR
Txn[Transaction] --> Normalize[Normalize]
Normalize --> Merchant[Match Merchant]
Merchant --> Rules[Apply Rules]
Rules --> Result[Category + Type]
Precedence
- User rules — per-user overrides in
data/users/{id}/ - Merchant defaults — auto-category from matched merchant
- System rules — global rules in
data/rules/system/ - Fallback — uncategorized
Rule Structure
- id: netflix-subscription
match:
merchant: "Netflix"
amount: { gt: 10 }
set:
direction: expense
group: fun_lifestyle
category: entertainment
subcategory: streaming
confidence: 0.9
Match Conditions
| Field | Syntax | Example |
|---|---|---|
merchant |
string or list | "REWE" or ["REWE", "Aldi"] |
text |
contains/regex | { contains: "aws" } |
amount |
comparison | { gt: 10, lt: 100 } |
Logical Operators
match:
all: # AND
- text: { contains: "amazon" }
- amount: { gt: 0 }
any: # OR
- merchant: "Amazon"
- text: { matches: "amzn" }
not: # negation
- text: { contains: "refund" }
Set Fields
| Field | Description |
|---|---|
direction |
income, expense, transfer_out, transfer_in, refund |
group |
Budget group (see below) |
category |
Specific area |
subcategory |
Detail |
confidence |
0.0 - 1.0 |
tags |
List of tags |
Groups
| Group | Purpose |
|---|---|
fixed_costs |
Rent, insurance, tax, telecom |
daily_life |
Groceries, transport, food |
family |
Kids, education |
fun_lifestyle |
Leisure, sports, gifts |
finance_misc |
Income, transfers, savings |
File Location
data/rules/
├── manifest.yaml # Entry point
├── merchants/ # Merchant catalog (see merchants.md)
└── system/ # System rules by group
├── 10_fixed_costs/
├── 20_daily_life/
├── 30_family/
├── 40_fun_lifestyle/
├── 50_finance_misc/
└── 99_fallback.yaml
See merchants.md for merchant normalization.
[!NOTE] The UI has a user-facing explainer in
app/templates/rules/_rules_explainer.html
Keep it in sync when updating this documentation.