@dizzlkheinz/ynab-mcpb 0.15.0 → 0.15.1

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.
@@ -2,4 +2,8 @@ import type * as ynab from 'ynab';
2
2
  import { type ParseCSVOptions, type CSVParseResult } from './csvParser.js';
3
3
  import { type MatchingConfig } from './matcher.js';
4
4
  import type { ReconciliationAnalysis } from './types.js';
5
- export declare function analyzeReconciliation(csvContentOrParsed: string | CSVParseResult, _csvFilePath: string | undefined, ynabTransactions: ynab.TransactionDetail[], statementBalance: number, config?: MatchingConfig, currency?: string, accountId?: string, budgetId?: string, invertBankAmounts?: boolean, csvOptions?: ParseCSVOptions): ReconciliationAnalysis;
5
+ export declare function analyzeReconciliation(csvContentOrParsed: string | CSVParseResult, _csvFilePath: string | undefined, ynabTransactions: ynab.TransactionDetail[], statementBalance: number, config?: MatchingConfig, currency?: string, accountId?: string, budgetId?: string, invertBankAmounts?: boolean, csvOptions?: ParseCSVOptions, accountSnapshot?: {
6
+ balance?: number;
7
+ cleared_balance?: number;
8
+ uncleared_balance?: number;
9
+ }): ReconciliationAnalysis;
@@ -29,20 +29,22 @@ function mapToTransactionMatch(result) {
29
29
  }
30
30
  return match;
31
31
  }
32
- function calculateBalances(ynabTransactions, statementBalanceDecimal, currency) {
33
- let clearedBalance = 0;
34
- let unclearedBalance = 0;
32
+ function calculateBalances(ynabTransactions, statementBalanceDecimal, currency, accountSnapshot) {
33
+ let computedCleared = 0;
34
+ let computedUncleared = 0;
35
35
  for (const txn of ynabTransactions) {
36
36
  const amount = txn.amount;
37
37
  if (txn.cleared === 'cleared' || txn.cleared === 'reconciled') {
38
- clearedBalance += amount;
38
+ computedCleared += amount;
39
39
  }
40
40
  else {
41
- unclearedBalance += amount;
41
+ computedUncleared += amount;
42
42
  }
43
43
  }
44
+ const clearedBalance = accountSnapshot?.cleared_balance ?? computedCleared;
45
+ const unclearedBalance = accountSnapshot?.uncleared_balance ?? computedUncleared;
46
+ const totalBalance = accountSnapshot?.balance ?? clearedBalance + unclearedBalance;
44
47
  const statementBalanceMilli = Math.round(statementBalanceDecimal * 1000);
45
- const totalBalance = clearedBalance + unclearedBalance;
46
48
  const discrepancy = clearedBalance - statementBalanceMilli;
47
49
  return {
48
50
  current_cleared: toMoneyValue(clearedBalance, currency),
@@ -220,7 +222,7 @@ function detectInsights(unmatchedBank, _summary, balances, currency, csvErrors =
220
222
  }
221
223
  return insights.slice(0, 5);
222
224
  }
223
- export function analyzeReconciliation(csvContentOrParsed, _csvFilePath, ynabTransactions, statementBalance, config = DEFAULT_CONFIG, currency = 'USD', accountId, budgetId, invertBankAmounts = false, csvOptions) {
225
+ export function analyzeReconciliation(csvContentOrParsed, _csvFilePath, ynabTransactions, statementBalance, config = DEFAULT_CONFIG, currency = 'USD', accountId, budgetId, invertBankAmounts = false, csvOptions, accountSnapshot) {
224
226
  let parseResult;
225
227
  if (typeof csvContentOrParsed === 'string') {
226
228
  parseResult = parseCSV(csvContentOrParsed, {
@@ -254,7 +256,7 @@ export function analyzeReconciliation(csvContentOrParsed, _csvFilePath, ynabTran
254
256
  matchedYnabIds.add(m.ynabTransaction.id);
255
257
  });
256
258
  const unmatchedYNAB = newYNABTransactions.filter((t) => !matchedYnabIds.has(t.id));
257
- const balances = calculateBalances(newYNABTransactions, statementBalance, currency);
259
+ const balances = calculateBalances(newYNABTransactions, statementBalance, currency, accountSnapshot);
258
260
  const summary = generateSummary(matches.map((m) => m.bankTransaction), newYNABTransactions, autoMatches, suggestedMatches, unmatchedBank, unmatchedYNAB, balances);
259
261
  const nextSteps = generateNextSteps(summary);
260
262
  const insights = detectInsights(unmatchedBank, summary, balances, currency, csvParseErrors, csvParseWarnings);
@@ -230,12 +230,12 @@ export async function handleReconcileAccount(ynabAPI, deltaFetcherOrParams, mayb
230
230
  delta_merge_applied: transactionsResult.usedDelta,
231
231
  },
232
232
  };
233
- const analysis = analyzeReconciliation(parseResult ?? csvContent, params.csv_file_path, ynabTransactions, adjustedStatementBalance, config, currencyCode, params.account_id, params.budget_id, finalInvertAmounts, csvOptions);
234
233
  const initialAccount = {
235
234
  balance: accountData.balance,
236
235
  cleared_balance: accountData.cleared_balance,
237
236
  uncleared_balance: accountData.uncleared_balance,
238
237
  };
238
+ const analysis = analyzeReconciliation(parseResult ?? csvContent, params.csv_file_path, ynabTransactions, adjustedStatementBalance, config, currencyCode, params.account_id, params.budget_id, finalInvertAmounts, csvOptions, initialAccount);
239
239
  let executionData;
240
240
  const wantsBalanceVerification = Boolean(params.statement_date);
241
241
  const shouldExecute = params.auto_create_transactions ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dizzlkheinz/ynab-mcpb",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "Model Context Protocol server for YNAB (You Need A Budget) integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -61,22 +61,28 @@ function calculateBalances(
61
61
  ynabTransactions: YNABTransaction[],
62
62
  statementBalanceDecimal: number,
63
63
  currency: string,
64
+ accountSnapshot?: { balance?: number; cleared_balance?: number; uncleared_balance?: number },
64
65
  ): BalanceInfo {
65
- let clearedBalance = 0;
66
- let unclearedBalance = 0;
66
+ // Compute from the fetched transactions, but prefer the authoritative account snapshot
67
+ // because we usually fetch a limited date window.
68
+ let computedCleared = 0;
69
+ let computedUncleared = 0;
67
70
 
68
71
  for (const txn of ynabTransactions) {
69
72
  const amount = txn.amount; // Milliunits
70
73
 
71
74
  if (txn.cleared === 'cleared' || txn.cleared === 'reconciled') {
72
- clearedBalance += amount;
75
+ computedCleared += amount;
73
76
  } else {
74
- unclearedBalance += amount;
77
+ computedUncleared += amount;
75
78
  }
76
79
  }
77
80
 
81
+ const clearedBalance = accountSnapshot?.cleared_balance ?? computedCleared;
82
+ const unclearedBalance = accountSnapshot?.uncleared_balance ?? computedUncleared;
83
+ const totalBalance = accountSnapshot?.balance ?? clearedBalance + unclearedBalance;
84
+
78
85
  const statementBalanceMilli = Math.round(statementBalanceDecimal * 1000);
79
- const totalBalance = clearedBalance + unclearedBalance;
80
86
  const discrepancy = clearedBalance - statementBalanceMilli;
81
87
 
82
88
  return {
@@ -342,6 +348,7 @@ export function analyzeReconciliation(
342
348
  budgetId?: string,
343
349
  invertBankAmounts: boolean = false,
344
350
  csvOptions?: ParseCSVOptions,
351
+ accountSnapshot?: { balance?: number; cleared_balance?: number; uncleared_balance?: number },
345
352
  ): ReconciliationAnalysis {
346
353
  // Step 1: Parse bank CSV using new Parser (or use provided result)
347
354
  let parseResult: CSVParseResult;
@@ -398,7 +405,12 @@ export function analyzeReconciliation(
398
405
  const unmatchedYNAB = newYNABTransactions.filter((t) => !matchedYnabIds.has(t.id));
399
406
 
400
407
  // Step 6: Calculate balances
401
- const balances = calculateBalances(newYNABTransactions, statementBalance, currency);
408
+ const balances = calculateBalances(
409
+ newYNABTransactions,
410
+ statementBalance,
411
+ currency,
412
+ accountSnapshot,
413
+ );
402
414
 
403
415
  // Step 7: Generate summary
404
416
  const summary = generateSummary(
@@ -349,6 +349,12 @@ export async function handleReconcileAccount(
349
349
  },
350
350
  };
351
351
 
352
+ const initialAccount: AccountSnapshot = {
353
+ balance: accountData.balance,
354
+ cleared_balance: accountData.cleared_balance,
355
+ uncleared_balance: accountData.uncleared_balance,
356
+ };
357
+
352
358
  // Perform analysis
353
359
  const analysis = analyzeReconciliation(
354
360
  parseResult ?? csvContent,
@@ -361,14 +367,9 @@ export async function handleReconcileAccount(
361
367
  params.budget_id,
362
368
  finalInvertAmounts, // Use smart-detected value
363
369
  csvOptions,
370
+ initialAccount,
364
371
  );
365
372
 
366
- const initialAccount: AccountSnapshot = {
367
- balance: accountData.balance,
368
- cleared_balance: accountData.cleared_balance,
369
- uncleared_balance: accountData.uncleared_balance,
370
- };
371
-
372
373
  let executionData: LegacyReconciliationResult | undefined;
373
374
  const wantsBalanceVerification = Boolean(params.statement_date);
374
375
  const shouldExecute =