@fuzzle/opencode-accountant 0.4.6 → 0.5.0
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 +5 -9
- package/agent/accountant.md +27 -87
- package/dist/index.js +502 -896
- package/docs/architecture/import-context.md +674 -0
- package/docs/tools/classify-statements.md +84 -7
- package/docs/tools/import-pipeline.md +611 -0
- package/docs/tools/import-statements.md +43 -5
- package/docs/tools/reconcile-statement.md +529 -0
- package/package.json +3 -4
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
# import-pipeline Tool
|
|
2
|
+
|
|
3
|
+
The `import-pipeline` tool orchestrates the complete bank statement import workflow, from classification through reconciliation. It coordinates all steps in sequence, ensuring data flows correctly between each stage via import contexts.
|
|
4
|
+
|
|
5
|
+
This tool is **restricted to the accountant agent only**.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The pipeline automates five sequential steps:
|
|
10
|
+
|
|
11
|
+
1. **Classify** - Detect provider/currency, create contexts, organize files
|
|
12
|
+
2. **Account Declarations** - Ensure all accounts exist in year journals
|
|
13
|
+
3. **Dry Run** - Validate transactions, check for unknown accounts
|
|
14
|
+
4. **Import** - Add transactions to journals, move files to done
|
|
15
|
+
5. **Reconcile** - Verify balances match expectations
|
|
16
|
+
|
|
17
|
+
**Key behavior**: The pipeline processes files via **import contexts**. Each classified CSV gets a unique context ID, and subsequent steps operate on these contexts **sequentially** with **fail-fast** error handling.
|
|
18
|
+
|
|
19
|
+
## Arguments
|
|
20
|
+
|
|
21
|
+
| Argument | Type | Default | Description |
|
|
22
|
+
| ---------------- | ------- | ------- | --------------------------------------------------------------------- |
|
|
23
|
+
| `closingBalance` | string | - | Manual closing balance override (e.g., `"CHF 2324.79"`) for all files |
|
|
24
|
+
| `account` | string | - | Manual hledger account override (e.g., `"assets:bank:ubs:checking"`) |
|
|
25
|
+
| `skipClassify` | boolean | `false` | If true, skip classification (assumes files already in pending) |
|
|
26
|
+
|
|
27
|
+
**Note**: These parameters apply to ALL contexts processed by the pipeline. For per-file overrides, use individual tools manually.
|
|
28
|
+
|
|
29
|
+
## Output Format
|
|
30
|
+
|
|
31
|
+
### Success - All Steps Complete
|
|
32
|
+
|
|
33
|
+
When all steps complete successfully:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"success": true,
|
|
38
|
+
"contexts": ["f47ac10b-58cc-4372-a567-0e02b2c3d479", "8b3e9c21-1a4f-4d89-b123-9f8e7d6c5b4a"],
|
|
39
|
+
"steps": {
|
|
40
|
+
"classify": {
|
|
41
|
+
"success": true,
|
|
42
|
+
"message": "Classification complete (2 context(s) created)"
|
|
43
|
+
},
|
|
44
|
+
"accountDeclarations": {
|
|
45
|
+
"success": true,
|
|
46
|
+
"message": "Account declarations complete"
|
|
47
|
+
},
|
|
48
|
+
"dryRun": {
|
|
49
|
+
"success": true,
|
|
50
|
+
"message": "Dry run complete - all transactions validated"
|
|
51
|
+
},
|
|
52
|
+
"import": {
|
|
53
|
+
"success": true,
|
|
54
|
+
"message": "Imported 42 transactions"
|
|
55
|
+
},
|
|
56
|
+
"reconcile": {
|
|
57
|
+
"success": true,
|
|
58
|
+
"message": "Balance reconciled: CHF 5432.10"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"summary": "Successfully imported 42 transaction(s) from 2 file(s)"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Note**: The `contexts` array contains the context IDs created during classification. These can be used to inspect individual import contexts in `.memory/{uuid}.json`.
|
|
66
|
+
|
|
67
|
+
### Success - No Files to Import
|
|
68
|
+
|
|
69
|
+
When the incoming directory is empty:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"success": true,
|
|
74
|
+
"contexts": [],
|
|
75
|
+
"steps": {
|
|
76
|
+
"classify": {
|
|
77
|
+
"success": true,
|
|
78
|
+
"message": "Classification complete (0 context(s) created)"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"summary": "No files to import"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Failure - Unknown Accounts in Dry Run
|
|
86
|
+
|
|
87
|
+
When dry run detects unknown accounts:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"success": false,
|
|
92
|
+
"contexts": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
|
|
93
|
+
"steps": {
|
|
94
|
+
"classify": {
|
|
95
|
+
"success": true,
|
|
96
|
+
"message": "Classification complete (1 context(s) created)"
|
|
97
|
+
},
|
|
98
|
+
"accountDeclarations": {
|
|
99
|
+
"success": true,
|
|
100
|
+
"message": "Account declarations complete"
|
|
101
|
+
},
|
|
102
|
+
"dryRun": {
|
|
103
|
+
"success": false,
|
|
104
|
+
"message": "Dry run failed: 3 transactions with unknown accounts"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"error": "Dry run validation failed: 3 transactions with unknown accounts",
|
|
108
|
+
"hint": "Update rules file to categorize unknown transactions"
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Failure - Balance Mismatch in Reconcile
|
|
113
|
+
|
|
114
|
+
When reconciliation fails:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"success": false,
|
|
119
|
+
"contexts": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
|
|
120
|
+
"steps": {
|
|
121
|
+
"classify": { "success": true },
|
|
122
|
+
"accountDeclarations": { "success": true },
|
|
123
|
+
"dryRun": { "success": true },
|
|
124
|
+
"import": { "success": true, "message": "Imported 42 transactions" },
|
|
125
|
+
"reconcile": {
|
|
126
|
+
"success": false,
|
|
127
|
+
"message": "Balance mismatch: expected CHF 5432.10, got CHF 5422.10"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"error": "Reconciliation failed: Balance mismatch",
|
|
131
|
+
"hint": "Check for missing transactions or incorrect rules"
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Pipeline Steps
|
|
136
|
+
|
|
137
|
+
### Step 1: Classify Statements
|
|
138
|
+
|
|
139
|
+
**Purpose**: Detect provider/currency, create import contexts, organize files
|
|
140
|
+
|
|
141
|
+
**What happens**:
|
|
142
|
+
|
|
143
|
+
1. Scans `import/incoming/` for CSV files
|
|
144
|
+
2. Detects provider/currency from each CSV
|
|
145
|
+
3. Creates a context (`.memory/{uuid}.json`) for each file
|
|
146
|
+
4. Extracts metadata (account number, dates, balances)
|
|
147
|
+
5. Moves files to `import/pending/{provider}/{currency}/`
|
|
148
|
+
|
|
149
|
+
**Output**: Array of context IDs
|
|
150
|
+
|
|
151
|
+
**Failure scenarios**:
|
|
152
|
+
|
|
153
|
+
- File collision (target file already exists)
|
|
154
|
+
- Configuration missing
|
|
155
|
+
- Unrecognized CSV format → moved to `import/unrecognized/`
|
|
156
|
+
|
|
157
|
+
See [classify-statements](classify-statements.md) for details.
|
|
158
|
+
|
|
159
|
+
### Step 2: Account Declarations
|
|
160
|
+
|
|
161
|
+
**Purpose**: Ensure all accounts referenced in rules files are declared in year journals
|
|
162
|
+
|
|
163
|
+
**What happens** (per context):
|
|
164
|
+
|
|
165
|
+
1. Finds CSV file in `pending/` directory
|
|
166
|
+
2. Matches CSV to rules file
|
|
167
|
+
3. Extracts all account names from rules file
|
|
168
|
+
4. Determines transaction year
|
|
169
|
+
5. Ensures accounts declared in year journal (e.g., `ledger/2026.journal`)
|
|
170
|
+
|
|
171
|
+
**Failure scenarios**:
|
|
172
|
+
|
|
173
|
+
- Rules file not found for CSV
|
|
174
|
+
- Multiple years in single CSV
|
|
175
|
+
- Cannot create year journal file
|
|
176
|
+
|
|
177
|
+
### Step 3: Dry Run
|
|
178
|
+
|
|
179
|
+
**Purpose**: Validate all transactions can be categorized before importing
|
|
180
|
+
|
|
181
|
+
**What happens** (per context):
|
|
182
|
+
|
|
183
|
+
1. Loads context to find CSV file
|
|
184
|
+
2. Runs `hledger print` with rules file
|
|
185
|
+
3. Checks for `income:unknown` or `expenses:unknown` accounts
|
|
186
|
+
4. Reports unknown postings if found
|
|
187
|
+
|
|
188
|
+
**Failure scenarios**:
|
|
189
|
+
|
|
190
|
+
- Unknown accounts detected → **Pipeline stops here**
|
|
191
|
+
- CSV parsing errors
|
|
192
|
+
- Rules file syntax errors
|
|
193
|
+
|
|
194
|
+
**User action required**: Update rules file to categorize unknown transactions, then re-run pipeline.
|
|
195
|
+
|
|
196
|
+
### Step 4: Import
|
|
197
|
+
|
|
198
|
+
**Purpose**: Add transactions to journal and mark files as processed
|
|
199
|
+
|
|
200
|
+
**What happens** (per context):
|
|
201
|
+
|
|
202
|
+
1. Loads context to find CSV file
|
|
203
|
+
2. Imports transactions using `hledger import`
|
|
204
|
+
3. Moves CSV from `pending/` to `done/`
|
|
205
|
+
4. Updates context with:
|
|
206
|
+
- `rulesFile` path
|
|
207
|
+
- `yearJournal` path
|
|
208
|
+
- `transactionCount`
|
|
209
|
+
- Updated `filePath` (now in `done/`)
|
|
210
|
+
|
|
211
|
+
**Failure scenarios**:
|
|
212
|
+
|
|
213
|
+
- Duplicate transactions detected
|
|
214
|
+
- Journal file write error
|
|
215
|
+
- File move operation fails
|
|
216
|
+
|
|
217
|
+
See [import-statements](import-statements.md) for details.
|
|
218
|
+
|
|
219
|
+
### Step 5: Reconcile
|
|
220
|
+
|
|
221
|
+
**Purpose**: Verify imported transactions result in correct closing balance
|
|
222
|
+
|
|
223
|
+
**What happens** (per context):
|
|
224
|
+
|
|
225
|
+
1. Loads context to get account number and closing balance
|
|
226
|
+
2. Finds CSV using account number (critical for multi-account)
|
|
227
|
+
3. Queries hledger for actual balance
|
|
228
|
+
4. Compares expected vs actual balance
|
|
229
|
+
5. Updates context with:
|
|
230
|
+
- `reconciledAccount`
|
|
231
|
+
- `actualBalance`
|
|
232
|
+
- `lastTransactionDate`
|
|
233
|
+
- `reconciled` (true/false)
|
|
234
|
+
|
|
235
|
+
**Failure scenarios**:
|
|
236
|
+
|
|
237
|
+
- Balance mismatch → **Pipeline fails**
|
|
238
|
+
- Missing closing balance (requires manual input)
|
|
239
|
+
- Account not determined from rules file
|
|
240
|
+
|
|
241
|
+
See [reconcile-statement](reconcile-statement.md) for details.
|
|
242
|
+
|
|
243
|
+
## Context Flow Through Pipeline
|
|
244
|
+
|
|
245
|
+
### Visual Flow
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
249
|
+
│ USER: Drop CSV files into import/incoming/ │
|
|
250
|
+
└────────────────────────────────────────────────────────────────┘
|
|
251
|
+
│
|
|
252
|
+
▼
|
|
253
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
254
|
+
│ STEP 1: classify-statements │
|
|
255
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
256
|
+
│ │ For each CSV: │ │
|
|
257
|
+
│ │ • Detect provider/currency │ │
|
|
258
|
+
│ │ • Extract metadata (account#, dates, balances) │ │
|
|
259
|
+
│ │ • Generate UUID │ │
|
|
260
|
+
│ │ • CREATE: .memory/{uuid}.json │ │
|
|
261
|
+
│ │ • Move: incoming/ → pending/{provider}/{currency}/ │ │
|
|
262
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
263
|
+
│ │
|
|
264
|
+
│ OUTPUT: ["uuid-1", "uuid-2"] │
|
|
265
|
+
└────────────────────────────────────────────────────────────────┘
|
|
266
|
+
│
|
|
267
|
+
▼
|
|
268
|
+
┌───────────────────┴───────────────────┐
|
|
269
|
+
│ │
|
|
270
|
+
▼ ▼
|
|
271
|
+
┌──────────┐ ┌──────────┐
|
|
272
|
+
│ uuid-1 │ │ uuid-2 │
|
|
273
|
+
│ checking │ │ savings │
|
|
274
|
+
└──────────┘ └──────────┘
|
|
275
|
+
│ │
|
|
276
|
+
▼ │
|
|
277
|
+
┌────────────────────────────────────────┐ │
|
|
278
|
+
│ STEPS 2-5 (for uuid-1): │ │
|
|
279
|
+
│ • Account Declarations │ │
|
|
280
|
+
│ • Dry Run │ │
|
|
281
|
+
│ • Import │ │
|
|
282
|
+
│ • Reconcile │ │
|
|
283
|
+
│ • UPDATE: .memory/uuid-1.json │ │
|
|
284
|
+
└────────────────────────────────────────┘ │
|
|
285
|
+
│ ✓ Success │
|
|
286
|
+
│ │
|
|
287
|
+
│ ▼
|
|
288
|
+
│ ┌────────────────────────────────────────┐
|
|
289
|
+
│ │ STEPS 2-5 (for uuid-2): │
|
|
290
|
+
│ │ • Account Declarations │
|
|
291
|
+
│ │ • Dry Run │
|
|
292
|
+
│ │ • Import │
|
|
293
|
+
│ │ • Reconcile │
|
|
294
|
+
│ │ • UPDATE: .memory/uuid-2.json │
|
|
295
|
+
│ └────────────────────────────────────────┘
|
|
296
|
+
│ │ ✓ Success
|
|
297
|
+
│ │
|
|
298
|
+
▼ ▼
|
|
299
|
+
┌──────────┐ ┌──────────┐
|
|
300
|
+
│ Complete │ │ Complete │
|
|
301
|
+
└──────────┘ └──────────┘
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Sequential Processing (Fail-Fast)
|
|
305
|
+
|
|
306
|
+
The pipeline processes contexts **one at a time**:
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
for each contextId:
|
|
310
|
+
1. Load context
|
|
311
|
+
2. Run steps 2-5 for this context
|
|
312
|
+
3. If ANY step fails → STOP PIPELINE
|
|
313
|
+
4. Update context with results
|
|
314
|
+
5. Move to next context
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Example**: If you have 2 CSVs (checking and savings):
|
|
318
|
+
|
|
319
|
+
- Process checking account (uuid-1) through all steps
|
|
320
|
+
- If checking succeeds → Process savings account (uuid-2)
|
|
321
|
+
- If checking fails → **Stop immediately**, savings not processed
|
|
322
|
+
|
|
323
|
+
**Why fail-fast?**
|
|
324
|
+
|
|
325
|
+
- Easier debugging (focus on one failure at a time)
|
|
326
|
+
- Prevents cascading errors
|
|
327
|
+
- Maintains data consistency
|
|
328
|
+
|
|
329
|
+
## Typical Usage
|
|
330
|
+
|
|
331
|
+
### Scenario 1: Basic Import
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# Step 1: Drop CSV files in import/incoming/
|
|
335
|
+
cp bank-statement.csv import/incoming/
|
|
336
|
+
|
|
337
|
+
# Step 2: Run pipeline
|
|
338
|
+
import-pipeline
|
|
339
|
+
|
|
340
|
+
# Step 3: Check results
|
|
341
|
+
# - Files moved to import/done/
|
|
342
|
+
# - Transactions in ledger journals
|
|
343
|
+
# - Contexts in .memory/
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Scenario 2: Multi-Account Import
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
# Drop multiple CSVs
|
|
350
|
+
cp ubs-checking-2026-02.csv import/incoming/
|
|
351
|
+
cp ubs-savings-2026-02.csv import/incoming/
|
|
352
|
+
|
|
353
|
+
# Run pipeline (processes sequentially)
|
|
354
|
+
import-pipeline
|
|
355
|
+
|
|
356
|
+
# Result: Both accounts imported and reconciled independently
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Scenario 3: Manual Balance Override
|
|
360
|
+
|
|
361
|
+
When CSV doesn't include closing balance (e.g., Revolut):
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
import-pipeline --closingBalance "EUR 1234.56"
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Note**: This applies the same balance to ALL contexts. For different balances per file, use individual tools:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Classify and get contextIds
|
|
371
|
+
classify-statements
|
|
372
|
+
# Output: { "contexts": ["uuid-1", "uuid-2"] }
|
|
373
|
+
|
|
374
|
+
# Import each manually
|
|
375
|
+
import-statements --contextId "uuid-1" --checkOnly false
|
|
376
|
+
import-statements --contextId "uuid-2" --checkOnly false
|
|
377
|
+
|
|
378
|
+
# Reconcile with different balances
|
|
379
|
+
reconcile-statement --contextId "uuid-1" --closingBalance "EUR 1000.00"
|
|
380
|
+
reconcile-statement --contextId "uuid-2" --closingBalance "EUR 2000.00"
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Scenario 4: Skip Classification
|
|
384
|
+
|
|
385
|
+
When files are already in `import/pending/`:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
import-pipeline --skipClassify
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Warning**: When `skipClassify` is true, the pipeline skips context creation entirely. If no contexts exist from a prior `classify-statements` run, the pipeline returns immediately with "No files to import". Only use this flag after running `classify-statements` separately.
|
|
392
|
+
|
|
393
|
+
### Scenario 5: Handling Unknown Accounts
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
# First run: dry run fails
|
|
397
|
+
import-pipeline
|
|
398
|
+
# Output: "Dry run failed: 3 transactions with unknown accounts"
|
|
399
|
+
|
|
400
|
+
# Check which transactions are unknown
|
|
401
|
+
import-statements --contextId {from-output} --checkOnly true
|
|
402
|
+
# Shows unknown postings with suggestions
|
|
403
|
+
|
|
404
|
+
# Update rules file
|
|
405
|
+
echo "if %description COFFEE SHOP" >> ledger/rules/revolut-eur.rules
|
|
406
|
+
echo " account2 expenses:food:coffee" >> ledger/rules/revolut-eur.rules
|
|
407
|
+
|
|
408
|
+
# Re-run pipeline (will pick up where it left off)
|
|
409
|
+
import-pipeline --skipClassify
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Error Handling
|
|
413
|
+
|
|
414
|
+
### Common Error Patterns
|
|
415
|
+
|
|
416
|
+
| Error | Step | Cause | Resolution |
|
|
417
|
+
| ----------------------- | ------------------- | ----------------------------- | --------------------------------------------------- |
|
|
418
|
+
| File collision | Classify | Target file already exists | Move existing file to done or delete |
|
|
419
|
+
| Unrecognized CSV | Classify | Unknown provider | Add provider config or move to pending manually |
|
|
420
|
+
| Unknown accounts | Dry Run | Transactions without rules | Update rules file to categorize |
|
|
421
|
+
| Balance mismatch | Reconcile | Missing transactions | Check skip rules, verify all transactions imported |
|
|
422
|
+
| Context not found | Import/Reconcile | Invalid context ID | Re-run classify-statements |
|
|
423
|
+
| Multi-year CSV | Account Declaration | CSV spans multiple years | Split CSV by year or import to single year manually |
|
|
424
|
+
| Missing closing balance | Reconcile | CSV metadata incomplete | Provide `--closingBalance` parameter |
|
|
425
|
+
| Account not determined | Reconcile | Rules file missing `account1` | Add `account1` directive to rules file |
|
|
426
|
+
|
|
427
|
+
### Debugging Failed Pipelines
|
|
428
|
+
|
|
429
|
+
**Step 1: Check pipeline output**
|
|
430
|
+
|
|
431
|
+
The output shows which step failed:
|
|
432
|
+
|
|
433
|
+
```json
|
|
434
|
+
{
|
|
435
|
+
"success": false,
|
|
436
|
+
"steps": {
|
|
437
|
+
"classify": { "success": true },
|
|
438
|
+
"accountDeclarations": { "success": true },
|
|
439
|
+
"dryRun": { "success": false } // ← Failed here
|
|
440
|
+
},
|
|
441
|
+
"error": "Dry run validation failed..."
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Step 2: Run failed step manually for details**
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
# Get contextId from pipeline output
|
|
449
|
+
# contexts: ["abc123-..."]
|
|
450
|
+
|
|
451
|
+
# Run the failed step individually
|
|
452
|
+
import-statements --contextId "abc123-..." --checkOnly true
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Step 3: Fix issue and resume**
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
# Fix the issue (e.g., update rules file)
|
|
459
|
+
# Then re-run pipeline with skipClassify
|
|
460
|
+
import-pipeline --skipClassify
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Step 4: Inspect contexts**
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
# View context file
|
|
467
|
+
jq . .memory/abc123-....json
|
|
468
|
+
|
|
469
|
+
# Check what was successfully completed
|
|
470
|
+
jq '{transactionCount, reconciledAccount, reconciled}' .memory/abc123-....json
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Context Inspection
|
|
474
|
+
|
|
475
|
+
After pipeline completion, inspect contexts for audit:
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
# List all contexts
|
|
479
|
+
ls -lh .memory/
|
|
480
|
+
|
|
481
|
+
# Pretty-print a context
|
|
482
|
+
jq . .memory/f47ac10b-58cc-4372-a567-0e02b2c3d479.json
|
|
483
|
+
|
|
484
|
+
# Get reconciliation status
|
|
485
|
+
jq '{account: .reconciledAccount, reconciled, actualBalance}' .memory/*.json
|
|
486
|
+
|
|
487
|
+
# Find failed reconciliations
|
|
488
|
+
jq 'select(.reconciled == false)' .memory/*.json
|
|
489
|
+
|
|
490
|
+
# Get transaction counts
|
|
491
|
+
jq '{file: .filename, count: .transactionCount}' .memory/*.json
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Sequential vs Parallel Processing
|
|
495
|
+
|
|
496
|
+
### Current Design: Sequential (Fail-Fast)
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
Context 1: classify → import → reconcile ✓
|
|
500
|
+
↓
|
|
501
|
+
Context 2: classify → import → reconcile ✓
|
|
502
|
+
↓
|
|
503
|
+
Context 3: classify → import → reconcile ✓
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
**Advantages**:
|
|
507
|
+
|
|
508
|
+
- Simple error handling
|
|
509
|
+
- Clear failure point
|
|
510
|
+
- No race conditions
|
|
511
|
+
- Easy to debug
|
|
512
|
+
|
|
513
|
+
**Disadvantage**:
|
|
514
|
+
|
|
515
|
+
- Slower for many files
|
|
516
|
+
|
|
517
|
+
### Why Not Parallel?
|
|
518
|
+
|
|
519
|
+
Parallel processing was considered but rejected:
|
|
520
|
+
|
|
521
|
+
```
|
|
522
|
+
Context 1: classify → import → reconcile ✓
|
|
523
|
+
Context 2: classify → import → reconcile ❌ (balance mismatch)
|
|
524
|
+
Context 3: classify → import → reconcile ✓
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**Problems with parallel**:
|
|
528
|
+
|
|
529
|
+
- Complex error handling (partial success)
|
|
530
|
+
- Potential journal corruption
|
|
531
|
+
- Harder to debug
|
|
532
|
+
- Need rollback mechanism
|
|
533
|
+
- Unclear which failed first
|
|
534
|
+
|
|
535
|
+
**Decision**: Sequential processing is simpler and more reliable. Performance is adequate for typical import volumes (1-10 files).
|
|
536
|
+
|
|
537
|
+
## Integration with Other Tools
|
|
538
|
+
|
|
539
|
+
### Called by Pipeline
|
|
540
|
+
|
|
541
|
+
The pipeline invokes these tools internally:
|
|
542
|
+
|
|
543
|
+
1. `classify-statements` → Returns context IDs
|
|
544
|
+
2. `import-statements` (per context) → Updates contexts
|
|
545
|
+
3. `reconcile-statement` (per context) → Updates contexts
|
|
546
|
+
|
|
547
|
+
### Manual Tool Usage
|
|
548
|
+
|
|
549
|
+
You can run tools independently for more control:
|
|
550
|
+
|
|
551
|
+
```bash
|
|
552
|
+
# Fine-grained control
|
|
553
|
+
classify-statements
|
|
554
|
+
# → Get contextIds
|
|
555
|
+
|
|
556
|
+
import-statements --contextId {uuid} --checkOnly true
|
|
557
|
+
# → Validate first
|
|
558
|
+
|
|
559
|
+
import-statements --contextId {uuid} --checkOnly false
|
|
560
|
+
# → Import
|
|
561
|
+
|
|
562
|
+
reconcile-statement --contextId {uuid}
|
|
563
|
+
# → Reconcile
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### When to Use Pipeline vs Individual Tools
|
|
567
|
+
|
|
568
|
+
**Use import-pipeline when**:
|
|
569
|
+
|
|
570
|
+
- Standard import workflow
|
|
571
|
+
- Multiple files with same parameters
|
|
572
|
+
- Want automatic end-to-end processing
|
|
573
|
+
|
|
574
|
+
**Use individual tools when**:
|
|
575
|
+
|
|
576
|
+
- Need different parameters per file
|
|
577
|
+
- Debugging specific step
|
|
578
|
+
- Want to inspect results between steps
|
|
579
|
+
- Handling complex edge cases
|
|
580
|
+
|
|
581
|
+
## Performance
|
|
582
|
+
|
|
583
|
+
### Timing
|
|
584
|
+
|
|
585
|
+
For typical import (1-2 CSVs, 50-100 transactions each):
|
|
586
|
+
|
|
587
|
+
- **Classify**: <1 second
|
|
588
|
+
- **Account Declarations**: <1 second
|
|
589
|
+
- **Dry Run**: 1-2 seconds (hledger processing)
|
|
590
|
+
- **Import**: 1-2 seconds (hledger processing)
|
|
591
|
+
- **Reconcile**: 1-2 seconds (hledger queries)
|
|
592
|
+
|
|
593
|
+
**Total**: ~5-10 seconds per context
|
|
594
|
+
|
|
595
|
+
### Scalability
|
|
596
|
+
|
|
597
|
+
- **10 CSVs**: ~60-100 seconds (sequential)
|
|
598
|
+
- **100 CSVs**: ~10-15 minutes (sequential)
|
|
599
|
+
|
|
600
|
+
If you regularly import 50+ CSVs, consider:
|
|
601
|
+
|
|
602
|
+
- Batching by provider/currency
|
|
603
|
+
- Running pipeline multiple times for different subsets
|
|
604
|
+
- Using manual tool invocation for parallel control
|
|
605
|
+
|
|
606
|
+
## See Also
|
|
607
|
+
|
|
608
|
+
- [Import Context Architecture](../architecture/import-context.md) - Deep dive into context system
|
|
609
|
+
- [classify-statements Tool](classify-statements.md) - Step 1: Classification
|
|
610
|
+
- [import-statements Tool](import-statements.md) - Step 4: Import
|
|
611
|
+
- [reconcile-statement Tool](reconcile-statement.md) - Step 5: Reconciliation
|
|
@@ -5,6 +5,8 @@ The `import-statements` tool imports classified CSV bank statements into hledger
|
|
|
5
5
|
- **Check mode** (`checkOnly: true`, default): Validates transactions and reports any that cannot be categorized
|
|
6
6
|
- **Import mode** (`checkOnly: false`): Imports validated transactions and moves processed files to the done directory
|
|
7
7
|
|
|
8
|
+
**Important**: This tool requires a `contextId` from a prior `classify-statements` run. The context provides the file path and metadata needed for import. See [Import Context Architecture](../architecture/import-context.md) for details.
|
|
9
|
+
|
|
8
10
|
## Year-Based Journal Routing
|
|
9
11
|
|
|
10
12
|
Transactions are automatically routed to year-specific journal files based on transaction dates:
|
|
@@ -19,13 +21,47 @@ Transactions are automatically routed to year-specific journal files based on tr
|
|
|
19
21
|
|
|
20
22
|
**Constraint:** Each CSV file must contain transactions from a single year. CSVs with transactions spanning multiple years are rejected during check mode with an error message listing the years found.
|
|
21
23
|
|
|
24
|
+
## Using Context IDs
|
|
25
|
+
|
|
26
|
+
The tool uses import contexts to locate CSV files and access metadata:
|
|
27
|
+
|
|
28
|
+
### How It Works
|
|
29
|
+
|
|
30
|
+
1. **classify-statements** creates a context for each CSV with a unique ID
|
|
31
|
+
2. **import-statements** receives the contextId (via import-pipeline or manual invocation)
|
|
32
|
+
3. Tool loads the context from `.memory/{contextId}.json`
|
|
33
|
+
4. Context provides the file path to the CSV (no need to search by provider/currency)
|
|
34
|
+
5. After import, tool updates context with results (rules file, year journal, transaction count)
|
|
35
|
+
|
|
36
|
+
### Manual Invocation Example
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Step 1: Classify statements
|
|
40
|
+
classify-statements
|
|
41
|
+
# Output: { "classified": [{ "contextId": "abc123-...", ... }] }
|
|
42
|
+
|
|
43
|
+
# Step 2: Import using contextId
|
|
44
|
+
import-statements --contextId "abc123-..." --checkOnly false
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Automatic Invocation via Pipeline
|
|
48
|
+
|
|
49
|
+
When using `import-pipeline`, context IDs are passed automatically:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Pipeline handles everything
|
|
53
|
+
import-pipeline
|
|
54
|
+
# Internally: classify → get contextIds → import each context → reconcile each context
|
|
55
|
+
```
|
|
56
|
+
|
|
22
57
|
## Arguments
|
|
23
58
|
|
|
24
|
-
| Argument | Type | Default | Description
|
|
25
|
-
| ----------- | ------- | ------- |
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
|
|
59
|
+
| Argument | Type | Required | Default | Description |
|
|
60
|
+
| ----------- | ------- | -------- | ------- | -------------------------------------------------- |
|
|
61
|
+
| `contextId` | string | Yes | - | Context ID from `classify-statements` (e.g., UUID) |
|
|
62
|
+
| `checkOnly` | boolean | No | `true` | If true, only validate without importing |
|
|
63
|
+
|
|
64
|
+
**Note**: When called via `import-pipeline`, the `contextId` is passed automatically. For manual invocation, get the `contextId` from `classify-statements` output.
|
|
29
65
|
|
|
30
66
|
## Output Format
|
|
31
67
|
|
|
@@ -61,6 +97,8 @@ When all transactions have matching rules:
|
|
|
61
97
|
}
|
|
62
98
|
```
|
|
63
99
|
|
|
100
|
+
**Note**: When invoked via `import-pipeline`, the CSV file path comes from the import context (loaded via `contextId`). The context also provides metadata like account number and closing balance.
|
|
101
|
+
|
|
64
102
|
### Check Mode - Unknown Postings Found
|
|
65
103
|
|
|
66
104
|
When transactions don't match any `if` pattern in the rules file, the tool returns the full CSV row data for each unknown posting to provide context for classification:
|