@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.
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +87 -8
- package/bin/ynab-mcp-server.cjs +2 -2
- package/bin/ynab-mcp-server.js +3 -3
- package/biome.json +39 -0
- package/dist/bundle/index.cjs +67 -67
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -27
- package/dist/server/YNABMCPServer.d.ts +3 -4
- package/dist/server/YNABMCPServer.js +111 -116
- package/dist/server/budgetResolver.d.ts +6 -5
- package/dist/server/budgetResolver.js +46 -36
- package/dist/server/cacheKeys.js +6 -6
- package/dist/server/cacheManager.js +14 -11
- package/dist/server/completions.d.ts +2 -2
- package/dist/server/completions.js +20 -15
- package/dist/server/config.d.ts +10 -5
- package/dist/server/config.js +24 -7
- package/dist/server/deltaCache.d.ts +2 -2
- package/dist/server/deltaCache.js +22 -16
- package/dist/server/deltaCache.merge.d.ts +2 -2
- package/dist/server/diagnostics.d.ts +4 -4
- package/dist/server/diagnostics.js +38 -32
- package/dist/server/errorHandler.d.ts +5 -12
- package/dist/server/errorHandler.js +219 -217
- package/dist/server/prompts.d.ts +2 -2
- package/dist/server/prompts.js +45 -45
- package/dist/server/rateLimiter.js +4 -4
- package/dist/server/requestLogger.d.ts +1 -1
- package/dist/server/requestLogger.js +40 -35
- package/dist/server/resources.d.ts +3 -3
- package/dist/server/resources.js +55 -52
- package/dist/server/responseFormatter.js +6 -6
- package/dist/server/securityMiddleware.d.ts +2 -2
- package/dist/server/securityMiddleware.js +22 -20
- package/dist/server/serverKnowledgeStore.js +1 -1
- package/dist/server/toolRegistry.d.ts +3 -3
- package/dist/server/toolRegistry.js +47 -40
- package/dist/tools/__tests__/deltaTestUtils.d.ts +3 -3
- package/dist/tools/__tests__/deltaTestUtils.js +2 -2
- package/dist/tools/accountTools.d.ts +9 -8
- package/dist/tools/accountTools.js +47 -47
- package/dist/tools/adapters.d.ts +13 -8
- package/dist/tools/adapters.js +21 -11
- package/dist/tools/budgetTools.d.ts +8 -7
- package/dist/tools/budgetTools.js +22 -22
- package/dist/tools/categoryTools.d.ts +9 -8
- package/dist/tools/categoryTools.js +68 -59
- package/dist/tools/compareTransactions/formatter.d.ts +3 -3
- package/dist/tools/compareTransactions/formatter.js +9 -9
- package/dist/tools/compareTransactions/index.d.ts +6 -6
- package/dist/tools/compareTransactions/index.js +58 -43
- package/dist/tools/compareTransactions/matcher.d.ts +1 -1
- package/dist/tools/compareTransactions/matcher.js +28 -15
- package/dist/tools/compareTransactions/parser.d.ts +2 -2
- package/dist/tools/compareTransactions/parser.js +144 -138
- package/dist/tools/compareTransactions/types.d.ts +4 -4
- package/dist/tools/compareTransactions.d.ts +1 -1
- package/dist/tools/compareTransactions.js +1 -1
- package/dist/tools/deltaFetcher.d.ts +2 -2
- package/dist/tools/deltaFetcher.js +16 -15
- package/dist/tools/deltaSupport.d.ts +4 -4
- package/dist/tools/deltaSupport.js +35 -41
- package/dist/tools/exportTransactions.d.ts +5 -4
- package/dist/tools/exportTransactions.js +61 -59
- package/dist/tools/monthTools.d.ts +7 -6
- package/dist/tools/monthTools.js +31 -29
- package/dist/tools/payeeTools.d.ts +7 -6
- package/dist/tools/payeeTools.js +28 -28
- package/dist/tools/reconcileAdapter.d.ts +2 -2
- package/dist/tools/reconcileAdapter.js +21 -11
- package/dist/tools/reconciliation/analyzer.d.ts +4 -4
- package/dist/tools/reconciliation/analyzer.js +136 -57
- package/dist/tools/reconciliation/csvParser.d.ts +3 -3
- package/dist/tools/reconciliation/csvParser.js +128 -104
- package/dist/tools/reconciliation/executor.d.ts +4 -4
- package/dist/tools/reconciliation/executor.js +148 -109
- package/dist/tools/reconciliation/index.d.ts +10 -10
- package/dist/tools/reconciliation/index.js +96 -83
- package/dist/tools/reconciliation/matcher.d.ts +3 -3
- package/dist/tools/reconciliation/matcher.js +17 -16
- package/dist/tools/reconciliation/payeeNormalizer.js +19 -8
- package/dist/tools/reconciliation/recommendationEngine.d.ts +1 -1
- package/dist/tools/reconciliation/recommendationEngine.js +40 -40
- package/dist/tools/reconciliation/reportFormatter.d.ts +2 -2
- package/dist/tools/reconciliation/reportFormatter.js +79 -54
- package/dist/tools/reconciliation/signDetector.d.ts +1 -1
- package/dist/tools/reconciliation/types.d.ts +19 -16
- package/dist/tools/reconciliation/ynabAdapter.d.ts +2 -2
- package/dist/tools/schemas/common.d.ts +1 -1
- package/dist/tools/schemas/common.js +1 -1
- package/dist/tools/schemas/outputs/accountOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/accountOutputs.js +24 -18
- package/dist/tools/schemas/outputs/budgetOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/budgetOutputs.js +14 -11
- package/dist/tools/schemas/outputs/categoryOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/categoryOutputs.js +49 -29
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/comparisonOutputs.js +12 -12
- package/dist/tools/schemas/outputs/index.d.ts +14 -14
- package/dist/tools/schemas/outputs/index.js +14 -14
- package/dist/tools/schemas/outputs/monthOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/monthOutputs.js +56 -41
- package/dist/tools/schemas/outputs/payeeOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/payeeOutputs.js +10 -10
- package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +2 -2
- package/dist/tools/schemas/outputs/reconciliationOutputs.js +45 -45
- package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/transactionMutationOutputs.js +28 -22
- package/dist/tools/schemas/outputs/transactionOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/transactionOutputs.js +43 -35
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/utilityOutputs.js +5 -3
- package/dist/tools/schemas/shared/commonOutputs.d.ts +1 -1
- package/dist/tools/schemas/shared/commonOutputs.js +15 -9
- package/dist/tools/transactionReadTools.d.ts +11 -0
- package/dist/tools/transactionReadTools.js +202 -0
- package/dist/tools/transactionSchemas.d.ts +309 -0
- package/dist/tools/transactionSchemas.js +235 -0
- package/dist/tools/transactionTools.d.ts +6 -302
- package/dist/tools/transactionTools.js +7 -2054
- package/dist/tools/transactionUtils.d.ts +31 -0
- package/dist/tools/transactionUtils.js +364 -0
- package/dist/tools/transactionWriteTools.d.ts +20 -0
- package/dist/tools/transactionWriteTools.js +1342 -0
- package/dist/tools/utilityTools.d.ts +5 -4
- package/dist/tools/utilityTools.js +11 -11
- package/dist/types/index.d.ts +7 -7
- package/dist/types/index.js +6 -6
- package/dist/types/reconciliation.d.ts +1 -1
- package/dist/types/toolRegistration.d.ts +14 -12
- package/dist/utils/amountUtils.js +1 -1
- package/dist/utils/dateUtils.js +4 -4
- package/dist/utils/errors.d.ts +3 -3
- package/dist/utils/errors.js +4 -4
- package/dist/utils/money.d.ts +2 -2
- package/dist/utils/money.js +8 -8
- package/dist/utils/validationError.d.ts +1 -1
- package/dist/utils/validationError.js +1 -1
- package/docs/assets/examples/reconciliation-with-recommendations.json +66 -66
- package/docs/assets/schemas/reconciliation-v2.json +360 -336
- package/docs/plans/2025-12-25-transaction-tools-refactor-design.md +211 -0
- package/docs/plans/2025-12-25-transaction-tools-refactor.md +905 -0
- package/esbuild.config.mjs +53 -50
- package/meta.json +12548 -12548
- package/package.json +98 -109
- package/scripts/analyze-bundle.mjs +33 -30
- package/scripts/create-pr-description.js +169 -120
- package/scripts/run-all-tests.js +205 -0
- package/scripts/run-domain-integration-tests.js +28 -18
- package/scripts/run-generate-mcpb.js +19 -17
- package/scripts/run-throttled-integration-tests.js +92 -83
- package/scripts/test-delta-params.mjs +149 -120
- package/scripts/test-recommendations.ts +36 -32
- package/scripts/tmpTransaction.ts +80 -43
- package/scripts/validate-env.js +98 -91
- package/scripts/verify-build.js +78 -76
- package/src/__tests__/comprehensive.integration.test.ts +1281 -1154
- package/src/__tests__/performance.test.ts +723 -671
- package/src/__tests__/setup.ts +442 -395
- package/src/__tests__/smoke.e2e.test.ts +41 -39
- package/src/__tests__/testRunner.ts +314 -295
- package/src/__tests__/testUtils.ts +456 -364
- package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +109 -107
- package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +41 -41
- package/src/index.ts +68 -59
- package/src/server/CLAUDE.md +480 -0
- package/src/server/YNABMCPServer.ts +821 -794
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +929 -893
- package/src/server/__tests__/YNABMCPServer.test.ts +903 -899
- package/src/server/__tests__/budgetResolver.test.ts +466 -423
- package/src/server/__tests__/cacheManager.test.ts +891 -874
- package/src/server/__tests__/completions.integration.test.ts +115 -106
- package/src/server/__tests__/completions.test.ts +334 -313
- package/src/server/__tests__/config.test.ts +98 -86
- package/src/server/__tests__/deltaCache.merge.test.ts +774 -703
- package/src/server/__tests__/deltaCache.swr.test.ts +198 -153
- package/src/server/__tests__/deltaCache.test.ts +946 -759
- package/src/server/__tests__/diagnostics.test.ts +825 -792
- package/src/server/__tests__/errorHandler.integration.test.ts +512 -462
- package/src/server/__tests__/errorHandler.test.ts +402 -397
- package/src/server/__tests__/prompts.test.ts +424 -347
- package/src/server/__tests__/rateLimiter.test.ts +313 -309
- package/src/server/__tests__/requestLogger.test.ts +443 -403
- package/src/server/__tests__/resources.template.test.ts +196 -185
- package/src/server/__tests__/resources.test.ts +294 -288
- package/src/server/__tests__/security.integration.test.ts +487 -421
- package/src/server/__tests__/securityMiddleware.test.ts +519 -444
- package/src/server/__tests__/server-startup.integration.test.ts +509 -490
- package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -173
- package/src/server/__tests__/toolRegistration.test.ts +239 -210
- package/src/server/__tests__/toolRegistry.test.ts +907 -845
- package/src/server/budgetResolver.ts +221 -181
- package/src/server/cacheKeys.ts +6 -6
- package/src/server/cacheManager.ts +498 -484
- package/src/server/completions.ts +267 -243
- package/src/server/config.ts +35 -14
- package/src/server/deltaCache.merge.ts +146 -128
- package/src/server/deltaCache.ts +352 -309
- package/src/server/diagnostics.ts +257 -242
- package/src/server/errorHandler.ts +747 -744
- package/src/server/prompts.ts +181 -176
- package/src/server/rateLimiter.ts +131 -129
- package/src/server/requestLogger.ts +350 -322
- package/src/server/resources.ts +442 -374
- package/src/server/responseFormatter.ts +41 -37
- package/src/server/securityMiddleware.ts +223 -205
- package/src/server/serverKnowledgeStore.ts +67 -67
- package/src/server/toolRegistry.ts +508 -474
- package/src/tools/CLAUDE.md +604 -0
- package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -111
- package/src/tools/__tests__/accountTools.integration.test.ts +129 -111
- package/src/tools/__tests__/accountTools.test.ts +685 -638
- package/src/tools/__tests__/adapters.test.ts +142 -108
- package/src/tools/__tests__/budgetTools.delta.integration.test.ts +73 -73
- package/src/tools/__tests__/budgetTools.integration.test.ts +132 -124
- package/src/tools/__tests__/budgetTools.test.ts +442 -413
- package/src/tools/__tests__/categoryTools.delta.integration.test.ts +76 -68
- package/src/tools/__tests__/categoryTools.integration.test.ts +314 -288
- package/src/tools/__tests__/categoryTools.test.ts +656 -625
- package/src/tools/__tests__/compareTransactions/formatter.test.ts +535 -462
- package/src/tools/__tests__/compareTransactions/index.test.ts +378 -358
- package/src/tools/__tests__/compareTransactions/matcher.test.ts +497 -398
- package/src/tools/__tests__/compareTransactions/parser.test.ts +765 -747
- package/src/tools/__tests__/compareTransactions.test.ts +352 -332
- package/src/tools/__tests__/compareTransactions.window.test.ts +150 -146
- package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +69 -65
- package/src/tools/__tests__/deltaFetcher.test.ts +325 -265
- package/src/tools/__tests__/deltaSupport.test.ts +211 -184
- package/src/tools/__tests__/deltaTestUtils.ts +37 -33
- package/src/tools/__tests__/exportTransactions.test.ts +205 -200
- package/src/tools/__tests__/monthTools.delta.integration.test.ts +68 -68
- package/src/tools/__tests__/monthTools.integration.test.ts +178 -166
- package/src/tools/__tests__/monthTools.test.ts +561 -512
- package/src/tools/__tests__/payeeTools.delta.integration.test.ts +68 -68
- package/src/tools/__tests__/payeeTools.integration.test.ts +158 -142
- package/src/tools/__tests__/payeeTools.test.ts +486 -434
- package/src/tools/__tests__/transactionSchemas.test.ts +1204 -0
- package/src/tools/__tests__/transactionTools.integration.test.ts +875 -825
- package/src/tools/__tests__/transactionTools.test.ts +4923 -4366
- package/src/tools/__tests__/transactionUtils.test.ts +1016 -0
- package/src/tools/__tests__/utilityTools.integration.test.ts +32 -32
- package/src/tools/__tests__/utilityTools.test.ts +68 -58
- package/src/tools/accountTools.ts +293 -271
- package/src/tools/adapters.ts +120 -63
- package/src/tools/budgetTools.ts +121 -116
- package/src/tools/categoryTools.ts +379 -339
- package/src/tools/compareTransactions/formatter.ts +131 -119
- package/src/tools/compareTransactions/index.ts +249 -214
- package/src/tools/compareTransactions/matcher.ts +259 -209
- package/src/tools/compareTransactions/parser.ts +517 -487
- package/src/tools/compareTransactions/types.ts +38 -38
- package/src/tools/compareTransactions.ts +1 -1
- package/src/tools/deltaFetcher.ts +281 -260
- package/src/tools/deltaSupport.ts +264 -259
- package/src/tools/exportTransactions.ts +230 -218
- package/src/tools/monthTools.ts +180 -165
- package/src/tools/payeeTools.ts +152 -140
- package/src/tools/reconcileAdapter.ts +297 -246
- package/src/tools/reconciliation/CLAUDE.md +506 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +135 -112
- package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -227
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -335
- package/src/tools/reconciliation/__tests__/csvParser.test.ts +71 -69
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +348 -323
- package/src/tools/reconciliation/__tests__/executor.progress.test.ts +503 -457
- package/src/tools/reconciliation/__tests__/executor.test.ts +898 -831
- package/src/tools/reconciliation/__tests__/matcher.test.ts +667 -663
- package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +296 -276
- package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +692 -624
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1008 -986
- package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -530
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -71
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -58
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +58 -43
- package/src/tools/reconciliation/__tests__/signDetector.test.ts +209 -206
- package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +66 -60
- package/src/tools/reconciliation/analyzer.ts +582 -406
- package/src/tools/reconciliation/csvParser.ts +656 -609
- package/src/tools/reconciliation/executor.ts +1290 -1128
- package/src/tools/reconciliation/index.ts +580 -528
- package/src/tools/reconciliation/matcher.ts +256 -240
- package/src/tools/reconciliation/payeeNormalizer.ts +92 -78
- package/src/tools/reconciliation/recommendationEngine.ts +357 -345
- package/src/tools/reconciliation/reportFormatter.ts +349 -276
- package/src/tools/reconciliation/signDetector.ts +89 -83
- package/src/tools/reconciliation/types.ts +164 -153
- package/src/tools/reconciliation/ynabAdapter.ts +17 -15
- package/src/tools/schemas/CLAUDE.md +546 -0
- package/src/tools/schemas/common.ts +1 -1
- package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +410 -409
- package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +305 -299
- package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +431 -430
- package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +510 -495
- package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +179 -153
- package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +293 -254
- package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +457 -457
- package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +362 -356
- package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +402 -399
- package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +225 -211
- package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +457 -454
- package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +316 -315
- package/src/tools/schemas/outputs/accountOutputs.ts +40 -34
- package/src/tools/schemas/outputs/budgetOutputs.ts +24 -19
- package/src/tools/schemas/outputs/categoryOutputs.ts +76 -56
- package/src/tools/schemas/outputs/comparisonOutputs.ts +192 -169
- package/src/tools/schemas/outputs/index.ts +163 -163
- package/src/tools/schemas/outputs/monthOutputs.ts +95 -80
- package/src/tools/schemas/outputs/payeeOutputs.ts +18 -18
- package/src/tools/schemas/outputs/reconciliationOutputs.ts +386 -373
- package/src/tools/schemas/outputs/transactionMutationOutputs.ts +259 -231
- package/src/tools/schemas/outputs/transactionOutputs.ts +81 -71
- package/src/tools/schemas/outputs/utilityOutputs.ts +90 -84
- package/src/tools/schemas/shared/commonOutputs.ts +27 -19
- package/src/tools/toolCategories.ts +114 -114
- package/src/tools/transactionReadTools.ts +327 -0
- package/src/tools/transactionSchemas.ts +484 -0
- package/src/tools/transactionTools.ts +107 -2990
- package/src/tools/transactionUtils.ts +621 -0
- package/src/tools/transactionWriteTools.ts +2110 -0
- package/src/tools/utilityTools.ts +46 -41
- package/src/types/CLAUDE.md +477 -0
- package/src/types/__tests__/index.test.ts +51 -51
- package/src/types/index.ts +43 -39
- package/src/types/integration-tests.d.ts +26 -26
- package/src/types/reconciliation.ts +29 -29
- package/src/types/toolAnnotations.ts +30 -30
- package/src/types/toolRegistration.ts +43 -32
- package/src/utils/CLAUDE.md +508 -0
- package/src/utils/__tests__/dateUtils.test.ts +174 -168
- package/src/utils/__tests__/money.test.ts +193 -187
- package/src/utils/amountUtils.ts +5 -5
- package/src/utils/baseError.ts +5 -5
- package/src/utils/dateUtils.ts +29 -26
- package/src/utils/errors.ts +14 -14
- package/src/utils/money.ts +66 -52
- package/src/utils/validationError.ts +1 -1
- package/tsconfig.json +29 -29
- package/tsconfig.prod.json +16 -16
- package/vitest-reporters/split-json-reporter.ts +247 -204
- package/vitest.config.ts +99 -95
- package/.prettierignore +0 -10
- package/.prettierrc.json +0 -10
- package/eslint.config.js +0 -49
package/dist/tools/payeeTools.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { ToolAnnotationPresets } from
|
|
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,
|
|
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,
|
|
18
|
-
payee_id: z.string().min(1,
|
|
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:
|
|
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 ?
|
|
47
|
-
:
|
|
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
|
-
},
|
|
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,
|
|
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:
|
|
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
|
-
?
|
|
79
|
-
:
|
|
78
|
+
? "Data retrieved from cache for improved performance"
|
|
79
|
+
: "Fresh data retrieved from YNAB API",
|
|
80
80
|
}),
|
|
81
81
|
},
|
|
82
82
|
],
|
|
83
83
|
};
|
|
84
|
-
},
|
|
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:
|
|
91
|
-
description:
|
|
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:
|
|
98
|
+
title: "YNAB: List Payees",
|
|
99
99
|
},
|
|
100
100
|
},
|
|
101
101
|
});
|
|
102
102
|
registry.register({
|
|
103
|
-
name:
|
|
104
|
-
description:
|
|
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:
|
|
111
|
+
title: "YNAB: Get Payee Details",
|
|
112
112
|
},
|
|
113
113
|
},
|
|
114
114
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
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
|
|
2
|
-
import { formatHumanReadableReport, } from
|
|
3
|
-
const OUTPUT_VERSION =
|
|
4
|
-
const SCHEMA_URL =
|
|
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
|
|
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 ??
|
|
135
|
-
const executionView = execution
|
|
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[
|
|
168
|
+
structured["recommendations"] = analysis.recommendations;
|
|
159
169
|
}
|
|
160
170
|
if (options.csvFormat) {
|
|
161
|
-
structured[
|
|
171
|
+
structured["csv_format"] = options.csvFormat;
|
|
162
172
|
}
|
|
163
173
|
if (executionView) {
|
|
164
|
-
structured[
|
|
174
|
+
structured["execution"] = executionView;
|
|
165
175
|
}
|
|
166
176
|
if (options.auditMetadata) {
|
|
167
|
-
structured[
|
|
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
|
|
2
|
-
import { type
|
|
3
|
-
import { type MatchingConfig } from
|
|
4
|
-
import type { ReconciliationAnalysis } from
|
|
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
|
|
2
|
-
import { findMatches, normalizeConfig,
|
|
3
|
-
import { normalizeYNABTransactions } from
|
|
4
|
-
import { toMoneyValue } from
|
|
5
|
-
import { generateRecommendations } from
|
|
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(
|
|
19
|
-
actionHint: result.confidence ===
|
|
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 ===
|
|
28
|
-
match.recommendation =
|
|
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 ===
|
|
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,
|
|
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]}` :
|
|
61
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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(
|
|
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 =
|
|
113
|
-
const formatter = new Intl.NumberFormat(
|
|
114
|
-
style:
|
|
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 =
|
|
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:
|
|
143
|
-
severity: top.txns.length >= 4 ?
|
|
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:
|
|
162
|
-
type:
|
|
163
|
-
severity: discrepancyAbs >= 100000 ?
|
|
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:
|
|
188
|
-
type:
|
|
189
|
-
severity: csvErrors.length >= 5 ?
|
|
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(
|
|
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:
|
|
204
|
-
type:
|
|
205
|
-
severity:
|
|
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(
|
|
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 =
|
|
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 ===
|
|
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
|
|
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,
|
|
317
|
+
const newMatches = findMatches(newBankTransactions, ynabInRange, normalizedConfig);
|
|
242
318
|
const matches = newMatches.map(mapToTransactionMatch);
|
|
243
|
-
const autoMatches = matches.filter((m) => m.confidence ===
|
|
319
|
+
const autoMatches = matches.filter((m) => m.confidence === "high");
|
|
244
320
|
const autoMatchedYnabIds = new Set();
|
|
245
|
-
autoMatches
|
|
246
|
-
if (
|
|
247
|
-
autoMatchedYnabIds.add(
|
|
248
|
-
|
|
249
|
-
|
|
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 ===
|
|
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
|
|
255
|
-
if (
|
|
256
|
-
matchedYnabIds.add(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
const
|
|
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:
|
|
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
|
|
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?:
|
|
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?:
|
|
49
|
+
dateFormat?: "YMD" | "MDY" | "DMY";
|
|
50
50
|
header?: boolean;
|
|
51
51
|
maxRows?: number;
|
|
52
52
|
maxBytes?: number;
|