@fuzzle/opencode-accountant 0.0.12-next.1 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -23
- package/agent/accountant.md +5 -105
- package/package.json +2 -3
- package/docs/tools/classify-statements.md +0 -404
- package/docs/tools/import-statements.md +0 -252
- package/docs/tools/update-prices.md +0 -581
package/README.md
CHANGED
|
@@ -93,10 +93,10 @@ The `classify-statements` tool classifies bank statement CSV files by provider a
|
|
|
93
93
|
|
|
94
94
|
```yaml
|
|
95
95
|
paths:
|
|
96
|
-
import: import
|
|
97
|
-
pending: import
|
|
98
|
-
done:
|
|
99
|
-
unrecognized: import/unrecognized
|
|
96
|
+
import: statements/import
|
|
97
|
+
pending: doc/agent/todo/import
|
|
98
|
+
done: doc/agent/done/import
|
|
99
|
+
unrecognized: statements/import/unrecognized
|
|
100
100
|
rules: ledger/rules
|
|
101
101
|
|
|
102
102
|
providers:
|
|
@@ -176,31 +176,35 @@ providers:
|
|
|
176
176
|
your-project/
|
|
177
177
|
├── config/
|
|
178
178
|
│ └── import/
|
|
179
|
-
│ └── providers.yaml
|
|
179
|
+
│ └── providers.yaml
|
|
180
180
|
├── ledger/
|
|
181
|
-
│ └── rules/
|
|
182
|
-
│ └── <provider>-{account-number}.rules
|
|
183
|
-
├──
|
|
184
|
-
│
|
|
185
|
-
│
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
│
|
|
190
|
-
│
|
|
191
|
-
│
|
|
181
|
+
│ └── rules/ # hledger rules files
|
|
182
|
+
│ └── <provider>-{account-number}.rules # {account-number} from metadata extraction
|
|
183
|
+
├── statements/
|
|
184
|
+
│ └── import/ # Drop CSV files here
|
|
185
|
+
│ └── unrecognized/ # Unclassified files moved here
|
|
186
|
+
└── doc/
|
|
187
|
+
└── agent/
|
|
188
|
+
├── todo/
|
|
189
|
+
│ └── import/
|
|
190
|
+
│ └── <provider>/ # e.g. revolut
|
|
191
|
+
│ └── <currency>/ # e.g. chf, eur, usd, btc
|
|
192
|
+
└── done/
|
|
193
|
+
└── import/
|
|
194
|
+
└── <provider>/
|
|
195
|
+
└── <currency>/
|
|
192
196
|
```
|
|
193
197
|
|
|
194
198
|
#### Workflow
|
|
195
199
|
|
|
196
|
-
1. Drop CSV files into `
|
|
200
|
+
1. Drop CSV files into `statements/import/`
|
|
197
201
|
2. Run `classify-statements` tool
|
|
198
|
-
3. Files are moved to `
|
|
199
|
-
4. Unrecognized files are moved to `
|
|
202
|
+
3. Files are moved to `doc/agent/todo/import/<provider>/<currency>/`
|
|
203
|
+
4. Unrecognized files are moved to `statements/import/unrecognized/`
|
|
200
204
|
5. Run `import-statements` with `checkOnly: true` to validate transactions
|
|
201
205
|
6. If unknown postings found: Add rules to the `.rules` file, repeat step 5
|
|
202
206
|
7. Once all transactions match: Run `import-statements` with `checkOnly: false`
|
|
203
|
-
8. Transactions are imported to journal, CSV files moved to `
|
|
207
|
+
8. Transactions are imported to journal, CSV files moved to `doc/agent/done/import/`
|
|
204
208
|
|
|
205
209
|
### Statement Import
|
|
206
210
|
|
|
@@ -219,13 +223,11 @@ The `import-statements` tool imports classified CSV statements into hledger usin
|
|
|
219
223
|
The tool matches CSV files to their rules files by parsing the `source` directive in each `.rules` file. For example, if `ubs-account.rules` contains:
|
|
220
224
|
|
|
221
225
|
```
|
|
222
|
-
source ../../import/
|
|
226
|
+
source ../../doc/agent/todo/import/ubs/chf/transactions.csv
|
|
223
227
|
```
|
|
224
228
|
|
|
225
229
|
The tool will use that rules file when processing `transactions.csv`.
|
|
226
230
|
|
|
227
|
-
**Note:** The `source` path should match your configured `{paths.pending}` directory structure.
|
|
228
|
-
|
|
229
231
|
See the hledger documentation for details on rules file format and syntax.
|
|
230
232
|
|
|
231
233
|
#### Unknown Postings
|
package/agent/accountant.md
CHANGED
|
@@ -8,7 +8,6 @@ tools:
|
|
|
8
8
|
bash: true
|
|
9
9
|
edit: true
|
|
10
10
|
write: true
|
|
11
|
-
# MCP tools available: classify-statements, import-statements, update-prices
|
|
12
11
|
permission:
|
|
13
12
|
bash: allow
|
|
14
13
|
edit: allow
|
|
@@ -50,42 +49,15 @@ When working with accounting tasks:
|
|
|
50
49
|
1. **File organization** - Keep transactions in appropriate year journals
|
|
51
50
|
1. **Duplicate checking** - Take extra care to avoid duplicate transactions
|
|
52
51
|
1. **Unintended edits** - If a balance is off, check the journal for unintended edits
|
|
52
|
+
1. **Statement tracking** - Move processed statements to `statements/{provider}/YYYY`
|
|
53
53
|
1. **Consistency** - Maintain consistent formatting and naming conventions across all files
|
|
54
54
|
|
|
55
|
-
## Required Tools
|
|
56
|
-
|
|
57
|
-
You have access to specialized MCP tools that MUST be used for their designated tasks. Do NOT attempt to replicate their functionality with bash commands, direct hledger CLI calls, or manual file edits.
|
|
58
|
-
|
|
59
|
-
| Tool | Use For | NEVER Do Instead |
|
|
60
|
-
| --------------------- | ---------------------------------- | ------------------------------------------ |
|
|
61
|
-
| `classify-statements` | Organizing incoming CSV files | Manual file moves or bash `mv` commands |
|
|
62
|
-
| `import-statements` | Importing transactions to journals | `hledger import`, manual journal edits |
|
|
63
|
-
| `update-prices` | Fetching exchange rates | `curl` to price APIs, manual price entries |
|
|
64
|
-
|
|
65
|
-
These tools handle validation, deduplication, error checking, and file organization automatically. Bypassing them risks data corruption, duplicate transactions, and inconsistent state.
|
|
66
|
-
|
|
67
|
-
## Bash Usage Policy
|
|
68
|
-
|
|
69
|
-
Bash is allowed ONLY for:
|
|
70
|
-
|
|
71
|
-
- Validation commands: `hledger check`, `hledger-fmt`, `hledger bal`
|
|
72
|
-
- Read-only queries: `hledger print`, `hledger reg`, `hledger accounts`
|
|
73
|
-
- File inspection: `cat`, `head`, `tail` (read-only)
|
|
74
|
-
|
|
75
|
-
Bash is FORBIDDEN for:
|
|
76
|
-
|
|
77
|
-
- `hledger import` - use `import-statements` tool instead
|
|
78
|
-
- Moving/copying CSV files - use `classify-statements` tool instead
|
|
79
|
-
- Editing journal files directly - use `edit` tool only for rules files
|
|
80
|
-
- Fetching prices - use `update-prices` tool instead
|
|
81
|
-
|
|
82
55
|
## Statement Import Workflow
|
|
83
56
|
|
|
84
|
-
|
|
57
|
+
Use the `import-statements` tool to import bank statements. Do not edit the ledger manually! The workflow:
|
|
85
58
|
|
|
86
|
-
1. **Prepare**: Drop CSV files into
|
|
87
|
-
2. **Classify**: Run `classify-statements` tool to
|
|
88
|
-
- Files moved to `{paths.pending}/<provider>/<currency>/`
|
|
59
|
+
1. **Prepare**: Drop CSV files into the incoming import folder configured in `config/import/providers.yaml`
|
|
60
|
+
2. **Classify**: Run `classify-statements` tool to move files to the configured import pending folder
|
|
89
61
|
3. **Validate (check mode)**: Run `import-statements(checkOnly: true)` to validate transactions
|
|
90
62
|
4. **Handle unknowns**: If unknown postings found:
|
|
91
63
|
- Tool returns full CSV row data for each unknown posting
|
|
@@ -93,7 +65,7 @@ Bash is FORBIDDEN for:
|
|
|
93
65
|
- Create or update rules file with `if` directives to match the transaction
|
|
94
66
|
- Repeat step 3 until all postings are matched
|
|
95
67
|
5. **Import**: Once all transactions have matching rules, run `import-statements(checkOnly: false)`
|
|
96
|
-
6. **Complete**: Transactions imported to journal, CSVs moved to `
|
|
68
|
+
6. **Complete**: Transactions imported to journal, CSVs moved to `doc/agent/done/import/`
|
|
97
69
|
|
|
98
70
|
### Rules Files
|
|
99
71
|
|
|
@@ -101,75 +73,3 @@ Bash is FORBIDDEN for:
|
|
|
101
73
|
- Match CSV to rules file via the `source` directive in each `.rules` file
|
|
102
74
|
- Use field names from the `fields` directive for matching
|
|
103
75
|
- Unknown account pattern: `income:unknown` (positive amounts) / `expenses:unknown` (negative amounts)
|
|
104
|
-
|
|
105
|
-
## Tool Usage Reference
|
|
106
|
-
|
|
107
|
-
The following are MCP tools available to you. Always call these tools directly - do not attempt to replicate their behavior with shell commands.
|
|
108
|
-
|
|
109
|
-
### classify-statements
|
|
110
|
-
|
|
111
|
-
**Purpose:** Organizes CSV files by auto-detecting provider and currency.
|
|
112
|
-
|
|
113
|
-
**Usage:** `classify-statements()` (no arguments)
|
|
114
|
-
|
|
115
|
-
**Behavior:**
|
|
116
|
-
|
|
117
|
-
- Scans `{paths.import}` for CSV files
|
|
118
|
-
- Detects provider using header matching + filename patterns
|
|
119
|
-
- Moves classified files to `{paths.pending}/<provider>/<currency>/`
|
|
120
|
-
- Moves unrecognized files to `{paths.unrecognized}/`
|
|
121
|
-
- Aborts if any file collision detected (no partial moves)
|
|
122
|
-
|
|
123
|
-
**Output:** Returns classified/unrecognized file lists with target paths
|
|
124
|
-
|
|
125
|
-
**Common issues:**
|
|
126
|
-
|
|
127
|
-
- Unrecognized files → Add provider config to `config/import/providers.yaml`
|
|
128
|
-
- Collisions → Move/rename existing pending files before re-running
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
### import-statements
|
|
133
|
-
|
|
134
|
-
**Purpose:** Imports classified CSV transactions into hledger journals.
|
|
135
|
-
|
|
136
|
-
**Usage:**
|
|
137
|
-
|
|
138
|
-
- Check mode (default): `import-statements(checkOnly: true)` or `import-statements()`
|
|
139
|
-
- Import mode: `import-statements(checkOnly: false)`
|
|
140
|
-
|
|
141
|
-
**Behavior:**
|
|
142
|
-
|
|
143
|
-
- Processes CSV files in `{paths.pending}/<provider>/<currency>/`
|
|
144
|
-
- Matches each CSV to rules file via `source` directive
|
|
145
|
-
- Check mode: Validates transactions, reports unknown postings with full CSV row data
|
|
146
|
-
- Import mode: Only proceeds if ALL transactions have known accounts, moves CSVs to `{paths.done}/`
|
|
147
|
-
|
|
148
|
-
**Output:** Returns per-file results with transaction counts and unknown postings (if any)
|
|
149
|
-
|
|
150
|
-
**Required for import:**
|
|
151
|
-
|
|
152
|
-
- All transactions must have matching rules (no `income:unknown` or `expenses:unknown`)
|
|
153
|
-
- Each CSV must have a corresponding `.rules` file in `{paths.rules}`
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
### update-prices
|
|
158
|
-
|
|
159
|
-
**Purpose:** Fetches currency exchange rates and updates `ledger/currencies/` journals.
|
|
160
|
-
|
|
161
|
-
**Usage:**
|
|
162
|
-
|
|
163
|
-
- Daily mode (default): `update-prices()` or `update-prices(backfill: false)`
|
|
164
|
-
- Backfill mode: `update-prices(backfill: true)`
|
|
165
|
-
|
|
166
|
-
**Behavior:**
|
|
167
|
-
|
|
168
|
-
- Daily mode: Fetches yesterday's prices only
|
|
169
|
-
- Backfill mode: Fetches from `backfill_date` (or Jan 1 of current year) to yesterday
|
|
170
|
-
- Updates journal files in-place with deduplication (newer prices overwrite older for same date)
|
|
171
|
-
- Processes all currencies independently (partial failures possible)
|
|
172
|
-
|
|
173
|
-
**Output:** Returns per-currency results with latest price line or error message
|
|
174
|
-
|
|
175
|
-
**Configuration:** `config/prices.yaml` defines currencies, sources, pairs, and backfill dates
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzzle/opencode-accountant",
|
|
3
|
-
"version": "0.0.12
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "An OpenCode accounting agent, specialized in double-entry-bookkepping with hledger",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ali bengali",
|
|
@@ -23,9 +23,8 @@
|
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
|
-
"agent",
|
|
27
26
|
"dist",
|
|
28
|
-
"
|
|
27
|
+
"agent"
|
|
29
28
|
],
|
|
30
29
|
"dependencies": {
|
|
31
30
|
"@opencode-ai/plugin": "latest",
|
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
# classify-statements Tool
|
|
2
|
-
|
|
3
|
-
The `classify-statements` tool organizes bank statement CSV files by automatically detecting their provider and currency, then moves them to the appropriate directories for import processing.
|
|
4
|
-
|
|
5
|
-
This tool is **restricted to the accountant agent only**.
|
|
6
|
-
|
|
7
|
-
## Arguments
|
|
8
|
-
|
|
9
|
-
| Argument | Type | Default | Description |
|
|
10
|
-
| -------- | ---- | ------- | ---------------------------- |
|
|
11
|
-
| (none) | - | - | This tool takes no arguments |
|
|
12
|
-
|
|
13
|
-
## Output Format
|
|
14
|
-
|
|
15
|
-
**Note on paths:** All file paths use `{paths.*}` variables configured in `config/import/providers.yaml`. Default values:
|
|
16
|
-
|
|
17
|
-
- `{paths.import}` = `import/incoming`
|
|
18
|
-
- `{paths.pending}` = `import/pending`
|
|
19
|
-
- `{paths.unrecognized}` = `import/unrecognized`
|
|
20
|
-
|
|
21
|
-
### Success - All Files Classified
|
|
22
|
-
|
|
23
|
-
When all CSV files are successfully classified:
|
|
24
|
-
|
|
25
|
-
```json
|
|
26
|
-
{
|
|
27
|
-
"success": true,
|
|
28
|
-
"classified": [
|
|
29
|
-
{
|
|
30
|
-
"filename": "transactions-ubs-2026-02.csv",
|
|
31
|
-
"provider": "ubs",
|
|
32
|
-
"currency": "chf",
|
|
33
|
-
"targetPath": "{paths.pending}/ubs/chf/transactions-ubs-2026-02.csv"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"filename": "account-statement_2026-02.csv",
|
|
37
|
-
"provider": "revolut",
|
|
38
|
-
"currency": "eur",
|
|
39
|
-
"targetPath": "{paths.pending}/revolut/eur/account-statement_2026-02.csv"
|
|
40
|
-
}
|
|
41
|
-
],
|
|
42
|
-
"unrecognized": [],
|
|
43
|
-
"summary": {
|
|
44
|
-
"total": 2,
|
|
45
|
-
"classified": 2,
|
|
46
|
-
"unrecognized": 0
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Success - With Filename Renaming
|
|
52
|
-
|
|
53
|
-
When provider config includes `renamePattern` with metadata extraction:
|
|
54
|
-
|
|
55
|
-
```json
|
|
56
|
-
{
|
|
57
|
-
"success": true,
|
|
58
|
-
"classified": [
|
|
59
|
-
{
|
|
60
|
-
"filename": "transactions-ubs-0235-90250546.csv",
|
|
61
|
-
"originalFilename": "export.csv",
|
|
62
|
-
"provider": "ubs",
|
|
63
|
-
"currency": "chf",
|
|
64
|
-
"targetPath": "{paths.pending}/ubs/chf/transactions-ubs-0235-90250546.csv"
|
|
65
|
-
}
|
|
66
|
-
],
|
|
67
|
-
"unrecognized": [],
|
|
68
|
-
"summary": {
|
|
69
|
-
"total": 1,
|
|
70
|
-
"classified": 1,
|
|
71
|
-
"unrecognized": 0
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
The `originalFilename` field appears when the file was renamed using metadata extraction.
|
|
77
|
-
|
|
78
|
-
### Success - Some Files Unrecognized
|
|
79
|
-
|
|
80
|
-
When some files cannot be classified:
|
|
81
|
-
|
|
82
|
-
```json
|
|
83
|
-
{
|
|
84
|
-
"success": true,
|
|
85
|
-
"classified": [
|
|
86
|
-
{
|
|
87
|
-
"filename": "transactions-ubs-2026-02.csv",
|
|
88
|
-
"provider": "ubs",
|
|
89
|
-
"currency": "chf",
|
|
90
|
-
"targetPath": "{paths.pending}/ubs/chf/transactions-ubs-2026-02.csv"
|
|
91
|
-
}
|
|
92
|
-
],
|
|
93
|
-
"unrecognized": [
|
|
94
|
-
{
|
|
95
|
-
"filename": "mystery-bank.csv",
|
|
96
|
-
"targetPath": "{paths.unrecognized}/mystery-bank.csv"
|
|
97
|
-
}
|
|
98
|
-
],
|
|
99
|
-
"summary": {
|
|
100
|
-
"total": 2,
|
|
101
|
-
"classified": 1,
|
|
102
|
-
"unrecognized": 1
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Unrecognized files are moved to `{paths.unrecognized}` for manual review.
|
|
108
|
-
|
|
109
|
-
### Failure - File Collisions
|
|
110
|
-
|
|
111
|
-
When target files already exist (prevents overwriting):
|
|
112
|
-
|
|
113
|
-
```json
|
|
114
|
-
{
|
|
115
|
-
"success": false,
|
|
116
|
-
"error": "Cannot classify: 1 file(s) would overwrite existing pending files.",
|
|
117
|
-
"collisions": [
|
|
118
|
-
{
|
|
119
|
-
"filename": "transactions.csv",
|
|
120
|
-
"existingPath": "{paths.pending}/ubs/chf/transactions.csv"
|
|
121
|
-
}
|
|
122
|
-
],
|
|
123
|
-
"classified": [],
|
|
124
|
-
"unrecognized": []
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
**Important:** The tool uses a two-pass approach (detect → check collisions → move) to prevent partial classification. If ANY collision is detected, NO files are moved.
|
|
129
|
-
|
|
130
|
-
### Configuration Error
|
|
131
|
-
|
|
132
|
-
When `config/import/providers.yaml` is missing or invalid:
|
|
133
|
-
|
|
134
|
-
```json
|
|
135
|
-
{
|
|
136
|
-
"success": false,
|
|
137
|
-
"error": "Failed to load configuration: config/import/providers.yaml not found",
|
|
138
|
-
"classified": [],
|
|
139
|
-
"unrecognized": []
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### Agent Restriction Error
|
|
144
|
-
|
|
145
|
-
When called by the wrong agent:
|
|
146
|
-
|
|
147
|
-
```json
|
|
148
|
-
{
|
|
149
|
-
"success": false,
|
|
150
|
-
"error": "This tool is restricted to the accountant agent only.",
|
|
151
|
-
"hint": "Use: Task(subagent_type='accountant', prompt='classify statements')",
|
|
152
|
-
"caller": "main assistant",
|
|
153
|
-
"classified": [],
|
|
154
|
-
"unrecognized": []
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Provider Detection
|
|
159
|
-
|
|
160
|
-
The tool detects providers using rules defined in `config/import/providers.yaml`:
|
|
161
|
-
|
|
162
|
-
### Detection Methods
|
|
163
|
-
|
|
164
|
-
1. **Filename Pattern** (optional): Regex match against filename
|
|
165
|
-
2. **CSV Header** (required): Exact match of CSV header row
|
|
166
|
-
3. **Currency Field** (required): Which column contains the currency
|
|
167
|
-
|
|
168
|
-
### Detection Example
|
|
169
|
-
|
|
170
|
-
```yaml
|
|
171
|
-
providers:
|
|
172
|
-
revolut:
|
|
173
|
-
detect:
|
|
174
|
-
- filenamePattern: '^account-statement_'
|
|
175
|
-
header: 'Type,Product,Started Date,Completed Date,Description,Amount,Fee,Currency,State,Balance'
|
|
176
|
-
currencyField: Currency
|
|
177
|
-
currencies:
|
|
178
|
-
CHF: chf
|
|
179
|
-
EUR: eur
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
Detection process:
|
|
183
|
-
|
|
184
|
-
1. Check if filename matches `filenamePattern` (if specified)
|
|
185
|
-
2. Read CSV and check if header matches exactly
|
|
186
|
-
3. Determine currency from `currencyField` column
|
|
187
|
-
4. Map raw currency value (e.g., "EUR") to normalized folder name (e.g., "eur")
|
|
188
|
-
|
|
189
|
-
### Filename Renaming
|
|
190
|
-
|
|
191
|
-
Providers can specify `renamePattern` with metadata extraction:
|
|
192
|
-
|
|
193
|
-
```yaml
|
|
194
|
-
ubs:
|
|
195
|
-
detect:
|
|
196
|
-
- header: 'Trade date,Trade time,...'
|
|
197
|
-
currencyField: Currency
|
|
198
|
-
skipRows: 9
|
|
199
|
-
delimiter: ';'
|
|
200
|
-
renamePattern: 'transactions-ubs-{account-number}.csv'
|
|
201
|
-
metadata:
|
|
202
|
-
- field: account-number
|
|
203
|
-
row: 0
|
|
204
|
-
column: 1
|
|
205
|
-
normalize: spaces-to-dashes
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
This extracts metadata from the CSV (e.g., account number from row 0, column 1) and uses it in the output filename.
|
|
209
|
-
|
|
210
|
-
## Directory Structure
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
your-project/
|
|
214
|
-
├── config/
|
|
215
|
-
│ └── import/
|
|
216
|
-
│ └── providers.yaml # Defines all paths and detection rules
|
|
217
|
-
├── {paths.import}/ # Drop CSV files here (default: import/incoming)
|
|
218
|
-
│ ├── bank1.csv
|
|
219
|
-
│ └── bank2.csv
|
|
220
|
-
│
|
|
221
|
-
├── {paths.pending}/ # Classified files (default: import/pending)
|
|
222
|
-
│ ├── <provider>/ # e.g., revolut, ubs
|
|
223
|
-
│ │ └── <currency>/ # e.g., chf, eur, usd, btc
|
|
224
|
-
│ │ └── classified.csv
|
|
225
|
-
│ ├── ubs/
|
|
226
|
-
│ │ └── chf/
|
|
227
|
-
│ │ └── transactions-ubs-0235-90250546.csv
|
|
228
|
-
│ └── revolut/
|
|
229
|
-
│ └── eur/
|
|
230
|
-
│ └── account-statement_2026-02.csv
|
|
231
|
-
│
|
|
232
|
-
└── {paths.unrecognized}/ # Unclassified files (default: import/unrecognized)
|
|
233
|
-
└── mystery-bank.csv
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## Typical Workflow
|
|
237
|
-
|
|
238
|
-
### Scenario 1: Successful Classification
|
|
239
|
-
|
|
240
|
-
1. Drop CSV files into `{paths.import}/` (e.g., `import/incoming/`)
|
|
241
|
-
2. Run `classify-statements` tool (no arguments)
|
|
242
|
-
3. Check output - all files classified successfully
|
|
243
|
-
4. Files organized in `{paths.pending}/<provider>/<currency>/`
|
|
244
|
-
5. Proceed to `import-statements` tool
|
|
245
|
-
|
|
246
|
-
### Scenario 2: Handling Unrecognized Files
|
|
247
|
-
|
|
248
|
-
1. Run `classify-statements` tool
|
|
249
|
-
2. Review `unrecognized` array in output
|
|
250
|
-
3. Check files in `{paths.unrecognized}/` directory
|
|
251
|
-
4. Options to resolve:
|
|
252
|
-
- **Add provider config**: Update `config/import/providers.yaml` with detection rules
|
|
253
|
-
- **Manual classification**: Move file to correct `{paths.pending}/<provider>/<currency>/` directory
|
|
254
|
-
- **Investigate format**: Check if CSV format matches expected patterns
|
|
255
|
-
5. Re-run tool after adding configuration
|
|
256
|
-
|
|
257
|
-
### Scenario 3: Resolving Collisions
|
|
258
|
-
|
|
259
|
-
1. Run `classify-statements` tool
|
|
260
|
-
2. Tool reports collision - file would overwrite existing file
|
|
261
|
-
3. Check `collisions` array for affected files
|
|
262
|
-
4. Options to resolve:
|
|
263
|
-
- **Archive existing**: Move existing pending file to `{paths.done}/` if already processed
|
|
264
|
-
- **Rename**: Rename one of the conflicting files
|
|
265
|
-
- **Remove**: Delete duplicate file if confirmed redundant
|
|
266
|
-
5. Re-run tool after resolving collision
|
|
267
|
-
|
|
268
|
-
**Important:** No files are moved until ALL collisions are resolved. This prevents partial/inconsistent state.
|
|
269
|
-
|
|
270
|
-
## Handling Unrecognized Files
|
|
271
|
-
|
|
272
|
-
### What "Unrecognized" Means
|
|
273
|
-
|
|
274
|
-
A file is unrecognized when:
|
|
275
|
-
|
|
276
|
-
- Filename doesn't match any `filenamePattern` (if patterns are specified)
|
|
277
|
-
- CSV header doesn't match any configured provider's `header`
|
|
278
|
-
- CSV is malformed or has unexpected structure
|
|
279
|
-
- Currency value doesn't map to configured currencies
|
|
280
|
-
|
|
281
|
-
### Common Causes
|
|
282
|
-
|
|
283
|
-
| Cause | Solution |
|
|
284
|
-
| ------------------------- | ------------------------------------------------------------------------------- |
|
|
285
|
-
| New bank/provider | Add provider config to `config/import/providers.yaml` |
|
|
286
|
-
| Non-standard CSV format | Check CSV structure; add detection rules with correct header/skipRows/delimiter |
|
|
287
|
-
| Filename pattern mismatch | Update `filenamePattern` or remove it (header-only detection) |
|
|
288
|
-
| Unknown currency | Add currency mapping to provider's `currencies` section |
|
|
289
|
-
| Metadata in header rows | Use `skipRows` to skip non-CSV rows before header |
|
|
290
|
-
| Wrong delimiter | Specify `delimiter` (e.g., `";"` for semicolon-delimited) |
|
|
291
|
-
|
|
292
|
-
### Adding Provider Detection
|
|
293
|
-
|
|
294
|
-
Example: Adding a new bank called "SwissBank":
|
|
295
|
-
|
|
296
|
-
```yaml
|
|
297
|
-
providers:
|
|
298
|
-
swissbank:
|
|
299
|
-
detect:
|
|
300
|
-
- filenamePattern: '^swissbank-'
|
|
301
|
-
header: 'Date,Description,Amount,Balance,Currency'
|
|
302
|
-
currencyField: Currency
|
|
303
|
-
currencies:
|
|
304
|
-
CHF: chf
|
|
305
|
-
EUR: eur
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
After updating config, re-run `classify-statements` to classify previously unrecognized files.
|
|
309
|
-
|
|
310
|
-
## Collision Safety
|
|
311
|
-
|
|
312
|
-
The tool uses a **two-pass approach** to ensure atomic operations:
|
|
313
|
-
|
|
314
|
-
### Two-Pass Process
|
|
315
|
-
|
|
316
|
-
**Pass 1: Detection & Planning**
|
|
317
|
-
|
|
318
|
-
- Scan all CSV files in `{paths.import}/`
|
|
319
|
-
- Detect provider/currency for each file
|
|
320
|
-
- Determine target path for each file
|
|
321
|
-
- Build complete move plan
|
|
322
|
-
|
|
323
|
-
**Pass 2: Collision Check**
|
|
324
|
-
|
|
325
|
-
- Check if ANY target file already exists
|
|
326
|
-
- If collisions found: abort with error (no files moved)
|
|
327
|
-
- If no collisions: proceed to Pass 3
|
|
328
|
-
|
|
329
|
-
**Pass 3: Move Files**
|
|
330
|
-
|
|
331
|
-
- Execute all planned moves atomically
|
|
332
|
-
- All files moved successfully or none at all
|
|
333
|
-
|
|
334
|
-
### Why This Matters
|
|
335
|
-
|
|
336
|
-
Without collision checking, partial classification could occur:
|
|
337
|
-
|
|
338
|
-
- Some files moved, others fail mid-process
|
|
339
|
-
- Inconsistent state requiring manual cleanup
|
|
340
|
-
- Risk of lost data or confusion about what was processed
|
|
341
|
-
|
|
342
|
-
With two-pass approach:
|
|
343
|
-
|
|
344
|
-
- All-or-nothing operation
|
|
345
|
-
- Easy to retry after fixing collisions
|
|
346
|
-
- No partial/inconsistent states
|
|
347
|
-
|
|
348
|
-
### Resolving Collisions
|
|
349
|
-
|
|
350
|
-
Check the `collisions` array in the error output:
|
|
351
|
-
|
|
352
|
-
```json
|
|
353
|
-
"collisions": [
|
|
354
|
-
{
|
|
355
|
-
"filename": "transactions.csv",
|
|
356
|
-
"existingPath": "{paths.pending}/ubs/chf/transactions.csv"
|
|
357
|
-
}
|
|
358
|
-
]
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
Then:
|
|
362
|
-
|
|
363
|
-
1. Inspect existing file: `cat {paths.pending}/ubs/chf/transactions.csv`
|
|
364
|
-
2. Determine if it's already processed (check if transactions were imported)
|
|
365
|
-
3. If processed: Move to `{paths.done}/` or delete
|
|
366
|
-
4. If not processed: Rename one of the files or merge manually
|
|
367
|
-
5. Re-run `classify-statements`
|
|
368
|
-
|
|
369
|
-
## Error Handling
|
|
370
|
-
|
|
371
|
-
### Common Errors
|
|
372
|
-
|
|
373
|
-
| Error | Cause | Solution |
|
|
374
|
-
| ------------------- | ------------------------------------------------- | --------------------------------------------------------------------- |
|
|
375
|
-
| File collision | Target file already exists in pending directory | Move existing file to done, rename, or delete; then re-run |
|
|
376
|
-
| Configuration error | Missing or invalid `config/import/providers.yaml` | Ensure config file exists with proper YAML syntax and required fields |
|
|
377
|
-
| Agent restriction | Called by wrong agent | Use `Task(subagent_type='accountant', prompt='classify statements')` |
|
|
378
|
-
| Permission error | Cannot read/write directories | Check file permissions on import/pending/unrecognized directories |
|
|
379
|
-
| No CSV files found | Import directory is empty | Add CSV files to `{paths.import}` directory first |
|
|
380
|
-
| CSV parsing error | Malformed CSV file | Check CSV structure; ensure proper delimiter and header row |
|
|
381
|
-
|
|
382
|
-
### Configuration File Required Fields
|
|
383
|
-
|
|
384
|
-
Ensure `config/import/providers.yaml` contains:
|
|
385
|
-
|
|
386
|
-
```yaml
|
|
387
|
-
paths:
|
|
388
|
-
import: <path> # Required
|
|
389
|
-
pending: <path> # Required
|
|
390
|
-
done: <path> # Required (used by import-statements tool)
|
|
391
|
-
unrecognized: <path> # Required
|
|
392
|
-
rules: <path> # Required (used by import-statements tool)
|
|
393
|
-
|
|
394
|
-
providers:
|
|
395
|
-
<provider-name>:
|
|
396
|
-
detect:
|
|
397
|
-
- header: <exact-csv-header> # Required
|
|
398
|
-
currencyField: <column-name> # Required
|
|
399
|
-
# Optional: filenamePattern, skipRows, delimiter, renamePattern, metadata
|
|
400
|
-
currencies:
|
|
401
|
-
<RAW-VALUE>: <normalized-folder> # Required (at least one)
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
Missing any required field will cause a configuration error.
|