@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,529 +0,0 @@
1
- # reconcile-statement Tool
2
-
3
- The `reconcile-statement` tool validates that imported bank statement transactions result in the correct closing balance. It compares the expected balance (from CSV metadata or manual input) against the actual balance calculated by hledger.
4
-
5
- This tool is **restricted to the accountant agent only**.
6
-
7
- **Important**: This tool requires a `contextId` from a prior `classify-statements` and `import-statements` run. The context provides the account number needed to find the correct CSV file, especially when multiple accounts exist for the same provider/currency. See [Import Context Architecture](../architecture/import-context.md) for details.
8
-
9
- ## Arguments
10
-
11
- | Argument | Type | Required | Default | Description |
12
- | ---------------- | ------ | -------- | ------- | -------------------------------------------------------------------- |
13
- | `contextId` | string | Yes | - | Context ID from `classify-statements` (e.g., UUID) |
14
- | `closingBalance` | string | No | - | Manual closing balance override (e.g., `"CHF 2324.79"`) |
15
- | `account` | string | No | - | Manual hledger account override (e.g., `"assets:bank:ubs:checking"`) |
16
-
17
- ### Context-Based Operation
18
-
19
- The tool operates exclusively via import contexts:
20
-
21
- 1. Loads context from `.memory/{contextId}.json`
22
- 2. Uses context's `accountNumber` to find the correct CSV file
23
- 3. Uses context's `closingBalance` (unless manually overridden)
24
- 4. Uses context's `filePath` to locate the imported CSV
25
- 5. Updates context with reconciliation results
26
-
27
- **No fallback behavior**: If `contextId` is not provided or invalid, the tool will fail immediately. This ensures correct CSV file selection in multi-account scenarios.
28
-
29
- ## Output Format
30
-
31
- ### Success - Balance Reconciled
32
-
33
- When the actual balance matches the expected balance:
34
-
35
- ```json
36
- {
37
- "success": true,
38
- "account": "assets:bank:ubs:checking",
39
- "expectedBalance": "CHF 5432.10",
40
- "actualBalance": "CHF 5432.10",
41
- "lastTransactionDate": "2026-02-28",
42
- "csvFile": "import/done/ubs/chf/ubs-0235-90250546.0-transactions-2026-02-01-to-2026-02-28.csv",
43
- "metadata": {
44
- "from-date": "2026-02-01",
45
- "until-date": "2026-02-28",
46
- "opening-balance": "4123.45",
47
- "closing-balance": "5432.10",
48
- "currency": "CHF",
49
- "account-number": "0235-90250546.0"
50
- },
51
- "note": "✓ Balance reconciled successfully"
52
- }
53
- ```
54
-
55
- ### Failure - Balance Mismatch
56
-
57
- When the actual balance doesn't match the expected balance:
58
-
59
- ```json
60
- {
61
- "success": false,
62
- "account": "assets:bank:ubs:savings",
63
- "expectedBalance": "CHF 12500.00",
64
- "actualBalance": "CHF 12490.00",
65
- "difference": "CHF -10.00",
66
- "lastTransactionDate": "2026-02-24",
67
- "csvFile": "import/done/ubs/chf/ubs-0235-90250546.1-transactions-2026-02-22-to-2026-02-24.csv",
68
- "error": "Balance mismatch: expected CHF 12500.00, got CHF 12490.00 (difference: CHF -10.00)",
69
- "hint": "Check for missing transactions or incorrect rules. Review transactions around 2026-02-24."
70
- }
71
- ```
72
-
73
- ### Error - Context Not Found
74
-
75
- When the context ID is invalid or context file doesn't exist:
76
-
77
- ```json
78
- {
79
- "success": false,
80
- "error": "Failed to load import context: abc123-...",
81
- "hint": "Ensure the context ID is valid and the context file exists in .memory/"
82
- }
83
- ```
84
-
85
- ### Error - CSV File Not Found
86
-
87
- When the CSV file referenced by the context doesn't exist:
88
-
89
- ```json
90
- {
91
- "success": false,
92
- "error": "CSV file not found: import/done/ubs/chf/transactions.csv",
93
- "hint": "The file may have been moved or deleted. Context ID: abc123-..."
94
- }
95
- ```
96
-
97
- ### Error - No Closing Balance
98
-
99
- When closing balance cannot be determined from context, CSV metadata, or manual override:
100
-
101
- ```json
102
- {
103
- "success": false,
104
- "csvFile": "import/done/revolut/eur/account-statement_2026-02.csv",
105
- "error": "No closing balance found in CSV metadata or data",
106
- "hint": "Provide closingBalance parameter manually. Example retry: reconcile-statement --contextId abc123-... --closingBalance \"EUR 1234.56\"",
107
- "metadata": {
108
- "from-date": "2026-02-01",
109
- "until-date": "2026-02-28",
110
- "currency": "EUR"
111
- }
112
- }
113
- ```
114
-
115
- ### Error - Account Not Determined
116
-
117
- When hledger account cannot be determined from rules file or manual override:
118
-
119
- ```json
120
- {
121
- "success": false,
122
- "csvFile": "import/done/ubs/chf/transactions.csv",
123
- "error": "Could not determine account from rules file",
124
- "hint": "Add 'account1 assets:bank:...' to ledger/rules/ubs-....rules or retry with: reconcile-statement --contextId abc123-... --account \"assets:bank:...\""
125
- }
126
- ```
127
-
128
- ## How It Works
129
-
130
- The reconciliation process follows these steps:
131
-
132
- ### Step 1: Load Import Context
133
-
134
- ```
135
- Load .memory/{contextId}.json
136
-
137
- Extract:
138
- - accountNumber (e.g., "0235-90250546.1")
139
- - closingBalance (e.g., "9001.55")
140
- - filePath (e.g., "import/done/ubs/chf/...")
141
- ```
142
-
143
- ### Step 2: Find CSV File
144
-
145
- The tool uses the `filePath` from the import context to locate the CSV:
146
-
147
- ```typescript
148
- const csvFile = path.join(directory, importContext.filePath);
149
- // Verify file exists, return error if not found
150
- ```
151
-
152
- **Critical**: The file path comes from the context, which was updated when `import-statements` moved the file from `pending/` to `done/`.
153
-
154
- ### Step 3: Determine Closing Balance
155
-
156
- Priority order:
157
-
158
- 1. **Manual override** (`--closingBalance` parameter)
159
- 2. **Context value** (`context.closingBalance`)
160
- 3. **CSV metadata** (from CSV header, e.g., UBS files)
161
- 4. **CSV data analysis** (inspect last transaction balance)
162
-
163
- If none available → ERROR (requires manual input)
164
-
165
- ### Step 4: Determine Account
166
-
167
- Priority order:
168
-
169
- 1. **Manual override** (`--account` parameter)
170
- 2. **Rules file** (`account1` directive in matching `.rules` file)
171
-
172
- If none available → ERROR (requires manual input or rules file update)
173
-
174
- ### Step 5: Query hledger Balance
175
-
176
- ```
177
- Run: hledger balance --end {lastTransactionDate + 1 day} {account}
178
-
179
- Parse output to get actual balance
180
-
181
- Format as "CHF 5432.10"
182
- ```
183
-
184
- ### Step 6: Compare Balances
185
-
186
- ```
187
- expectedBalance (from CSV) vs actualBalance (from hledger)
188
-
189
- Match? → SUCCESS
190
-
191
- Mismatch? → FAILURE (with difference calculated)
192
- ```
193
-
194
- ### Step 7: Update Context
195
-
196
- ```
197
- Update .memory/{contextId}.json with:
198
- - reconciledAccount
199
- - actualBalance
200
- - lastTransactionDate
201
- - reconciled: true/false
202
- ```
203
-
204
- ## Balance Sources
205
-
206
- ### Automatic Balance Detection
207
-
208
- **UBS**: Closing balance extracted from CSV header metadata (row with "Closing balance:")
209
-
210
- ```csv
211
- ...metadata rows...
212
- Closing balance:;9001.55;CHF
213
- ...
214
- Trade date;Trade time;...
215
- 2026-02-22;09:30:00;...
216
- ```
217
-
218
- **Revolut**: No balance in CSV metadata → requires manual input
219
-
220
- ```bash
221
- reconcile-statement --contextId abc123-... --closingBalance "EUR 1234.56"
222
- ```
223
-
224
- ### Balance Normalization
225
-
226
- The tool normalizes balances to format: `{CURRENCY} {amount}`
227
-
228
- Examples:
229
-
230
- - Input: `"CHF 2324.79"` → Used as-is
231
- - Input: `"2324.79"` (from CSV) + currency from context → `"CHF 2324.79"`
232
- - Input: `"2,324.79 CHF"` → Parsed and normalized to `"CHF 2324.79"`
233
-
234
- ## Account Detection
235
-
236
- ### Automatic Account Detection
237
-
238
- Accounts are detected from the matching rules file's `account1` directive:
239
-
240
- **Example rules file** (`ledger/rules/ubs-0235-90250546.0.rules`):
241
-
242
- ```
243
- account1 assets:bank:ubs:checking
244
- account2 expenses:unknown
245
-
246
- fields date, time, description1, description2, ...
247
-
248
- if %description1 SALARY
249
- account2 income:salary
250
- ```
251
-
252
- The tool reads `account1 assets:bank:ubs:checking` to determine which account to reconcile.
253
-
254
- ### Manual Account Override
255
-
256
- If rules file doesn't have `account1` or you want to override:
257
-
258
- ```bash
259
- reconcile-statement --contextId abc123-... --account "assets:bank:ubs:checking"
260
- ```
261
-
262
- ## The Multi-Account Problem
263
-
264
- ### Why Context ID is Required
265
-
266
- **Scenario**: You have two UBS CHF accounts:
267
-
268
- - Checking: `0235-90250546.0`
269
- - Savings: `0235-90250546.1`
270
-
271
- Both CSVs end up in `import/done/ubs/chf/`. Without context IDs, the tool would:
272
-
273
- ❌ Search for: `provider=ubs`, `currency=chf`
274
- ❌ Find: BOTH CSV files
275
- ❌ Select: Most recent file (by modification time)
276
- ❌ Result: Wrong CSV reconciled against wrong account
277
-
278
- **With context IDs**:
279
-
280
- ✓ Receive: `contextId` for checking account
281
- ✓ Load: Context with `accountNumber="0235-90250546.0"`
282
- ✓ Filter: CSVs by account number in filename
283
- ✓ Find: Exact CSV for checking account
284
- ✓ Result: Correct CSV reconciled against correct account
285
-
286
- See [Import Context Architecture](../architecture/import-context.md) for technical details.
287
-
288
- ## Typical Workflow
289
-
290
- ### Scenario 1: Automatic Reconciliation (via Pipeline)
291
-
292
- ```bash
293
- # Pipeline handles everything automatically
294
- import-pipeline
295
- ```
296
-
297
- **What happens**:
298
-
299
- 1. `classify-statements` creates contexts for each CSV
300
- 2. `import-statements` imports transactions (one context at a time)
301
- 3. `reconcile-statement` receives contextId automatically
302
- 4. Tool loads context, finds CSV by accountNumber
303
- 5. Compares balances
304
- 6. Updates context with results
305
-
306
- ### Scenario 2: Manual Reconciliation
307
-
308
- ```bash
309
- # Step 1: Classify and import first
310
- classify-statements
311
- # Output: { "classified": [{ "contextId": "abc123-...", ... }] }
312
-
313
- import-statements --contextId "abc123-..." --checkOnly false
314
-
315
- # Step 2: Reconcile using contextId
316
- reconcile-statement --contextId "abc123-..."
317
- ```
318
-
319
- ### Scenario 3: Override Closing Balance
320
-
321
- When CSV doesn't have balance metadata (e.g., Revolut):
322
-
323
- ```bash
324
- # Get contextId from classify output
325
- classify-statements
326
- # Output: { "classified": [{ "contextId": "xyz789-...", "provider": "revolut", ... }] }
327
-
328
- # Import
329
- import-statements --contextId "xyz789-..." --checkOnly false
330
-
331
- # Reconcile with manual balance
332
- reconcile-statement --contextId "xyz789-..." --closingBalance "EUR 1234.56"
333
- ```
334
-
335
- ### Scenario 4: Override Account
336
-
337
- When rules file is missing `account1` directive:
338
-
339
- ```bash
340
- reconcile-statement --contextId "abc123-..." --account "assets:bank:ubs:checking"
341
- ```
342
-
343
- ## Troubleshooting
344
-
345
- ### Balance Mismatch
346
-
347
- **Symptom**: `Balance mismatch: expected CHF 12500.00, got CHF 12490.00 (difference: CHF -10.00)`
348
-
349
- **Common Causes**:
350
-
351
- 1. **Missing transaction**: Check if a transaction was skipped during import
352
-
353
- ```bash
354
- # Compare CSV transaction count vs imported count
355
- wc -l import/done/ubs/chf/transactions.csv
356
- hledger register assets:bank:ubs:checking -b 2026-02-01 -e 2026-03-01 | wc -l
357
- ```
358
-
359
- 2. **Skip rule too aggressive**: A rule in the `.rules` file may be skipping legitimate transactions
360
-
361
- ```bash
362
- # Review skip rules in rules file
363
- grep "^skip" ledger/rules/ubs-0235-90250546.0.rules
364
- ```
365
-
366
- 3. **Wrong date range**: Transactions outside the CSV's date range affecting balance
367
-
368
- ```bash
369
- # Check for transactions after the CSV's until-date
370
- hledger register assets:bank:ubs:checking -b {until-date}
371
- ```
372
-
373
- 4. **Manual entries**: Transactions manually added to journal that aren't in CSV
374
- ```bash
375
- # Search for manual entries
376
- grep assets:bank:ubs:checking ledger/2026.journal
377
- ```
378
-
379
- ### Wrong CSV Reconciled
380
-
381
- **Symptom**: Reconciling checking account but errors mention savings account transactions
382
-
383
- **Cause**: This shouldn't happen with context IDs. If it does:
384
-
385
- 1. **Verify context**:
386
-
387
- ```bash
388
- jq . .memory/{contextId}.json
389
- # Check that accountNumber matches the intended account
390
- ```
391
-
392
- 2. **Check CSV filename**:
393
-
394
- ```bash
395
- ls import/done/ubs/chf/
396
- # Verify filename contains correct account number
397
- ```
398
-
399
- 3. **Verify rules file**:
400
- ```bash
401
- cat ledger/rules/ubs-0235-90250546.0.rules
402
- # Check that account1 is correct
403
- ```
404
-
405
- ### Context Not Found
406
-
407
- **Error**: `Failed to load import context: abc123-...`
408
-
409
- **Solutions**:
410
-
411
- 1. **Verify context file exists**:
412
-
413
- ```bash
414
- ls .memory/abc123-*.json
415
- ```
416
-
417
- 2. **Get valid contextId from classify output**:
418
-
419
- ```bash
420
- # Re-run classify to get current contextIds
421
- classify-statements
422
- ```
423
-
424
- 3. **Check for typos**: Ensure contextId is copied correctly (UUIDs are long!)
425
-
426
- ### No Closing Balance
427
-
428
- **Error**: `No closing balance found in CSV metadata or data`
429
-
430
- **Solutions**:
431
-
432
- 1. **Provide manual balance**:
433
-
434
- ```bash
435
- reconcile-statement --contextId abc123-... --closingBalance "CHF 2324.79"
436
- ```
437
-
438
- 2. **Check CSV metadata**: Some providers include balance in header rows
439
-
440
- ```bash
441
- head -15 import/done/ubs/chf/transactions.csv
442
- # Look for "Closing balance" or similar
443
- ```
444
-
445
- 3. **Update provider config**: Add balance extraction rules to `config/import/providers.yaml`
446
-
447
- ### Account Not Determined
448
-
449
- **Error**: `Could not determine account from rules file`
450
-
451
- **Solutions**:
452
-
453
- 1. **Add account1 to rules file**:
454
-
455
- ```bash
456
- # Edit rules file
457
- echo "account1 assets:bank:ubs:checking" >> ledger/rules/ubs-0235-90250546.0.rules
458
- ```
459
-
460
- 2. **Provide manual account**:
461
- ```bash
462
- reconcile-statement --contextId abc123-... --account "assets:bank:ubs:checking"
463
- ```
464
-
465
- ## Context Updates
466
-
467
- After reconciliation, the context is updated with results:
468
-
469
- **Before reconciliation** (`.memory/abc123-....json`):
470
-
471
- ```json
472
- {
473
- "id": "abc123-...",
474
- "accountNumber": "0235-90250546.0",
475
- "closingBalance": "5432.10",
476
- "transactionCount": 42
477
- }
478
- ```
479
-
480
- **After successful reconciliation**:
481
-
482
- ```json
483
- {
484
- "id": "abc123-...",
485
- "accountNumber": "0235-90250546.0",
486
- "closingBalance": "5432.10",
487
- "transactionCount": 42,
488
- "reconciledAccount": "assets:bank:ubs:checking",
489
- "actualBalance": "CHF 5432.10",
490
- "lastTransactionDate": "2026-02-28",
491
- "reconciled": true
492
- }
493
- ```
494
-
495
- **After failed reconciliation**:
496
-
497
- ```json
498
- {
499
- "id": "abc123-...",
500
- "reconciledAccount": "assets:bank:ubs:checking",
501
- "actualBalance": "CHF 5422.10",
502
- "lastTransactionDate": "2026-02-28",
503
- "reconciled": false
504
- }
505
- ```
506
-
507
- This provides an audit trail of reconciliation attempts.
508
-
509
- ## Error Handling
510
-
511
- ### Common Errors
512
-
513
- | Error | Cause | Solution |
514
- | ------------------------ | ----------------------------------------- | -------------------------------------------------------------- |
515
- | Context not found | Invalid contextId or missing context file | Verify contextId from classify-statements output |
516
- | CSV file not found | File was moved/deleted after import | Re-run import-statements or check file location |
517
- | No closing balance | CSV metadata missing balance | Provide `--closingBalance` manually |
518
- | Account not determined | Rules file missing `account1` | Add `account1` directive to rules file or provide `--account` |
519
- | Balance mismatch | Missing transactions or incorrect rules | Check for skipped transactions, review skip rules |
520
- | Multiple CSVs match | Account number ambiguous | Shouldn't happen with contexts; check accountNumber in context |
521
- | hledger query failed | Journal file corrupted or invalid | Run `hledger check` to validate journal |
522
- | Last transaction missing | Import didn't include all transactions | Check import logs, verify CSV was fully processed |
523
-
524
- ## See Also
525
-
526
- - [Import Context Architecture](../architecture/import-context.md) - Technical details on context system
527
- - [classify-statements Tool](classify-statements.md) - Creates contexts
528
- - [import-statements Tool](import-statements.md) - Updates contexts with import results
529
- - [import-pipeline Tool](import-pipeline.md) - Orchestrates classify → import → reconcile flow