@fuzzle/opencode-accountant 0.4.6-next.1 → 0.4.6

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.
@@ -1,611 +0,0 @@
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