@dizzlkheinz/ynab-mcpb 0.18.3 → 0.19.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.
Files changed (346) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/CLAUDE.md +87 -8
  3. package/bin/ynab-mcp-server.cjs +2 -2
  4. package/bin/ynab-mcp-server.js +3 -3
  5. package/biome.json +39 -0
  6. package/dist/bundle/index.cjs +67 -67
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +27 -27
  9. package/dist/server/YNABMCPServer.d.ts +3 -4
  10. package/dist/server/YNABMCPServer.js +111 -116
  11. package/dist/server/budgetResolver.d.ts +6 -5
  12. package/dist/server/budgetResolver.js +46 -36
  13. package/dist/server/cacheKeys.js +6 -6
  14. package/dist/server/cacheManager.js +14 -11
  15. package/dist/server/completions.d.ts +2 -2
  16. package/dist/server/completions.js +20 -15
  17. package/dist/server/config.d.ts +10 -5
  18. package/dist/server/config.js +24 -7
  19. package/dist/server/deltaCache.d.ts +2 -2
  20. package/dist/server/deltaCache.js +22 -16
  21. package/dist/server/deltaCache.merge.d.ts +2 -2
  22. package/dist/server/diagnostics.d.ts +4 -4
  23. package/dist/server/diagnostics.js +38 -32
  24. package/dist/server/errorHandler.d.ts +5 -12
  25. package/dist/server/errorHandler.js +219 -217
  26. package/dist/server/prompts.d.ts +2 -2
  27. package/dist/server/prompts.js +45 -45
  28. package/dist/server/rateLimiter.js +4 -4
  29. package/dist/server/requestLogger.d.ts +1 -1
  30. package/dist/server/requestLogger.js +40 -35
  31. package/dist/server/resources.d.ts +3 -3
  32. package/dist/server/resources.js +55 -52
  33. package/dist/server/responseFormatter.js +6 -6
  34. package/dist/server/securityMiddleware.d.ts +2 -2
  35. package/dist/server/securityMiddleware.js +22 -20
  36. package/dist/server/serverKnowledgeStore.js +1 -1
  37. package/dist/server/toolRegistry.d.ts +3 -3
  38. package/dist/server/toolRegistry.js +47 -40
  39. package/dist/tools/__tests__/deltaTestUtils.d.ts +3 -3
  40. package/dist/tools/__tests__/deltaTestUtils.js +2 -2
  41. package/dist/tools/accountTools.d.ts +9 -8
  42. package/dist/tools/accountTools.js +47 -47
  43. package/dist/tools/adapters.d.ts +13 -8
  44. package/dist/tools/adapters.js +21 -11
  45. package/dist/tools/budgetTools.d.ts +8 -7
  46. package/dist/tools/budgetTools.js +22 -22
  47. package/dist/tools/categoryTools.d.ts +9 -8
  48. package/dist/tools/categoryTools.js +68 -59
  49. package/dist/tools/compareTransactions/formatter.d.ts +3 -3
  50. package/dist/tools/compareTransactions/formatter.js +9 -9
  51. package/dist/tools/compareTransactions/index.d.ts +6 -6
  52. package/dist/tools/compareTransactions/index.js +58 -43
  53. package/dist/tools/compareTransactions/matcher.d.ts +1 -1
  54. package/dist/tools/compareTransactions/matcher.js +28 -15
  55. package/dist/tools/compareTransactions/parser.d.ts +2 -2
  56. package/dist/tools/compareTransactions/parser.js +144 -138
  57. package/dist/tools/compareTransactions/types.d.ts +4 -4
  58. package/dist/tools/compareTransactions.d.ts +1 -1
  59. package/dist/tools/compareTransactions.js +1 -1
  60. package/dist/tools/deltaFetcher.d.ts +2 -2
  61. package/dist/tools/deltaFetcher.js +16 -15
  62. package/dist/tools/deltaSupport.d.ts +4 -4
  63. package/dist/tools/deltaSupport.js +35 -41
  64. package/dist/tools/exportTransactions.d.ts +5 -4
  65. package/dist/tools/exportTransactions.js +61 -59
  66. package/dist/tools/monthTools.d.ts +7 -6
  67. package/dist/tools/monthTools.js +31 -29
  68. package/dist/tools/payeeTools.d.ts +7 -6
  69. package/dist/tools/payeeTools.js +28 -28
  70. package/dist/tools/reconcileAdapter.d.ts +2 -2
  71. package/dist/tools/reconcileAdapter.js +21 -11
  72. package/dist/tools/reconciliation/analyzer.d.ts +4 -4
  73. package/dist/tools/reconciliation/analyzer.js +136 -57
  74. package/dist/tools/reconciliation/csvParser.d.ts +3 -3
  75. package/dist/tools/reconciliation/csvParser.js +128 -104
  76. package/dist/tools/reconciliation/executor.d.ts +4 -4
  77. package/dist/tools/reconciliation/executor.js +148 -109
  78. package/dist/tools/reconciliation/index.d.ts +10 -10
  79. package/dist/tools/reconciliation/index.js +96 -83
  80. package/dist/tools/reconciliation/matcher.d.ts +3 -3
  81. package/dist/tools/reconciliation/matcher.js +17 -16
  82. package/dist/tools/reconciliation/payeeNormalizer.js +19 -8
  83. package/dist/tools/reconciliation/recommendationEngine.d.ts +1 -1
  84. package/dist/tools/reconciliation/recommendationEngine.js +40 -40
  85. package/dist/tools/reconciliation/reportFormatter.d.ts +2 -2
  86. package/dist/tools/reconciliation/reportFormatter.js +79 -54
  87. package/dist/tools/reconciliation/signDetector.d.ts +1 -1
  88. package/dist/tools/reconciliation/types.d.ts +19 -16
  89. package/dist/tools/reconciliation/ynabAdapter.d.ts +2 -2
  90. package/dist/tools/schemas/common.d.ts +1 -1
  91. package/dist/tools/schemas/common.js +1 -1
  92. package/dist/tools/schemas/outputs/accountOutputs.d.ts +1 -1
  93. package/dist/tools/schemas/outputs/accountOutputs.js +24 -18
  94. package/dist/tools/schemas/outputs/budgetOutputs.d.ts +1 -1
  95. package/dist/tools/schemas/outputs/budgetOutputs.js +14 -11
  96. package/dist/tools/schemas/outputs/categoryOutputs.d.ts +1 -1
  97. package/dist/tools/schemas/outputs/categoryOutputs.js +49 -29
  98. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
  99. package/dist/tools/schemas/outputs/comparisonOutputs.js +12 -12
  100. package/dist/tools/schemas/outputs/index.d.ts +14 -14
  101. package/dist/tools/schemas/outputs/index.js +14 -14
  102. package/dist/tools/schemas/outputs/monthOutputs.d.ts +1 -1
  103. package/dist/tools/schemas/outputs/monthOutputs.js +56 -41
  104. package/dist/tools/schemas/outputs/payeeOutputs.d.ts +1 -1
  105. package/dist/tools/schemas/outputs/payeeOutputs.js +10 -10
  106. package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +2 -2
  107. package/dist/tools/schemas/outputs/reconciliationOutputs.js +45 -45
  108. package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +1 -1
  109. package/dist/tools/schemas/outputs/transactionMutationOutputs.js +28 -22
  110. package/dist/tools/schemas/outputs/transactionOutputs.d.ts +1 -1
  111. package/dist/tools/schemas/outputs/transactionOutputs.js +43 -35
  112. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +1 -1
  113. package/dist/tools/schemas/outputs/utilityOutputs.js +5 -3
  114. package/dist/tools/schemas/shared/commonOutputs.d.ts +1 -1
  115. package/dist/tools/schemas/shared/commonOutputs.js +15 -9
  116. package/dist/tools/transactionReadTools.d.ts +11 -0
  117. package/dist/tools/transactionReadTools.js +202 -0
  118. package/dist/tools/transactionSchemas.d.ts +309 -0
  119. package/dist/tools/transactionSchemas.js +235 -0
  120. package/dist/tools/transactionTools.d.ts +6 -302
  121. package/dist/tools/transactionTools.js +7 -2054
  122. package/dist/tools/transactionUtils.d.ts +31 -0
  123. package/dist/tools/transactionUtils.js +364 -0
  124. package/dist/tools/transactionWriteTools.d.ts +20 -0
  125. package/dist/tools/transactionWriteTools.js +1342 -0
  126. package/dist/tools/utilityTools.d.ts +5 -4
  127. package/dist/tools/utilityTools.js +11 -11
  128. package/dist/types/index.d.ts +7 -7
  129. package/dist/types/index.js +6 -6
  130. package/dist/types/reconciliation.d.ts +1 -1
  131. package/dist/types/toolRegistration.d.ts +14 -12
  132. package/dist/utils/amountUtils.js +1 -1
  133. package/dist/utils/dateUtils.js +4 -4
  134. package/dist/utils/errors.d.ts +3 -3
  135. package/dist/utils/errors.js +4 -4
  136. package/dist/utils/money.d.ts +2 -2
  137. package/dist/utils/money.js +8 -8
  138. package/dist/utils/validationError.d.ts +1 -1
  139. package/dist/utils/validationError.js +1 -1
  140. package/docs/assets/examples/reconciliation-with-recommendations.json +66 -66
  141. package/docs/assets/schemas/reconciliation-v2.json +360 -336
  142. package/docs/plans/2025-12-25-transaction-tools-refactor-design.md +211 -0
  143. package/docs/plans/2025-12-25-transaction-tools-refactor.md +905 -0
  144. package/esbuild.config.mjs +53 -50
  145. package/meta.json +12548 -12548
  146. package/package.json +98 -109
  147. package/scripts/analyze-bundle.mjs +33 -30
  148. package/scripts/create-pr-description.js +169 -120
  149. package/scripts/run-all-tests.js +205 -0
  150. package/scripts/run-domain-integration-tests.js +28 -18
  151. package/scripts/run-generate-mcpb.js +19 -17
  152. package/scripts/run-throttled-integration-tests.js +92 -83
  153. package/scripts/test-delta-params.mjs +149 -120
  154. package/scripts/test-recommendations.ts +36 -32
  155. package/scripts/tmpTransaction.ts +80 -43
  156. package/scripts/validate-env.js +98 -91
  157. package/scripts/verify-build.js +78 -76
  158. package/src/__tests__/comprehensive.integration.test.ts +1281 -1154
  159. package/src/__tests__/performance.test.ts +723 -671
  160. package/src/__tests__/setup.ts +442 -395
  161. package/src/__tests__/smoke.e2e.test.ts +41 -39
  162. package/src/__tests__/testRunner.ts +314 -295
  163. package/src/__tests__/testUtils.ts +456 -364
  164. package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +109 -107
  165. package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +41 -41
  166. package/src/index.ts +68 -59
  167. package/src/server/CLAUDE.md +480 -0
  168. package/src/server/YNABMCPServer.ts +821 -794
  169. package/src/server/__tests__/YNABMCPServer.integration.test.ts +929 -893
  170. package/src/server/__tests__/YNABMCPServer.test.ts +903 -899
  171. package/src/server/__tests__/budgetResolver.test.ts +466 -423
  172. package/src/server/__tests__/cacheManager.test.ts +891 -874
  173. package/src/server/__tests__/completions.integration.test.ts +115 -106
  174. package/src/server/__tests__/completions.test.ts +334 -313
  175. package/src/server/__tests__/config.test.ts +98 -86
  176. package/src/server/__tests__/deltaCache.merge.test.ts +774 -703
  177. package/src/server/__tests__/deltaCache.swr.test.ts +198 -153
  178. package/src/server/__tests__/deltaCache.test.ts +946 -759
  179. package/src/server/__tests__/diagnostics.test.ts +825 -792
  180. package/src/server/__tests__/errorHandler.integration.test.ts +512 -462
  181. package/src/server/__tests__/errorHandler.test.ts +402 -397
  182. package/src/server/__tests__/prompts.test.ts +424 -347
  183. package/src/server/__tests__/rateLimiter.test.ts +313 -309
  184. package/src/server/__tests__/requestLogger.test.ts +443 -403
  185. package/src/server/__tests__/resources.template.test.ts +196 -185
  186. package/src/server/__tests__/resources.test.ts +294 -288
  187. package/src/server/__tests__/security.integration.test.ts +487 -421
  188. package/src/server/__tests__/securityMiddleware.test.ts +519 -444
  189. package/src/server/__tests__/server-startup.integration.test.ts +509 -490
  190. package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -173
  191. package/src/server/__tests__/toolRegistration.test.ts +239 -210
  192. package/src/server/__tests__/toolRegistry.test.ts +907 -845
  193. package/src/server/budgetResolver.ts +221 -181
  194. package/src/server/cacheKeys.ts +6 -6
  195. package/src/server/cacheManager.ts +498 -484
  196. package/src/server/completions.ts +267 -243
  197. package/src/server/config.ts +35 -14
  198. package/src/server/deltaCache.merge.ts +146 -128
  199. package/src/server/deltaCache.ts +352 -309
  200. package/src/server/diagnostics.ts +257 -242
  201. package/src/server/errorHandler.ts +747 -744
  202. package/src/server/prompts.ts +181 -176
  203. package/src/server/rateLimiter.ts +131 -129
  204. package/src/server/requestLogger.ts +350 -322
  205. package/src/server/resources.ts +442 -374
  206. package/src/server/responseFormatter.ts +41 -37
  207. package/src/server/securityMiddleware.ts +223 -205
  208. package/src/server/serverKnowledgeStore.ts +67 -67
  209. package/src/server/toolRegistry.ts +508 -474
  210. package/src/tools/CLAUDE.md +604 -0
  211. package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -111
  212. package/src/tools/__tests__/accountTools.integration.test.ts +129 -111
  213. package/src/tools/__tests__/accountTools.test.ts +685 -638
  214. package/src/tools/__tests__/adapters.test.ts +142 -108
  215. package/src/tools/__tests__/budgetTools.delta.integration.test.ts +73 -73
  216. package/src/tools/__tests__/budgetTools.integration.test.ts +132 -124
  217. package/src/tools/__tests__/budgetTools.test.ts +442 -413
  218. package/src/tools/__tests__/categoryTools.delta.integration.test.ts +76 -68
  219. package/src/tools/__tests__/categoryTools.integration.test.ts +314 -288
  220. package/src/tools/__tests__/categoryTools.test.ts +656 -625
  221. package/src/tools/__tests__/compareTransactions/formatter.test.ts +535 -462
  222. package/src/tools/__tests__/compareTransactions/index.test.ts +378 -358
  223. package/src/tools/__tests__/compareTransactions/matcher.test.ts +497 -398
  224. package/src/tools/__tests__/compareTransactions/parser.test.ts +765 -747
  225. package/src/tools/__tests__/compareTransactions.test.ts +352 -332
  226. package/src/tools/__tests__/compareTransactions.window.test.ts +150 -146
  227. package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +69 -65
  228. package/src/tools/__tests__/deltaFetcher.test.ts +325 -265
  229. package/src/tools/__tests__/deltaSupport.test.ts +211 -184
  230. package/src/tools/__tests__/deltaTestUtils.ts +37 -33
  231. package/src/tools/__tests__/exportTransactions.test.ts +205 -200
  232. package/src/tools/__tests__/monthTools.delta.integration.test.ts +68 -68
  233. package/src/tools/__tests__/monthTools.integration.test.ts +178 -166
  234. package/src/tools/__tests__/monthTools.test.ts +561 -512
  235. package/src/tools/__tests__/payeeTools.delta.integration.test.ts +68 -68
  236. package/src/tools/__tests__/payeeTools.integration.test.ts +158 -142
  237. package/src/tools/__tests__/payeeTools.test.ts +486 -434
  238. package/src/tools/__tests__/transactionSchemas.test.ts +1204 -0
  239. package/src/tools/__tests__/transactionTools.integration.test.ts +875 -825
  240. package/src/tools/__tests__/transactionTools.test.ts +4923 -4366
  241. package/src/tools/__tests__/transactionUtils.test.ts +1016 -0
  242. package/src/tools/__tests__/utilityTools.integration.test.ts +32 -32
  243. package/src/tools/__tests__/utilityTools.test.ts +68 -58
  244. package/src/tools/accountTools.ts +293 -271
  245. package/src/tools/adapters.ts +120 -63
  246. package/src/tools/budgetTools.ts +121 -116
  247. package/src/tools/categoryTools.ts +379 -339
  248. package/src/tools/compareTransactions/formatter.ts +131 -119
  249. package/src/tools/compareTransactions/index.ts +249 -214
  250. package/src/tools/compareTransactions/matcher.ts +259 -209
  251. package/src/tools/compareTransactions/parser.ts +517 -487
  252. package/src/tools/compareTransactions/types.ts +38 -38
  253. package/src/tools/compareTransactions.ts +1 -1
  254. package/src/tools/deltaFetcher.ts +281 -260
  255. package/src/tools/deltaSupport.ts +264 -259
  256. package/src/tools/exportTransactions.ts +230 -218
  257. package/src/tools/monthTools.ts +180 -165
  258. package/src/tools/payeeTools.ts +152 -140
  259. package/src/tools/reconcileAdapter.ts +297 -246
  260. package/src/tools/reconciliation/CLAUDE.md +506 -0
  261. package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +135 -112
  262. package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -227
  263. package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -335
  264. package/src/tools/reconciliation/__tests__/csvParser.test.ts +71 -69
  265. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +348 -323
  266. package/src/tools/reconciliation/__tests__/executor.progress.test.ts +503 -457
  267. package/src/tools/reconciliation/__tests__/executor.test.ts +898 -831
  268. package/src/tools/reconciliation/__tests__/matcher.test.ts +667 -663
  269. package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +296 -276
  270. package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +692 -624
  271. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1008 -986
  272. package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
  273. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -530
  274. package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -71
  275. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -58
  276. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
  277. package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +58 -43
  278. package/src/tools/reconciliation/__tests__/signDetector.test.ts +209 -206
  279. package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +66 -60
  280. package/src/tools/reconciliation/analyzer.ts +582 -406
  281. package/src/tools/reconciliation/csvParser.ts +656 -609
  282. package/src/tools/reconciliation/executor.ts +1290 -1128
  283. package/src/tools/reconciliation/index.ts +580 -528
  284. package/src/tools/reconciliation/matcher.ts +256 -240
  285. package/src/tools/reconciliation/payeeNormalizer.ts +92 -78
  286. package/src/tools/reconciliation/recommendationEngine.ts +357 -345
  287. package/src/tools/reconciliation/reportFormatter.ts +349 -276
  288. package/src/tools/reconciliation/signDetector.ts +89 -83
  289. package/src/tools/reconciliation/types.ts +164 -153
  290. package/src/tools/reconciliation/ynabAdapter.ts +17 -15
  291. package/src/tools/schemas/CLAUDE.md +546 -0
  292. package/src/tools/schemas/common.ts +1 -1
  293. package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +410 -409
  294. package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +305 -299
  295. package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +431 -430
  296. package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +510 -495
  297. package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +179 -153
  298. package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +293 -254
  299. package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +457 -457
  300. package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +362 -356
  301. package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +402 -399
  302. package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +225 -211
  303. package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +457 -454
  304. package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +316 -315
  305. package/src/tools/schemas/outputs/accountOutputs.ts +40 -34
  306. package/src/tools/schemas/outputs/budgetOutputs.ts +24 -19
  307. package/src/tools/schemas/outputs/categoryOutputs.ts +76 -56
  308. package/src/tools/schemas/outputs/comparisonOutputs.ts +192 -169
  309. package/src/tools/schemas/outputs/index.ts +163 -163
  310. package/src/tools/schemas/outputs/monthOutputs.ts +95 -80
  311. package/src/tools/schemas/outputs/payeeOutputs.ts +18 -18
  312. package/src/tools/schemas/outputs/reconciliationOutputs.ts +386 -373
  313. package/src/tools/schemas/outputs/transactionMutationOutputs.ts +259 -231
  314. package/src/tools/schemas/outputs/transactionOutputs.ts +81 -71
  315. package/src/tools/schemas/outputs/utilityOutputs.ts +90 -84
  316. package/src/tools/schemas/shared/commonOutputs.ts +27 -19
  317. package/src/tools/toolCategories.ts +114 -114
  318. package/src/tools/transactionReadTools.ts +327 -0
  319. package/src/tools/transactionSchemas.ts +484 -0
  320. package/src/tools/transactionTools.ts +107 -2990
  321. package/src/tools/transactionUtils.ts +621 -0
  322. package/src/tools/transactionWriteTools.ts +2110 -0
  323. package/src/tools/utilityTools.ts +46 -41
  324. package/src/types/CLAUDE.md +477 -0
  325. package/src/types/__tests__/index.test.ts +51 -51
  326. package/src/types/index.ts +43 -39
  327. package/src/types/integration-tests.d.ts +26 -26
  328. package/src/types/reconciliation.ts +29 -29
  329. package/src/types/toolAnnotations.ts +30 -30
  330. package/src/types/toolRegistration.ts +43 -32
  331. package/src/utils/CLAUDE.md +508 -0
  332. package/src/utils/__tests__/dateUtils.test.ts +174 -168
  333. package/src/utils/__tests__/money.test.ts +193 -187
  334. package/src/utils/amountUtils.ts +5 -5
  335. package/src/utils/baseError.ts +5 -5
  336. package/src/utils/dateUtils.ts +29 -26
  337. package/src/utils/errors.ts +14 -14
  338. package/src/utils/money.ts +66 -52
  339. package/src/utils/validationError.ts +1 -1
  340. package/tsconfig.json +29 -29
  341. package/tsconfig.prod.json +16 -16
  342. package/vitest-reporters/split-json-reporter.ts +247 -204
  343. package/vitest.config.ts +99 -95
  344. package/.prettierignore +0 -10
  345. package/.prettierrc.json +0 -10
  346. package/eslint.config.js +0 -49
@@ -1,24 +1,24 @@
1
- import { z } from 'zod/v4';
2
- import { withToolErrorHandling } from '../types/index.js';
3
- import { responseFormatter } from '../server/responseFormatter.js';
4
- import { cacheManager, CACHE_TTLS, CacheManager } from '../server/cacheManager.js';
5
- import { CacheKeys } from '../server/cacheKeys.js';
6
- import { resolveDeltaFetcherArgs } from './deltaSupport.js';
7
- import { createAdapters, createBudgetResolver } from './adapters.js';
8
- import { ToolAnnotationPresets } from './toolCategories.js';
1
+ import { z } from "zod/v4";
2
+ import { CacheKeys } from "../server/cacheKeys.js";
3
+ import { CACHE_TTLS, CacheManager, cacheManager, } from "../server/cacheManager.js";
4
+ import { responseFormatter } from "../server/responseFormatter.js";
5
+ import { withToolErrorHandling } from "../types/index.js";
6
+ import { createAdapters, createBudgetResolver } from "./adapters.js";
7
+ import { resolveDeltaFetcherArgs } from "./deltaSupport.js";
8
+ import { ToolAnnotationPresets } from "./toolCategories.js";
9
9
  export const ListPayeesSchema = z
10
10
  .object({
11
- budget_id: z.string().min(1, 'Budget ID is required'),
11
+ budget_id: z.string().min(1, "Budget ID is required"),
12
12
  limit: z.number().int().positive().optional(),
13
13
  })
14
14
  .strict();
15
15
  export const GetPayeeSchema = z
16
16
  .object({
17
- budget_id: z.string().min(1, 'Budget ID is required'),
18
- payee_id: z.string().min(1, 'Payee ID is required'),
17
+ budget_id: z.string().min(1, "Budget ID is required"),
18
+ payee_id: z.string().min(1, "Payee ID is required"),
19
19
  })
20
20
  .strict();
21
- export async function handleListPayees(ynabAPI, deltaFetcherOrParams, maybeParams) {
21
+ export async function handleListPayees(ynabAPI, deltaFetcherOrParams, maybeParams, errorHandler) {
22
22
  const { deltaFetcher, params } = resolveDeltaFetcherArgs(ynabAPI, deltaFetcherOrParams, maybeParams);
23
23
  return await withToolErrorHandling(async () => {
24
24
  const result = await deltaFetcher.fetchPayees(params.budget_id);
@@ -31,7 +31,7 @@ export async function handleListPayees(ynabAPI, deltaFetcherOrParams, maybeParam
31
31
  return {
32
32
  content: [
33
33
  {
34
- type: 'text',
34
+ type: "text",
35
35
  text: responseFormatter.format({
36
36
  payees: payees.map((payee) => ({
37
37
  id: payee.id,
@@ -43,17 +43,17 @@ export async function handleListPayees(ynabAPI, deltaFetcherOrParams, maybeParam
43
43
  returned_count: payees.length,
44
44
  cached: wasCached,
45
45
  cache_info: wasCached
46
- ? `Data retrieved from cache for improved performance${result.usedDelta ? ' (delta merge applied)' : ''}`
47
- : 'Fresh data retrieved from YNAB API',
46
+ ? `Data retrieved from cache for improved performance${result.usedDelta ? " (delta merge applied)" : ""}`
47
+ : "Fresh data retrieved from YNAB API",
48
48
  }),
49
49
  },
50
50
  ],
51
51
  };
52
- }, 'ynab:list_payees', 'listing payees');
52
+ }, "ynab:list_payees", "listing payees", errorHandler);
53
53
  }
54
- export async function handleGetPayee(ynabAPI, params) {
54
+ export async function handleGetPayee(ynabAPI, params, errorHandler) {
55
55
  return await withToolErrorHandling(async () => {
56
- const cacheKey = CacheManager.generateKey(CacheKeys.PAYEES, 'get', params.budget_id, params.payee_id);
56
+ const cacheKey = CacheManager.generateKey(CacheKeys.PAYEES, "get", params.budget_id, params.payee_id);
57
57
  const wasCached = cacheManager.has(cacheKey);
58
58
  const payee = await cacheManager.wrap(cacheKey, {
59
59
  ttl: CACHE_TTLS.PAYEES,
@@ -65,7 +65,7 @@ export async function handleGetPayee(ynabAPI, params) {
65
65
  return {
66
66
  content: [
67
67
  {
68
- type: 'text',
68
+ type: "text",
69
69
  text: responseFormatter.format({
70
70
  payee: {
71
71
  id: payee.id,
@@ -75,40 +75,40 @@ export async function handleGetPayee(ynabAPI, params) {
75
75
  },
76
76
  cached: wasCached,
77
77
  cache_info: wasCached
78
- ? 'Data retrieved from cache for improved performance'
79
- : 'Fresh data retrieved from YNAB API',
78
+ ? "Data retrieved from cache for improved performance"
79
+ : "Fresh data retrieved from YNAB API",
80
80
  }),
81
81
  },
82
82
  ],
83
83
  };
84
- }, 'ynab:get_payee', 'getting payee details');
84
+ }, "ynab:get_payee", "getting payee details", errorHandler);
85
85
  }
86
86
  export const registerPayeeTools = (registry, context) => {
87
87
  const { adapt, adaptWithDelta } = createAdapters(context);
88
88
  const budgetResolver = createBudgetResolver(context);
89
89
  registry.register({
90
- name: 'list_payees',
91
- description: 'List all payees for a specific budget',
90
+ name: "list_payees",
91
+ description: "List all payees for a specific budget",
92
92
  inputSchema: ListPayeesSchema,
93
93
  handler: adaptWithDelta(handleListPayees),
94
94
  defaultArgumentResolver: budgetResolver(),
95
95
  metadata: {
96
96
  annotations: {
97
97
  ...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
98
- title: 'YNAB: List Payees',
98
+ title: "YNAB: List Payees",
99
99
  },
100
100
  },
101
101
  });
102
102
  registry.register({
103
- name: 'get_payee',
104
- description: 'Get detailed information for a specific payee',
103
+ name: "get_payee",
104
+ description: "Get detailed information for a specific payee",
105
105
  inputSchema: GetPayeeSchema,
106
106
  handler: adapt(handleGetPayee),
107
107
  defaultArgumentResolver: budgetResolver(),
108
108
  metadata: {
109
109
  annotations: {
110
110
  ...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
111
- title: 'YNAB: Get Payee Details',
111
+ title: "YNAB: Get Payee Details",
112
112
  },
113
113
  },
114
114
  });
@@ -1,5 +1,5 @@
1
- import type { ReconciliationAnalysis } from './reconciliation/types.js';
2
- import type { LegacyReconciliationResult } from './reconciliation/executor.js';
1
+ import type { LegacyReconciliationResult } from "./reconciliation/executor.js";
2
+ import type { ReconciliationAnalysis } from "./reconciliation/types.js";
3
3
  interface AdapterOptions {
4
4
  accountName?: string;
5
5
  accountId?: string;
@@ -1,7 +1,7 @@
1
- import { toMoneyValue, toMoneyValueFromDecimal } from '../utils/money.js';
2
- import { formatHumanReadableReport, } from './reconciliation/reportFormatter.js';
3
- const OUTPUT_VERSION = '2.0';
4
- const SCHEMA_URL = 'https://raw.githubusercontent.com/dizzlkheinz/ynab-mcp-mcpb/master/docs/schemas/reconciliation-v2.json';
1
+ import { toMoneyValue, toMoneyValueFromDecimal } from "../utils/money.js";
2
+ import { formatHumanReadableReport, } from "./reconciliation/reportFormatter.js";
3
+ const OUTPUT_VERSION = "2.0";
4
+ const SCHEMA_URL = "https://raw.githubusercontent.com/dizzlkheinz/ynab-mcp-mcpb/master/docs/schemas/reconciliation-v2.json";
5
5
  const toBankTransactionView = (txn, currency) => ({
6
6
  ...txn,
7
7
  amount_money: toMoneyValue(txn.amount, currency),
@@ -38,6 +38,9 @@ const convertSummary = (analysis) => ({
38
38
  statement_date_range: analysis.summary.statement_date_range,
39
39
  bank_transactions_count: analysis.summary.bank_transactions_count,
40
40
  ynab_transactions_count: analysis.summary.ynab_transactions_count,
41
+ ynab_in_range_count: analysis.summary.ynab_in_range_count ??
42
+ analysis.summary.ynab_transactions_count,
43
+ ynab_outside_range_count: analysis.summary.ynab_outside_range_count ?? 0,
41
44
  auto_matched: analysis.summary.auto_matched,
42
45
  suggested_matches: analysis.summary.suggested_matches,
43
46
  unmatched_bank: analysis.summary.unmatched_bank,
@@ -49,7 +52,11 @@ const convertSummary = (analysis) => ({
49
52
  });
50
53
  const convertBalanceInfo = (analysis) => {
51
54
  const discrepancyMilli = analysis.balance_info.discrepancy.value_milliunits;
52
- const direction = discrepancyMilli === 0 ? 'balanced' : discrepancyMilli > 0 ? 'ynab_higher' : 'bank_higher';
55
+ const direction = discrepancyMilli === 0
56
+ ? "balanced"
57
+ : discrepancyMilli > 0
58
+ ? "ynab_higher"
59
+ : "bank_higher";
53
60
  return {
54
61
  current_cleared: analysis.balance_info.current_cleared,
55
62
  current_uncleared: analysis.balance_info.current_uncleared,
@@ -131,8 +138,10 @@ const buildHumanNarrative = (analysis, options, execution) => {
131
138
  return formatHumanReadableReport(analysis, formatterOptions, execution);
132
139
  };
133
140
  export const buildReconciliationPayload = (analysis, options = {}, execution) => {
134
- const currency = options.currencyCode ?? 'USD';
135
- const executionView = execution ? convertExecution(execution, currency) : undefined;
141
+ const currency = options.currencyCode ?? "USD";
142
+ const executionView = execution
143
+ ? convertExecution(execution, currency)
144
+ : undefined;
136
145
  const structured = {
137
146
  version: OUTPUT_VERSION,
138
147
  schema_url: SCHEMA_URL,
@@ -152,19 +161,20 @@ export const buildReconciliationPayload = (analysis, options = {}, execution) =>
152
161
  unmatched: {
153
162
  bank: analysis.unmatched_bank.map((txn) => toBankTransactionView(txn, currency)),
154
163
  ynab: analysis.unmatched_ynab.map((txn) => toYNABTransactionView(txn, currency)),
164
+ ynab_outside_date_range: (analysis.ynab_outside_date_range ?? []).map((txn) => toYNABTransactionView(txn, currency)),
155
165
  },
156
166
  };
157
167
  if (analysis.recommendations && analysis.recommendations.length > 0) {
158
- structured['recommendations'] = analysis.recommendations;
168
+ structured["recommendations"] = analysis.recommendations;
159
169
  }
160
170
  if (options.csvFormat) {
161
- structured['csv_format'] = options.csvFormat;
171
+ structured["csv_format"] = options.csvFormat;
162
172
  }
163
173
  if (executionView) {
164
- structured['execution'] = executionView;
174
+ structured["execution"] = executionView;
165
175
  }
166
176
  if (options.auditMetadata) {
167
- structured['audit'] = options.auditMetadata;
177
+ structured["audit"] = options.auditMetadata;
168
178
  }
169
179
  return {
170
180
  human: buildHumanNarrative(analysis, options, execution),
@@ -1,7 +1,7 @@
1
- import type * as ynab from 'ynab';
2
- import { type ParseCSVOptions, type CSVParseResult } from './csvParser.js';
3
- import { type MatchingConfig } from './matcher.js';
4
- import type { ReconciliationAnalysis } from './types.js';
1
+ import type * as ynab from "ynab";
2
+ import { type CSVParseResult, type ParseCSVOptions } from "./csvParser.js";
3
+ import { type MatchingConfig } from "./matcher.js";
4
+ import type { ReconciliationAnalysis } from "./types.js";
5
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
6
  balance?: number;
7
7
  cleared_balance?: number;
@@ -1,22 +1,75 @@
1
- import { parseCSV } from './csvParser.js';
2
- import { findMatches, normalizeConfig, DEFAULT_CONFIG } from './matcher.js';
3
- import { normalizeYNABTransactions } from './ynabAdapter.js';
4
- import { toMoneyValue } from '../../utils/money.js';
5
- import { generateRecommendations } from './recommendationEngine.js';
1
+ import { parseCSV, } from "./csvParser.js";
2
+ import { DEFAULT_CONFIG, findMatches, normalizeConfig, } from "./matcher.js";
3
+ import { normalizeYNABTransactions } from "./ynabAdapter.js";
4
+ import { toMoneyValue } from "../../utils/money.js";
5
+ import { generateRecommendations } from "./recommendationEngine.js";
6
+ function calculateDateRange(bankTransactions) {
7
+ if (bankTransactions.length === 0) {
8
+ return null;
9
+ }
10
+ const dates = bankTransactions
11
+ .map((t) => t.date)
12
+ .filter((d) => d && /^\d{4}-\d{2}-\d{2}$/.test(d))
13
+ .sort();
14
+ if (dates.length === 0) {
15
+ return null;
16
+ }
17
+ const minDate = dates[0];
18
+ const maxDate = dates[dates.length - 1];
19
+ if (!minDate || !maxDate) {
20
+ return null;
21
+ }
22
+ return {
23
+ minDate,
24
+ maxDate,
25
+ };
26
+ }
27
+ function filterByDateRange(ynabTransactions, dateRange, dateToleranceDays = 7) {
28
+ const safeToleranceDays = dateToleranceDays < 0 ? 0 : dateToleranceDays;
29
+ if (dateToleranceDays < 0) {
30
+ console.warn(`[filterByDateRange] dateToleranceDays must be non-negative, got ${dateToleranceDays}. Using 0.`);
31
+ }
32
+ const inRange = [];
33
+ const outsideRange = [];
34
+ const minParts = dateRange.minDate.split("-").map(Number);
35
+ const maxParts = dateRange.maxDate.split("-").map(Number);
36
+ if (minParts.length !== 3 ||
37
+ maxParts.length !== 3 ||
38
+ minParts.some((n) => !Number.isFinite(n)) ||
39
+ maxParts.some((n) => !Number.isFinite(n))) {
40
+ console.warn(`[filterByDateRange] Invalid date format in range: ${dateRange.minDate} to ${dateRange.maxDate} - returning all transactions`);
41
+ return { inRange: ynabTransactions, outsideRange: [] };
42
+ }
43
+ const [minYear, minMonth, minDay] = minParts;
44
+ const [maxYear, maxMonth, maxDay] = maxParts;
45
+ const minDateWithBuffer = new Date(Date.UTC(minYear, minMonth - 1, minDay - safeToleranceDays));
46
+ const minDateStr = minDateWithBuffer.toISOString().split("T")[0] ?? "";
47
+ const maxDateWithBuffer = new Date(Date.UTC(maxYear, maxMonth - 1, maxDay + safeToleranceDays));
48
+ const maxDateStr = maxDateWithBuffer.toISOString().split("T")[0] ?? "";
49
+ for (const txn of ynabTransactions) {
50
+ if (txn.date >= minDateStr && txn.date <= maxDateStr) {
51
+ inRange.push(txn);
52
+ }
53
+ else {
54
+ outsideRange.push(txn);
55
+ }
56
+ }
57
+ return { inRange, outsideRange };
58
+ }
6
59
  function mapToTransactionMatch(result) {
7
60
  const candidates = result.candidates.map((c) => ({
8
61
  ynab_transaction: c.ynabTransaction,
9
62
  confidence: c.scores.combined,
10
- match_reason: c.matchReasons.join(', '),
11
- explanation: c.matchReasons.join(', '),
63
+ match_reason: c.matchReasons.join(", "),
64
+ explanation: c.matchReasons.join(", "),
12
65
  }));
13
66
  const match = {
14
67
  bankTransaction: result.bankTransaction,
15
68
  candidates,
16
69
  confidence: result.confidence,
17
70
  confidenceScore: result.confidenceScore,
18
- matchReason: result.bestMatch?.matchReasons.join(', ') ?? 'No match found',
19
- actionHint: result.confidence === 'high' ? 'approve' : 'review',
71
+ matchReason: result.bestMatch?.matchReasons.join(", ") ?? "No match found",
72
+ actionHint: result.confidence === "high" ? "approve" : "review",
20
73
  };
21
74
  if (result.bestMatch) {
22
75
  match.ynabTransaction = result.bestMatch.ynabTransaction;
@@ -24,8 +77,9 @@ function mapToTransactionMatch(result) {
24
77
  if (result.candidates[0]) {
25
78
  match.topConfidence = result.candidates[0].scores.combined;
26
79
  }
27
- if (result.confidence === 'none') {
28
- match.recommendation = 'This bank transaction is not in YNAB. Consider adding it.';
80
+ if (result.confidence === "none") {
81
+ match.recommendation =
82
+ "This bank transaction is not in YNAB. Consider adding it.";
29
83
  }
30
84
  return match;
31
85
  }
@@ -34,7 +88,7 @@ function calculateBalances(ynabTransactions, statementBalanceDecimal, currency,
34
88
  let computedUncleared = 0;
35
89
  for (const txn of ynabTransactions) {
36
90
  const amount = txn.amount;
37
- if (txn.cleared === 'cleared' || txn.cleared === 'reconciled') {
91
+ if (txn.cleared === "cleared" || txn.cleared === "reconciled") {
38
92
  computedCleared += amount;
39
93
  }
40
94
  else {
@@ -55,12 +109,13 @@ function calculateBalances(ynabTransactions, statementBalanceDecimal, currency,
55
109
  on_track: Math.abs(discrepancy) < 10,
56
110
  };
57
111
  }
58
- function generateSummary(bankTransactions, ynabTransactions, autoMatches, suggestedMatches, unmatchedBank, unmatchedYNAB, balances) {
112
+ function generateSummary(bankTransactions, ynabTransactionsInRange, ynabTransactionsOutsideRange, autoMatches, suggestedMatches, unmatchedBank, unmatchedYNAB, balances) {
59
113
  const dates = bankTransactions.map((t) => t.date).sort();
60
- const dateRange = dates.length > 0 ? `${dates[0]} to ${dates[dates.length - 1]}` : 'Unknown';
61
- let discrepancyExplanation = '';
114
+ const dateRange = dates.length > 0 ? `${dates[0]} to ${dates[dates.length - 1]}` : "Unknown";
115
+ const totalYnabCount = ynabTransactionsInRange.length + ynabTransactionsOutsideRange.length;
116
+ let discrepancyExplanation = "";
62
117
  if (balances.on_track) {
63
- discrepancyExplanation = 'Cleared balance matches statement';
118
+ discrepancyExplanation = "Cleared balance matches statement";
64
119
  }
65
120
  else {
66
121
  const actionsNeeded = [];
@@ -74,12 +129,16 @@ function generateSummary(bankTransactions, ynabTransactions, autoMatches, sugges
74
129
  actionsNeeded.push(`review ${unmatchedYNAB.length} unmatched YNAB`);
75
130
  }
76
131
  discrepancyExplanation =
77
- actionsNeeded.length > 0 ? `Need to ${actionsNeeded.join(', ')}` : 'Manual review required';
132
+ actionsNeeded.length > 0
133
+ ? `Need to ${actionsNeeded.join(", ")}`
134
+ : "Manual review required";
78
135
  }
79
136
  return {
80
137
  statement_date_range: dateRange,
81
138
  bank_transactions_count: bankTransactions.length,
82
- ynab_transactions_count: ynabTransactions.length,
139
+ ynab_transactions_count: totalYnabCount,
140
+ ynab_in_range_count: ynabTransactionsInRange.length,
141
+ ynab_outside_range_count: ynabTransactionsOutsideRange.length,
83
142
  auto_matched: autoMatches.length,
84
143
  suggested_matches: suggestedMatches.length,
85
144
  unmatched_bank: unmatchedBank.length,
@@ -105,20 +164,20 @@ function generateNextSteps(summary) {
105
164
  steps.push(`Decide what to do with ${summary.unmatched_ynab} unmatched YNAB transactions (unclear/delete/ignore)`);
106
165
  }
107
166
  if (steps.length === 0) {
108
- steps.push('All transactions matched! Review and approve to complete reconciliation');
167
+ steps.push("All transactions matched! Review and approve to complete reconciliation");
109
168
  }
110
169
  return steps;
111
170
  }
112
- function formatCurrency(amountMilli, currency = 'USD') {
113
- const formatter = new Intl.NumberFormat('en-US', {
114
- style: 'currency',
171
+ function formatCurrency(amountMilli, currency = "USD") {
172
+ const formatter = new Intl.NumberFormat("en-US", {
173
+ style: "currency",
115
174
  currency: currency,
116
175
  minimumFractionDigits: 2,
117
176
  maximumFractionDigits: 2,
118
177
  });
119
178
  return formatter.format(amountMilli / 1000);
120
179
  }
121
- function repeatAmountInsights(unmatchedBank, currency = 'USD') {
180
+ function repeatAmountInsights(unmatchedBank, currency = "USD") {
122
181
  const insights = [];
123
182
  if (unmatchedBank.length === 0) {
124
183
  return insights;
@@ -137,13 +196,15 @@ function repeatAmountInsights(unmatchedBank, currency = 'USD') {
137
196
  return insights;
138
197
  }
139
198
  const top = repeated[0];
199
+ if (!top) {
200
+ return insights;
201
+ }
140
202
  insights.push({
141
203
  id: `repeat-${top.amount}`,
142
- type: 'repeat_amount',
143
- severity: top.txns.length >= 4 ? 'critical' : 'warning',
204
+ type: "repeat_amount",
205
+ severity: top.txns.length >= 4 ? "critical" : "warning",
144
206
  title: `${top.txns.length} unmatched transactions at ${formatCurrency(top.amount, currency)}`,
145
- description: `The bank statement shows ${top.txns.length} unmatched transaction(s) at ${formatCurrency(top.amount, currency)}. ` +
146
- 'Repeated amounts are usually the quickest wins — reconcile these first.',
207
+ description: `The bank statement shows ${top.txns.length} unmatched transaction(s) at ${formatCurrency(top.amount, currency)}. Repeated amounts are usually the quickest wins — reconcile these first.`,
147
208
  evidence: {
148
209
  amount: top.amount,
149
210
  occurrences: top.txns.length,
@@ -158,9 +219,9 @@ function anomalyInsights(balances) {
158
219
  const discrepancyAbs = Math.abs(balances.discrepancy.value_milliunits);
159
220
  if (discrepancyAbs >= 1000) {
160
221
  insights.push({
161
- id: 'balance-gap',
162
- type: 'anomaly',
163
- severity: discrepancyAbs >= 100000 ? 'critical' : 'warning',
222
+ id: "balance-gap",
223
+ type: "anomaly",
224
+ severity: discrepancyAbs >= 100000 ? "critical" : "warning",
164
225
  title: `Cleared balance off by ${balances.discrepancy.value_display}`,
165
226
  description: `YNAB cleared balance is ${balances.current_cleared.value_display} but the statement expects ` +
166
227
  `${balances.target_statement.value_display}. Focus on closing this gap.`,
@@ -184,14 +245,15 @@ function detectInsights(unmatchedBank, _summary, balances, currency, csvErrors =
184
245
  };
185
246
  if (csvErrors.length > 0) {
186
247
  addUnique({
187
- id: 'csv-parse-errors',
188
- type: 'anomaly',
189
- severity: csvErrors.length >= 5 ? 'critical' : 'warning',
248
+ id: "csv-parse-errors",
249
+ type: "anomaly",
250
+ severity: csvErrors.length >= 5 ? "critical" : "warning",
190
251
  title: `${csvErrors.length} CSV parsing error(s)`,
191
252
  description: csvErrors
192
253
  .slice(0, 3)
193
254
  .map((e) => `Row ${e.row}: ${e.message}`)
194
- .join('; ') + (csvErrors.length > 3 ? ` (+${csvErrors.length - 3} more)` : ''),
255
+ .join("; ") +
256
+ (csvErrors.length > 3 ? ` (+${csvErrors.length - 3} more)` : ""),
195
257
  evidence: {
196
258
  error_count: csvErrors.length,
197
259
  errors: csvErrors.slice(0, 5),
@@ -200,14 +262,15 @@ function detectInsights(unmatchedBank, _summary, balances, currency, csvErrors =
200
262
  }
201
263
  if (csvWarnings.length > 0) {
202
264
  addUnique({
203
- id: 'csv-parse-warnings',
204
- type: 'anomaly',
205
- severity: 'info',
265
+ id: "csv-parse-warnings",
266
+ type: "anomaly",
267
+ severity: "info",
206
268
  title: `${csvWarnings.length} CSV parsing warning(s)`,
207
269
  description: csvWarnings
208
270
  .slice(0, 3)
209
271
  .map((w) => `Row ${w.row}: ${w.message}`)
210
- .join('; ') + (csvWarnings.length > 3 ? ` (+${csvWarnings.length - 3} more)` : ''),
272
+ .join("; ") +
273
+ (csvWarnings.length > 3 ? ` (+${csvWarnings.length - 3} more)` : ""),
211
274
  evidence: {
212
275
  warning_count: csvWarnings.length,
213
276
  warnings: csvWarnings.slice(0, 5),
@@ -222,9 +285,9 @@ function detectInsights(unmatchedBank, _summary, balances, currency, csvErrors =
222
285
  }
223
286
  return insights.slice(0, 5);
224
287
  }
225
- export function analyzeReconciliation(csvContentOrParsed, _csvFilePath, ynabTransactions, statementBalance, config = DEFAULT_CONFIG, currency = 'USD', accountId, budgetId, invertBankAmounts = false, csvOptions, accountSnapshot) {
288
+ export function analyzeReconciliation(csvContentOrParsed, _csvFilePath, ynabTransactions, statementBalance, config = DEFAULT_CONFIG, currency = "USD", accountId, budgetId, invertBankAmounts = false, csvOptions, accountSnapshot) {
226
289
  let parseResult;
227
- if (typeof csvContentOrParsed === 'string') {
290
+ if (typeof csvContentOrParsed === "string") {
228
291
  parseResult = parseCSV(csvContentOrParsed, {
229
292
  ...csvOptions,
230
293
  invertAmounts: invertBankAmounts,
@@ -236,38 +299,54 @@ export function analyzeReconciliation(csvContentOrParsed, _csvFilePath, ynabTran
236
299
  const newBankTransactions = parseResult.transactions;
237
300
  const csvParseErrors = parseResult.errors;
238
301
  const csvParseWarnings = parseResult.warnings;
239
- const newYNABTransactions = normalizeYNABTransactions(ynabTransactions);
302
+ const allYNABTransactions = normalizeYNABTransactions(ynabTransactions);
303
+ const csvDateRange = calculateDateRange(newBankTransactions);
304
+ let ynabInRange;
305
+ let ynabOutsideRange;
306
+ if (csvDateRange) {
307
+ const dateToleranceDays = config.dateToleranceDays ?? 7;
308
+ const filtered = filterByDateRange(allYNABTransactions, csvDateRange, dateToleranceDays);
309
+ ynabInRange = filtered.inRange;
310
+ ynabOutsideRange = filtered.outsideRange;
311
+ }
312
+ else {
313
+ ynabInRange = allYNABTransactions;
314
+ ynabOutsideRange = [];
315
+ }
240
316
  const normalizedConfig = normalizeConfig(config);
241
- const newMatches = findMatches(newBankTransactions, newYNABTransactions, normalizedConfig);
317
+ const newMatches = findMatches(newBankTransactions, ynabInRange, normalizedConfig);
242
318
  const matches = newMatches.map(mapToTransactionMatch);
243
- const autoMatches = matches.filter((m) => m.confidence === 'high');
319
+ const autoMatches = matches.filter((m) => m.confidence === "high");
244
320
  const autoMatchedYnabIds = new Set();
245
- autoMatches.forEach((m) => {
246
- if (m.ynabTransaction)
247
- autoMatchedYnabIds.add(m.ynabTransaction.id);
248
- });
249
- const suggestedMatches = matches.filter((m) => m.confidence === 'medium' &&
321
+ for (const match of autoMatches) {
322
+ if (match.ynabTransaction) {
323
+ autoMatchedYnabIds.add(match.ynabTransaction.id);
324
+ }
325
+ }
326
+ const suggestedMatches = matches.filter((m) => m.confidence === "medium" &&
250
327
  (!m.ynabTransaction || !autoMatchedYnabIds.has(m.ynabTransaction.id)));
251
- const unmatchedBankMatches = matches.filter((m) => m.confidence === 'low' || m.confidence === 'none');
328
+ const unmatchedBankMatches = matches.filter((m) => m.confidence === "low" || m.confidence === "none");
252
329
  const unmatchedBank = unmatchedBankMatches.map((m) => m.bankTransaction);
253
330
  const matchedYnabIds = new Set();
254
- matches.forEach((m) => {
255
- if (m.ynabTransaction)
256
- matchedYnabIds.add(m.ynabTransaction.id);
257
- });
258
- const unmatchedYNAB = newYNABTransactions.filter((t) => !matchedYnabIds.has(t.id));
259
- const balances = calculateBalances(newYNABTransactions, statementBalance, currency, accountSnapshot);
260
- const summary = generateSummary(matches.map((m) => m.bankTransaction), newYNABTransactions, autoMatches, suggestedMatches, unmatchedBank, unmatchedYNAB, balances);
331
+ for (const match of matches) {
332
+ if (match.ynabTransaction) {
333
+ matchedYnabIds.add(match.ynabTransaction.id);
334
+ }
335
+ }
336
+ const unmatchedYNAB = ynabInRange.filter((t) => !matchedYnabIds.has(t.id));
337
+ const balances = calculateBalances(allYNABTransactions, statementBalance, currency, accountSnapshot);
338
+ const summary = generateSummary(matches.map((m) => m.bankTransaction), ynabInRange, ynabOutsideRange, autoMatches, suggestedMatches, unmatchedBank, unmatchedYNAB, balances);
261
339
  const nextSteps = generateNextSteps(summary);
262
340
  const insights = detectInsights(unmatchedBank, summary, balances, currency, csvParseErrors, csvParseWarnings);
263
341
  const analysis = {
264
342
  success: true,
265
- phase: 'analysis',
343
+ phase: "analysis",
266
344
  summary,
267
345
  auto_matches: autoMatches,
268
346
  suggested_matches: suggestedMatches,
269
347
  unmatched_bank: unmatchedBank,
270
348
  unmatched_ynab: unmatchedYNAB,
349
+ ynab_outside_date_range: ynabOutsideRange,
271
350
  balance_info: balances,
272
351
  next_steps: nextSteps,
273
352
  insights,
@@ -1,4 +1,4 @@
1
- import type { BankTransaction } from '../../types/reconciliation.js';
1
+ import type { BankTransaction } from "../../types/reconciliation.js";
2
2
  export interface CSVParseResult {
3
3
  transactions: BankTransaction[];
4
4
  errors: ParseError[];
@@ -29,7 +29,7 @@ export interface BankPreset {
29
29
  creditColumn?: string;
30
30
  descriptionColumn: string | string[];
31
31
  amountMultiplier?: number;
32
- dateFormat?: 'YMD' | 'MDY' | 'DMY';
32
+ dateFormat?: "YMD" | "MDY" | "DMY";
33
33
  header?: boolean;
34
34
  }
35
35
  export declare const BANK_PRESETS: Record<string, BankPreset>;
@@ -46,7 +46,7 @@ export interface ParseCSVOptions {
46
46
  credit?: string;
47
47
  description?: string;
48
48
  };
49
- dateFormat?: 'YMD' | 'MDY' | 'DMY';
49
+ dateFormat?: "YMD" | "MDY" | "DMY";
50
50
  header?: boolean;
51
51
  maxRows?: number;
52
52
  maxBytes?: number;