@dizzlkheinz/ynab-mcpb 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.chunkhound.json +11 -0
- package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +8 -0
- package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +3 -0
- package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +3 -0
- package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +1 -0
- package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +12 -0
- package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +1 -0
- package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +1 -0
- package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +5 -0
- package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +1453 -0
- package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +8 -0
- package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +9 -0
- package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +1 -0
- package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +5 -0
- package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +81 -0
- package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +610 -0
- package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +1 -0
- package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +1 -0
- package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +1 -0
- package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +3 -0
- package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +8 -0
- package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +5 -0
- package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +1 -0
- package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +9 -0
- package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +1 -0
- package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +13 -0
- package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +14 -0
- package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +5 -0
- package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +5 -0
- package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +5 -0
- package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +3 -0
- package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +1 -0
- package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +1 -0
- package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +5 -0
- package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +5 -0
- package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +8 -0
- package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +8 -0
- package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +5 -0
- package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +1 -0
- package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +1 -0
- package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +1 -0
- package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +1 -0
- package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +1 -0
- package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +5 -0
- package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +1 -0
- package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +1 -0
- package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +1 -0
- package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +3 -0
- package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +8 -0
- package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +1 -0
- package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +1 -0
- package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +8 -0
- package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +3 -0
- package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +5 -0
- package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +1 -0
- package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +1 -0
- package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +5 -0
- package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +1 -0
- package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +14 -0
- package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +1 -0
- package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +5 -0
- package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +5 -0
- package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +11 -0
- package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +1 -0
- package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +8 -0
- package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +1 -0
- package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +3 -0
- package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +3 -0
- package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +8 -0
- package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +1 -0
- package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +5 -0
- package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +1 -0
- package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +1 -0
- package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +1 -0
- package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +1 -0
- package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +3 -0
- package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +8 -0
- package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +1 -0
- package/.dxtignore +57 -0
- package/.env.example +44 -0
- package/.gemini/settings.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/ISSUE_TEMPLATE/release_checklist.md +31 -0
- package/.github/pull_request_template.md +41 -0
- package/.github/workflows/ci-tests.yml +41 -0
- package/.github/workflows/claude-code-review.yml +57 -0
- package/.github/workflows/claude.yml +50 -0
- package/.github/workflows/full-integration.yml +22 -0
- package/.github/workflows/pr-description-check.yml +88 -0
- package/.github/workflows/publish.yml +33 -0
- package/.github/workflows/release.yml +89 -0
- package/.mcpbignore +58 -0
- package/.prettierignore +10 -0
- package/.prettierrc.json +10 -0
- package/ADOS-2-Module-1-Complete-Manual.md +757 -0
- package/AGENTS.md +36 -0
- package/CHANGELOG.md +187 -0
- package/CLAUDE.md +414 -0
- package/CODEREVIEW_RESPONSE.md +128 -0
- package/LICENSE +17 -0
- package/NUL +1 -0
- package/README.md +222 -0
- package/SCHEMA_IMPROVEMENT_SUMMARY.md +120 -0
- package/TESTING_NOTES.md +217 -0
- package/WARP.md +245 -0
- package/accountactivity-merged.csv +149 -0
- package/bin/ynab-mcp-server.cjs +4 -0
- package/bin/ynab-mcp-server.js +8 -0
- package/bundle-analysis.html +13110 -0
- package/dist/bundle/index.cjs +124 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +85 -0
- package/dist/server/YNABMCPServer.d.ts +264 -0
- package/dist/server/YNABMCPServer.js +845 -0
- package/dist/server/budgetResolver.d.ts +15 -0
- package/dist/server/budgetResolver.js +99 -0
- package/dist/server/cacheManager.d.ts +74 -0
- package/dist/server/cacheManager.js +306 -0
- package/dist/server/config.d.ts +3 -0
- package/dist/server/config.js +19 -0
- package/dist/server/deltaCache.d.ts +61 -0
- package/dist/server/deltaCache.js +206 -0
- package/dist/server/deltaCache.merge.d.ts +9 -0
- package/dist/server/deltaCache.merge.js +111 -0
- package/dist/server/diagnostics.d.ts +90 -0
- package/dist/server/diagnostics.js +163 -0
- package/dist/server/errorHandler.d.ts +69 -0
- package/dist/server/errorHandler.js +524 -0
- package/dist/server/prompts.d.ts +31 -0
- package/dist/server/prompts.js +205 -0
- package/dist/server/rateLimiter.d.ts +27 -0
- package/dist/server/rateLimiter.js +82 -0
- package/dist/server/requestLogger.d.ts +62 -0
- package/dist/server/requestLogger.js +190 -0
- package/dist/server/resources.d.ts +39 -0
- package/dist/server/resources.js +85 -0
- package/dist/server/responseFormatter.d.ts +14 -0
- package/dist/server/responseFormatter.js +42 -0
- package/dist/server/securityMiddleware.d.ts +87 -0
- package/dist/server/securityMiddleware.js +117 -0
- package/dist/server/serverKnowledgeStore.d.ts +11 -0
- package/dist/server/serverKnowledgeStore.js +42 -0
- package/dist/server/toolRegistry.d.ts +85 -0
- package/dist/server/toolRegistry.js +272 -0
- package/dist/tools/__tests__/deltaTestUtils.d.ts +18 -0
- package/dist/tools/__tests__/deltaTestUtils.js +26 -0
- package/dist/tools/accountTools.d.ts +37 -0
- package/dist/tools/accountTools.js +175 -0
- package/dist/tools/budgetTools.d.ts +10 -0
- package/dist/tools/budgetTools.js +68 -0
- package/dist/tools/categoryTools.d.ts +27 -0
- package/dist/tools/categoryTools.js +232 -0
- package/dist/tools/compareTransactions/formatter.d.ts +71 -0
- package/dist/tools/compareTransactions/formatter.js +97 -0
- package/dist/tools/compareTransactions/index.d.ts +30 -0
- package/dist/tools/compareTransactions/index.js +160 -0
- package/dist/tools/compareTransactions/matcher.d.ts +12 -0
- package/dist/tools/compareTransactions/matcher.js +140 -0
- package/dist/tools/compareTransactions/parser.d.ts +14 -0
- package/dist/tools/compareTransactions/parser.js +430 -0
- package/dist/tools/compareTransactions/types.d.ts +27 -0
- package/dist/tools/compareTransactions/types.js +1 -0
- package/dist/tools/compareTransactions.d.ts +1 -0
- package/dist/tools/compareTransactions.js +1 -0
- package/dist/tools/deltaFetcher.d.ts +22 -0
- package/dist/tools/deltaFetcher.js +137 -0
- package/dist/tools/deltaSupport.d.ts +20 -0
- package/dist/tools/deltaSupport.js +176 -0
- package/dist/tools/exportTransactions.d.ts +17 -0
- package/dist/tools/exportTransactions.js +191 -0
- package/dist/tools/monthTools.d.ts +16 -0
- package/dist/tools/monthTools.js +107 -0
- package/dist/tools/payeeTools.d.ts +17 -0
- package/dist/tools/payeeTools.js +82 -0
- package/dist/tools/reconcileAdapter.d.ts +25 -0
- package/dist/tools/reconcileAdapter.js +167 -0
- package/dist/tools/reconciliation/analyzer.d.ts +3 -0
- package/dist/tools/reconciliation/analyzer.js +567 -0
- package/dist/tools/reconciliation/executor.d.ts +94 -0
- package/dist/tools/reconciliation/executor.js +611 -0
- package/dist/tools/reconciliation/index.d.ts +54 -0
- package/dist/tools/reconciliation/index.js +249 -0
- package/dist/tools/reconciliation/matcher.d.ts +3 -0
- package/dist/tools/reconciliation/matcher.js +160 -0
- package/dist/tools/reconciliation/payeeNormalizer.d.ts +6 -0
- package/dist/tools/reconciliation/payeeNormalizer.js +77 -0
- package/dist/tools/reconciliation/recommendationEngine.d.ts +2 -0
- package/dist/tools/reconciliation/recommendationEngine.js +273 -0
- package/dist/tools/reconciliation/reportFormatter.d.ts +13 -0
- package/dist/tools/reconciliation/reportFormatter.js +214 -0
- package/dist/tools/reconciliation/types.d.ts +172 -0
- package/dist/tools/reconciliation/types.js +7 -0
- package/dist/tools/schemas/outputs/accountOutputs.d.ts +58 -0
- package/dist/tools/schemas/outputs/accountOutputs.js +24 -0
- package/dist/tools/schemas/outputs/budgetOutputs.d.ts +48 -0
- package/dist/tools/schemas/outputs/budgetOutputs.js +15 -0
- package/dist/tools/schemas/outputs/categoryOutputs.d.ts +93 -0
- package/dist/tools/schemas/outputs/categoryOutputs.js +37 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +269 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.js +181 -0
- package/dist/tools/schemas/outputs/index.d.ts +14 -0
- package/dist/tools/schemas/outputs/index.js +14 -0
- package/dist/tools/schemas/outputs/monthOutputs.d.ts +122 -0
- package/dist/tools/schemas/outputs/monthOutputs.js +51 -0
- package/dist/tools/schemas/outputs/payeeOutputs.d.ts +34 -0
- package/dist/tools/schemas/outputs/payeeOutputs.js +16 -0
- package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +1275 -0
- package/dist/tools/schemas/outputs/reconciliationOutputs.js +377 -0
- package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +717 -0
- package/dist/tools/schemas/outputs/transactionMutationOutputs.js +260 -0
- package/dist/tools/schemas/outputs/transactionOutputs.d.ts +98 -0
- package/dist/tools/schemas/outputs/transactionOutputs.js +49 -0
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +219 -0
- package/dist/tools/schemas/outputs/utilityOutputs.js +120 -0
- package/dist/tools/schemas/shared/commonOutputs.d.ts +24 -0
- package/dist/tools/schemas/shared/commonOutputs.js +27 -0
- package/dist/tools/toolCategories.d.ts +32 -0
- package/dist/tools/toolCategories.js +32 -0
- package/dist/tools/transactionTools.d.ts +315 -0
- package/dist/tools/transactionTools.js +1722 -0
- package/dist/tools/utilityTools.d.ts +10 -0
- package/dist/tools/utilityTools.js +56 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.js +16 -0
- package/dist/types/toolAnnotations.d.ts +7 -0
- package/dist/types/toolAnnotations.js +1 -0
- package/dist/utils/amountUtils.d.ts +3 -0
- package/dist/utils/amountUtils.js +10 -0
- package/dist/utils/dateUtils.d.ts +9 -0
- package/dist/utils/dateUtils.js +43 -0
- package/dist/utils/money.d.ts +21 -0
- package/dist/utils/money.js +51 -0
- package/docs/README.md +72 -0
- package/docs/assets/examples/reconciliation-with-recommendations.json +68 -0
- package/docs/assets/schemas/reconciliation-v2.json +338 -0
- package/docs/getting-started/CONFIGURATION.md +175 -0
- package/docs/getting-started/INSTALLATION.md +333 -0
- package/docs/getting-started/QUICKSTART.md +282 -0
- package/docs/guides/ARCHITECTURE.md +650 -0
- package/docs/guides/DEPLOYMENT.md +189 -0
- package/docs/guides/INTEGRATION_TESTING.md +730 -0
- package/docs/guides/TESTING.md +591 -0
- package/docs/reconciliation-flow.md +83 -0
- package/docs/reference/API.md +1450 -0
- package/docs/reference/EXAMPLES.md +946 -0
- package/docs/reference/TOOLS.md +348 -0
- package/docs/reference/TROUBLESHOOTING.md +481 -0
- package/esbuild.config.mjs +68 -0
- package/eslint.config.js +49 -0
- package/fix-types.sh +17 -0
- package/meta.json +12550 -0
- package/package.json +105 -0
- package/package.json.tmp +105 -0
- package/scripts/analyze-bundle.mjs +41 -0
- package/scripts/create-pr-description.js +203 -0
- package/scripts/generate-mcpb.ps1 +96 -0
- package/scripts/run-domain-integration-tests.js +33 -0
- package/scripts/run-generate-mcpb.js +29 -0
- package/scripts/run-throttled-integration-tests.js +116 -0
- package/scripts/test-delta-params.mjs +140 -0
- package/scripts/test-recommendations.ts +53 -0
- package/scripts/tmpTransaction.ts +48 -0
- package/scripts/validate-env.js +122 -0
- package/scripts/verify-build.js +105 -0
- package/scripts/watch-and-restart.ps1 +50 -0
- package/src/__tests__/comprehensive.integration.test.ts +1196 -0
- package/src/__tests__/delta.performance.test.ts +80 -0
- package/src/__tests__/performance.test.ts +725 -0
- package/src/__tests__/setup.ts +449 -0
- package/src/__tests__/testRunner.ts +444 -0
- package/src/__tests__/testUtils.ts +563 -0
- package/src/__tests__/workflows.e2e.test.ts +1675 -0
- package/src/index.ts +124 -0
- package/src/server/.gitkeep +1 -0
- package/src/server/YNABMCPServer.ts +1188 -0
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +903 -0
- package/src/server/__tests__/YNABMCPServer.test.ts +894 -0
- package/src/server/__tests__/budgetResolver.test.ts +425 -0
- package/src/server/__tests__/cacheManager.test.ts +880 -0
- package/src/server/__tests__/config.test.ts +166 -0
- package/src/server/__tests__/deltaCache.merge.test.ts +724 -0
- package/src/server/__tests__/deltaCache.swr.test.ts +168 -0
- package/src/server/__tests__/deltaCache.test.ts +774 -0
- package/src/server/__tests__/diagnostics.test.ts +823 -0
- package/src/server/__tests__/errorHandler.integration.test.ts +466 -0
- package/src/server/__tests__/errorHandler.test.ts +416 -0
- package/src/server/__tests__/prompts.test.ts +354 -0
- package/src/server/__tests__/rateLimiter.test.ts +314 -0
- package/src/server/__tests__/requestLogger.test.ts +408 -0
- package/src/server/__tests__/resources.test.ts +299 -0
- package/src/server/__tests__/security.integration.test.ts +426 -0
- package/src/server/__tests__/securityMiddleware.test.ts +449 -0
- package/src/server/__tests__/server-startup.integration.test.ts +477 -0
- package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -0
- package/src/server/__tests__/toolRegistry.test.ts +855 -0
- package/src/server/budgetResolver.ts +235 -0
- package/src/server/cacheManager.ts +503 -0
- package/src/server/config.ts +41 -0
- package/src/server/deltaCache.merge.ts +149 -0
- package/src/server/deltaCache.ts +341 -0
- package/src/server/diagnostics.ts +338 -0
- package/src/server/errorHandler.ts +756 -0
- package/src/server/prompts.ts +291 -0
- package/src/server/rateLimiter.ts +156 -0
- package/src/server/requestLogger.ts +344 -0
- package/src/server/resources.ts +168 -0
- package/src/server/responseFormatter.ts +51 -0
- package/src/server/securityMiddleware.ts +236 -0
- package/src/server/serverKnowledgeStore.ts +91 -0
- package/src/server/toolRegistry.ts +489 -0
- package/src/tools/.gitkeep +1 -0
- package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -0
- package/src/tools/__tests__/accountTools.integration.test.ts +117 -0
- package/src/tools/__tests__/accountTools.test.ts +653 -0
- package/src/tools/__tests__/budgetTools.delta.integration.test.ts +90 -0
- package/src/tools/__tests__/budgetTools.integration.test.ts +134 -0
- package/src/tools/__tests__/budgetTools.test.ts +423 -0
- package/src/tools/__tests__/categoryTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/categoryTools.integration.test.ts +295 -0
- package/src/tools/__tests__/categoryTools.test.ts +622 -0
- package/src/tools/__tests__/compareTransactions/formatter.test.ts +486 -0
- package/src/tools/__tests__/compareTransactions/index.test.ts +383 -0
- package/src/tools/__tests__/compareTransactions/matcher.test.ts +410 -0
- package/src/tools/__tests__/compareTransactions/parser.test.ts +764 -0
- package/src/tools/__tests__/compareTransactions.test.ts +342 -0
- package/src/tools/__tests__/compareTransactions.window.test.ts +147 -0
- package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +76 -0
- package/src/tools/__tests__/deltaFetcher.test.ts +270 -0
- package/src/tools/__tests__/deltaSupport.test.ts +188 -0
- package/src/tools/__tests__/deltaTestUtils.ts +46 -0
- package/src/tools/__tests__/exportTransactions.test.ts +213 -0
- package/src/tools/__tests__/monthTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/monthTools.integration.test.ts +174 -0
- package/src/tools/__tests__/monthTools.test.ts +523 -0
- package/src/tools/__tests__/payeeTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/payeeTools.integration.test.ts +150 -0
- package/src/tools/__tests__/payeeTools.test.ts +445 -0
- package/src/tools/__tests__/transactionTools.integration.test.ts +762 -0
- package/src/tools/__tests__/transactionTools.test.ts +3521 -0
- package/src/tools/__tests__/utilityTools.integration.test.ts +128 -0
- package/src/tools/__tests__/utilityTools.test.ts +205 -0
- package/src/tools/accountTools.ts +283 -0
- package/src/tools/budgetTools.ts +112 -0
- package/src/tools/categoryTools.ts +366 -0
- package/src/tools/compareTransactions/formatter.ts +163 -0
- package/src/tools/compareTransactions/index.ts +228 -0
- package/src/tools/compareTransactions/matcher.ts +240 -0
- package/src/tools/compareTransactions/parser.ts +557 -0
- package/src/tools/compareTransactions/types.ts +60 -0
- package/src/tools/compareTransactions.ts +3 -0
- package/src/tools/deltaFetcher.ts +278 -0
- package/src/tools/deltaSupport.ts +293 -0
- package/src/tools/exportTransactions.ts +273 -0
- package/src/tools/monthTools.ts +164 -0
- package/src/tools/payeeTools.ts +140 -0
- package/src/tools/reconcileAdapter.ts +312 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +122 -0
- package/src/tools/reconciliation/__tests__/adapter.test.ts +234 -0
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +406 -0
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +366 -0
- package/src/tools/reconciliation/__tests__/executor.test.ts +779 -0
- package/src/tools/reconciliation/__tests__/matcher.test.ts +650 -0
- package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +278 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +658 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1000 -0
- package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +151 -0
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +573 -0
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +78 -0
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +47 -0
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +61 -0
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +49 -0
- package/src/tools/reconciliation/analyzer.ts +824 -0
- package/src/tools/reconciliation/executor.ts +880 -0
- package/src/tools/reconciliation/index.ts +400 -0
- package/src/tools/reconciliation/matcher.ts +269 -0
- package/src/tools/reconciliation/payeeNormalizer.ts +167 -0
- package/src/tools/reconciliation/recommendationEngine.ts +506 -0
- package/src/tools/reconciliation/reportFormatter.ts +363 -0
- package/src/tools/reconciliation/types.ts +314 -0
- package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +424 -0
- package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +310 -0
- package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +448 -0
- package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +519 -0
- package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +155 -0
- package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +288 -0
- package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +478 -0
- package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +370 -0
- package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +401 -0
- package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +213 -0
- package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +474 -0
- package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +333 -0
- package/src/tools/schemas/outputs/accountOutputs.ts +137 -0
- package/src/tools/schemas/outputs/budgetOutputs.ts +86 -0
- package/src/tools/schemas/outputs/categoryOutputs.ts +194 -0
- package/src/tools/schemas/outputs/comparisonOutputs.ts +600 -0
- package/src/tools/schemas/outputs/index.ts +270 -0
- package/src/tools/schemas/outputs/monthOutputs.ts +243 -0
- package/src/tools/schemas/outputs/payeeOutputs.ts +105 -0
- package/src/tools/schemas/outputs/reconciliationOutputs.ts +796 -0
- package/src/tools/schemas/outputs/transactionMutationOutputs.ts +758 -0
- package/src/tools/schemas/outputs/transactionOutputs.ts +243 -0
- package/src/tools/schemas/outputs/utilityOutputs.ts +411 -0
- package/src/tools/schemas/shared/commonOutputs.ts +140 -0
- package/src/tools/toolCategories.ts +140 -0
- package/src/tools/transactionTools.ts +2509 -0
- package/src/tools/utilityTools.ts +90 -0
- package/src/types/.gitkeep +1 -0
- package/src/types/__tests__/index.test.ts +52 -0
- package/src/types/index.ts +67 -0
- package/src/types/integration-tests.d.ts +35 -0
- package/src/types/toolAnnotations.ts +44 -0
- package/src/utils/__tests__/dateUtils.test.ts +170 -0
- package/src/utils/__tests__/money.test.ts +189 -0
- package/src/utils/amountUtils.ts +32 -0
- package/src/utils/dateUtils.ts +108 -0
- package/src/utils/money.ts +123 -0
- package/test-csv-sample.csv +28 -0
- package/test-exports/sample_bank_statement.csv +7 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +23 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +23 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +44 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +58 -0
- package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +115 -0
- package/test-reconcile-autodetect.js +40 -0
- package/test-reconcile-tool.js +152 -0
- package/test-reconcile-with-csv.cjs +89 -0
- package/test-statement.csv +8 -0
- package/test_debug.js +47 -0
- package/test_simple.mjs +16 -0
- package/tsconfig.json +31 -0
- package/tsconfig.prod.json +18 -0
- package/vitest-reporters/split-json-reporter.ts +211 -0
- package/vitest.config.ts +96 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payee normalization and similarity matching
|
|
3
|
+
* Implements two-tier matching: normalized string comparison + fuzzy matching
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Normalizes a payee string for comparison
|
|
8
|
+
* - Lowercase
|
|
9
|
+
* - Remove spaces, punctuation, special characters
|
|
10
|
+
* - Keep only alphanumeric characters
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* normalizePayee("SHELL #1234 OAKVILLE ON") => "shell1234oakvilleon"
|
|
14
|
+
* normalizePayee("AMZN MKTP CA*123456789") => "amznmktpca123456789"
|
|
15
|
+
*/
|
|
16
|
+
export function normalizePayee(payee: string | null | undefined): string {
|
|
17
|
+
if (!payee) return '';
|
|
18
|
+
|
|
19
|
+
return payee.toLowerCase().replace(/[^a-z0-9]/g, ''); // Remove all non-alphanumeric
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tier 1: Fast normalized string comparison
|
|
24
|
+
* Returns true if normalized strings are identical
|
|
25
|
+
*
|
|
26
|
+
* This catches 80%+ of matches quickly
|
|
27
|
+
*/
|
|
28
|
+
export function normalizedMatch(
|
|
29
|
+
payee1: string | null | undefined,
|
|
30
|
+
payee2: string | null | undefined,
|
|
31
|
+
): boolean {
|
|
32
|
+
const norm1 = normalizePayee(payee1);
|
|
33
|
+
const norm2 = normalizePayee(payee2);
|
|
34
|
+
|
|
35
|
+
if (!norm1 || !norm2) return false;
|
|
36
|
+
|
|
37
|
+
return norm1 === norm2;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Calculates Levenshtein distance between two strings
|
|
42
|
+
* Used for fuzzy matching when normalized comparison fails
|
|
43
|
+
*/
|
|
44
|
+
function levenshteinDistance(str1: string, str2: string): number {
|
|
45
|
+
const len1 = str1.length;
|
|
46
|
+
const len2 = str2.length;
|
|
47
|
+
|
|
48
|
+
// Create distance matrix
|
|
49
|
+
const matrix: number[][] = Array(len1 + 1)
|
|
50
|
+
.fill(null)
|
|
51
|
+
.map(() => Array(len2 + 1).fill(0));
|
|
52
|
+
|
|
53
|
+
// Initialize first row and column
|
|
54
|
+
for (let i = 0; i <= len1; i++) matrix[i]![0] = i;
|
|
55
|
+
for (let j = 0; j <= len2; j++) matrix[0]![j] = j;
|
|
56
|
+
|
|
57
|
+
// Fill the matrix
|
|
58
|
+
for (let i = 1; i <= len1; i++) {
|
|
59
|
+
for (let j = 1; j <= len2; j++) {
|
|
60
|
+
const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
|
61
|
+
matrix[i]![j] = Math.min(
|
|
62
|
+
matrix[i - 1]![j]! + 1, // deletion
|
|
63
|
+
matrix[i]![j - 1]! + 1, // insertion
|
|
64
|
+
matrix[i - 1]![j - 1]! + cost, // substitution
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return matrix[len1]![len2]!;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Tier 2: Fuzzy matching using Levenshtein distance
|
|
74
|
+
* Returns similarity score 0-100
|
|
75
|
+
*
|
|
76
|
+
* Only used when Tier 1 (normalized matching) fails
|
|
77
|
+
*/
|
|
78
|
+
export function fuzzyMatch(
|
|
79
|
+
payee1: string | null | undefined,
|
|
80
|
+
payee2: string | null | undefined,
|
|
81
|
+
): number {
|
|
82
|
+
const norm1 = normalizePayee(payee1);
|
|
83
|
+
const norm2 = normalizePayee(payee2);
|
|
84
|
+
|
|
85
|
+
if (!norm1 || !norm2) return 0;
|
|
86
|
+
if (norm1 === norm2) return 100; // Perfect match
|
|
87
|
+
|
|
88
|
+
const distance = levenshteinDistance(norm1, norm2);
|
|
89
|
+
const maxLen = Math.max(norm1.length, norm2.length);
|
|
90
|
+
|
|
91
|
+
if (maxLen === 0) return 0;
|
|
92
|
+
|
|
93
|
+
// Convert to similarity percentage
|
|
94
|
+
const similarity = (1 - distance / maxLen) * 100;
|
|
95
|
+
return Math.max(0, Math.min(100, similarity));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Token-based similarity for better handling of word order differences
|
|
100
|
+
* Splits normalized payees into tokens and compares overlap
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* "amazon prime video" vs "prime amazon" => higher similarity
|
|
104
|
+
*/
|
|
105
|
+
export function tokenBasedSimilarity(
|
|
106
|
+
payee1: string | null | undefined,
|
|
107
|
+
payee2: string | null | undefined,
|
|
108
|
+
): number {
|
|
109
|
+
const norm1 = normalizePayee(payee1);
|
|
110
|
+
const norm2 = normalizePayee(payee2);
|
|
111
|
+
|
|
112
|
+
if (!norm1 || !norm2) return 0;
|
|
113
|
+
|
|
114
|
+
// Split into tokens (any sequence of letters or digits)
|
|
115
|
+
const tokens1 = norm1.match(/[a-z]+|[0-9]+/g) || [];
|
|
116
|
+
const tokens2 = norm2.match(/[a-z]+|[0-9]+/g) || [];
|
|
117
|
+
|
|
118
|
+
if (tokens1.length === 0 || tokens2.length === 0) return 0;
|
|
119
|
+
|
|
120
|
+
// Count matching tokens
|
|
121
|
+
const set1 = new Set(tokens1);
|
|
122
|
+
const set2 = new Set(tokens2);
|
|
123
|
+
|
|
124
|
+
let matches = 0;
|
|
125
|
+
for (const token of set1) {
|
|
126
|
+
if (set2.has(token)) matches++;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Jaccard similarity
|
|
130
|
+
const union = new Set([...set1, ...set2]).size;
|
|
131
|
+
return (matches / union) * 100;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Combined payee similarity using multiple strategies
|
|
136
|
+
* Returns the best similarity score from:
|
|
137
|
+
* - Normalized exact match (100 if matches)
|
|
138
|
+
* - Fuzzy match (Levenshtein distance)
|
|
139
|
+
* - Token-based match
|
|
140
|
+
*/
|
|
141
|
+
export function payeeSimilarity(
|
|
142
|
+
payee1: string | null | undefined,
|
|
143
|
+
payee2: string | null | undefined,
|
|
144
|
+
): number {
|
|
145
|
+
// Tier 1: Normalized exact match
|
|
146
|
+
if (normalizedMatch(payee1, payee2)) return 100;
|
|
147
|
+
|
|
148
|
+
// Tier 2: Fuzzy and token-based matching
|
|
149
|
+
const fuzzyScore = fuzzyMatch(payee1, payee2);
|
|
150
|
+
const tokenScore = tokenBasedSimilarity(payee1, payee2);
|
|
151
|
+
|
|
152
|
+
// Return the best score
|
|
153
|
+
return Math.max(fuzzyScore, tokenScore);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Check if payee contains a common substring
|
|
158
|
+
* Useful for matching "AMAZON.COM" to "Amazon Prime"
|
|
159
|
+
*/
|
|
160
|
+
export function payeeContains(payee: string | null | undefined, substring: string): boolean {
|
|
161
|
+
const norm = normalizePayee(payee);
|
|
162
|
+
const normSub = normalizePayee(substring);
|
|
163
|
+
|
|
164
|
+
if (!norm || !normSub) return false;
|
|
165
|
+
|
|
166
|
+
return norm.includes(normSub);
|
|
167
|
+
}
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import type {
|
|
3
|
+
ActionableRecommendation,
|
|
4
|
+
CreateTransactionRecommendation,
|
|
5
|
+
UpdateClearedRecommendation,
|
|
6
|
+
ReviewDuplicateRecommendation,
|
|
7
|
+
ManualReviewRecommendation,
|
|
8
|
+
RecommendationContext,
|
|
9
|
+
ReconciliationInsight,
|
|
10
|
+
TransactionMatch,
|
|
11
|
+
BankTransaction,
|
|
12
|
+
YNABTransaction,
|
|
13
|
+
} from './types.js';
|
|
14
|
+
import { toMoneyValueFromDecimal, fromMilli, toMilli } from '../../utils/money.js';
|
|
15
|
+
|
|
16
|
+
const RECOMMENDATION_VERSION = '1.0';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Confidence scores for different recommendation types
|
|
20
|
+
*/
|
|
21
|
+
const CONFIDENCE = {
|
|
22
|
+
CREATE_EXACT_MATCH: 0.95,
|
|
23
|
+
NEAR_MATCH_REVIEW: 0.7,
|
|
24
|
+
REPEAT_AMOUNT: 0.75,
|
|
25
|
+
ANOMALY_REVIEW: 0.5,
|
|
26
|
+
UNMATCHED_BANK: 0.8,
|
|
27
|
+
UPDATE_CLEARED: 0.6,
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Priority order for sorting recommendations
|
|
32
|
+
*/
|
|
33
|
+
const PRIORITY_ORDER = { high: 3, medium: 2, low: 1 } as const;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate actionable recommendations from reconciliation analysis.
|
|
37
|
+
*
|
|
38
|
+
* This function processes reconciliation analysis results and generates specific,
|
|
39
|
+
* executable recommendations for resolving discrepancies. It analyzes insights,
|
|
40
|
+
* unmatched transactions, and suggested matches to create prioritized actions.
|
|
41
|
+
*
|
|
42
|
+
* @param context - The recommendation context containing analysis results, IDs, and config
|
|
43
|
+
* @param context.account_id - The YNAB account ID for transaction operations
|
|
44
|
+
* @param context.budget_id - The YNAB budget ID (reserved for future category suggestions)
|
|
45
|
+
* @param context.analysis - The complete reconciliation analysis results
|
|
46
|
+
* @param context.matching_config - The matching configuration used during analysis
|
|
47
|
+
* @returns Array of actionable recommendations sorted by priority and confidence
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* const recommendations = generateRecommendations({
|
|
51
|
+
* account_id: 'abc123',
|
|
52
|
+
* budget_id: 'budget-456',
|
|
53
|
+
* analysis: reconciliationAnalysis,
|
|
54
|
+
* matching_config: defaultConfig
|
|
55
|
+
* });
|
|
56
|
+
* // Returns recommendations like create_transaction, update_cleared, etc.
|
|
57
|
+
*/
|
|
58
|
+
export function generateRecommendations(
|
|
59
|
+
context: RecommendationContext,
|
|
60
|
+
): ActionableRecommendation[] {
|
|
61
|
+
const recommendations: ActionableRecommendation[] = [];
|
|
62
|
+
|
|
63
|
+
// Process insights from analyzer
|
|
64
|
+
for (const insight of context.analysis.insights) {
|
|
65
|
+
const recs = processInsight(insight, context);
|
|
66
|
+
recommendations.push(...recs);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Process unmatched transactions
|
|
70
|
+
const unmatchedRecs = processUnmatchedTransactions(context);
|
|
71
|
+
recommendations.push(...unmatchedRecs);
|
|
72
|
+
|
|
73
|
+
// Sort by priority and confidence
|
|
74
|
+
return sortRecommendations(recommendations);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Process a single insight into recommendations
|
|
79
|
+
*/
|
|
80
|
+
function processInsight(
|
|
81
|
+
insight: ReconciliationInsight,
|
|
82
|
+
context: RecommendationContext,
|
|
83
|
+
): ActionableRecommendation[] {
|
|
84
|
+
switch (insight.type) {
|
|
85
|
+
case 'near_match':
|
|
86
|
+
return [createNearMatchRecommendation(insight, context)];
|
|
87
|
+
|
|
88
|
+
case 'repeat_amount':
|
|
89
|
+
return createRepeatAmountRecommendations(insight, context);
|
|
90
|
+
|
|
91
|
+
case 'anomaly':
|
|
92
|
+
return [createManualReviewRecommendation(insight, context)];
|
|
93
|
+
|
|
94
|
+
default:
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create recommendation for suggested match with intelligent routing.
|
|
101
|
+
*
|
|
102
|
+
* This function handles three distinct scenarios based on the match characteristics:
|
|
103
|
+
*
|
|
104
|
+
* 1. **Potential Duplicate** (has YNAB transaction + confidence score):
|
|
105
|
+
* Returns review_duplicate recommendation for manual verification
|
|
106
|
+
*
|
|
107
|
+
* 2. **Combination Match** (multiple YNAB transactions matching one bank transaction):
|
|
108
|
+
* Returns manual_review recommendation to investigate complex matching scenario
|
|
109
|
+
*
|
|
110
|
+
* 3. **Missing Transaction** (no matching YNAB transaction):
|
|
111
|
+
* Returns create_transaction recommendation with complete parameters
|
|
112
|
+
*
|
|
113
|
+
* @param match - The transaction match containing bank transaction and optional YNAB candidates
|
|
114
|
+
* @param context - The recommendation context for account/budget IDs and analysis data
|
|
115
|
+
* @returns Appropriate recommendation type based on match characteristics
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* // Scenario 1: Potential duplicate detected
|
|
119
|
+
* const dupRec = createSuggestedMatchRecommendation(
|
|
120
|
+
* { bank_transaction, ynab_transaction, confidence: 'high', confidence_score: 85 },
|
|
121
|
+
* context
|
|
122
|
+
* ); // Returns: review_duplicate
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* // Scenario 2: Combination match (2+ YNAB transactions)
|
|
126
|
+
* const combRec = createSuggestedMatchRecommendation(
|
|
127
|
+
* { bank_transaction, candidates: [txn1, txn2], match_reason: 'combination_match' },
|
|
128
|
+
* context
|
|
129
|
+
* ); // Returns: manual_review
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* // Scenario 3: Create missing transaction
|
|
133
|
+
* const createRec = createSuggestedMatchRecommendation(
|
|
134
|
+
* { bank_transaction, confidence: 'none' },
|
|
135
|
+
* context
|
|
136
|
+
* ); // Returns: create_transaction
|
|
137
|
+
*/
|
|
138
|
+
function createSuggestedMatchRecommendation(
|
|
139
|
+
match: TransactionMatch,
|
|
140
|
+
context: RecommendationContext,
|
|
141
|
+
): CreateTransactionRecommendation | ReviewDuplicateRecommendation | ManualReviewRecommendation {
|
|
142
|
+
const bankTxn = match.bank_transaction;
|
|
143
|
+
|
|
144
|
+
// If there's a suggested YNAB transaction, review as possible duplicate
|
|
145
|
+
if (match.ynab_transaction && match.confidence !== 'none') {
|
|
146
|
+
return {
|
|
147
|
+
id: randomUUID(),
|
|
148
|
+
action_type: 'review_duplicate',
|
|
149
|
+
priority: 'high',
|
|
150
|
+
confidence: Math.max(0, Math.min(1, match.confidence_score / 100)),
|
|
151
|
+
message: `Review possible match: ${bankTxn.payee}`,
|
|
152
|
+
reason: match.match_reason,
|
|
153
|
+
estimated_impact: toMoneyValueFromDecimal(
|
|
154
|
+
0,
|
|
155
|
+
context.analysis.balance_info.current_cleared.currency,
|
|
156
|
+
),
|
|
157
|
+
account_id: context.account_id,
|
|
158
|
+
metadata: {
|
|
159
|
+
version: RECOMMENDATION_VERSION,
|
|
160
|
+
created_at: new Date().toISOString(),
|
|
161
|
+
},
|
|
162
|
+
parameters: {
|
|
163
|
+
candidate_ids: [match.ynab_transaction.id],
|
|
164
|
+
bank_transaction: bankTxn,
|
|
165
|
+
suggested_match_id: match.ynab_transaction.id,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check for combination matches (multiple YNAB transactions that together match the bank transaction)
|
|
171
|
+
const isCombinationMatch =
|
|
172
|
+
match.match_reason === 'combination_match' || (match.candidates?.length ?? 0) > 1;
|
|
173
|
+
|
|
174
|
+
if (isCombinationMatch) {
|
|
175
|
+
return createCombinationReviewRecommendation(match, context);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Otherwise suggest creating new transaction
|
|
179
|
+
const parameters: CreateTransactionRecommendation['parameters'] = {
|
|
180
|
+
account_id: context.account_id,
|
|
181
|
+
date: bankTxn.date,
|
|
182
|
+
amount: toMilli(bankTxn.amount), // Convert dollars to milliunits for create_transaction
|
|
183
|
+
payee_name: bankTxn.payee,
|
|
184
|
+
cleared: 'cleared',
|
|
185
|
+
approved: true,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
if (bankTxn.memo) {
|
|
189
|
+
parameters.memo = bankTxn.memo;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
id: randomUUID(),
|
|
194
|
+
action_type: 'create_transaction',
|
|
195
|
+
priority: 'high',
|
|
196
|
+
confidence: CONFIDENCE.CREATE_EXACT_MATCH,
|
|
197
|
+
message: `Create transaction for ${bankTxn.payee}`,
|
|
198
|
+
reason: `This transaction exactly matches your discrepancy`,
|
|
199
|
+
estimated_impact: toMoneyValueFromDecimal(
|
|
200
|
+
bankTxn.amount,
|
|
201
|
+
context.analysis.balance_info.current_cleared.currency,
|
|
202
|
+
),
|
|
203
|
+
account_id: context.account_id,
|
|
204
|
+
metadata: {
|
|
205
|
+
version: RECOMMENDATION_VERSION,
|
|
206
|
+
created_at: new Date().toISOString(),
|
|
207
|
+
},
|
|
208
|
+
parameters,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Create recommendation for combination match (multiple YNAB transactions matching one bank transaction)
|
|
214
|
+
*/
|
|
215
|
+
function createCombinationReviewRecommendation(
|
|
216
|
+
match: TransactionMatch,
|
|
217
|
+
context: RecommendationContext,
|
|
218
|
+
): ManualReviewRecommendation {
|
|
219
|
+
const bankTxn = match.bank_transaction;
|
|
220
|
+
const candidateIds = match.candidates?.map((candidate) => candidate.ynab_transaction.id) ?? [];
|
|
221
|
+
|
|
222
|
+
// Calculate total amount from candidates for context (convert from milliunits to decimal)
|
|
223
|
+
const candidateTotalAmount =
|
|
224
|
+
match.candidates?.reduce((sum, candidate) => {
|
|
225
|
+
const amount = candidate.ynab_transaction.amount;
|
|
226
|
+
if (!Number.isFinite(amount)) {
|
|
227
|
+
console.warn(`Invalid candidate amount: ${amount}`);
|
|
228
|
+
return sum;
|
|
229
|
+
}
|
|
230
|
+
return sum + fromMilli(amount);
|
|
231
|
+
}, 0) ?? 0;
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
id: randomUUID(),
|
|
235
|
+
action_type: 'manual_review',
|
|
236
|
+
priority: 'medium',
|
|
237
|
+
confidence: CONFIDENCE.NEAR_MATCH_REVIEW,
|
|
238
|
+
message: `Review combination match: ${bankTxn.payee}`,
|
|
239
|
+
reason:
|
|
240
|
+
match.recommendation ??
|
|
241
|
+
'Multiple YNAB transactions appear to match this bank transaction. Review before creating anything new.',
|
|
242
|
+
estimated_impact: toMoneyValueFromDecimal(
|
|
243
|
+
0,
|
|
244
|
+
context.analysis.balance_info.current_cleared.currency,
|
|
245
|
+
),
|
|
246
|
+
account_id: context.account_id,
|
|
247
|
+
metadata: {
|
|
248
|
+
version: RECOMMENDATION_VERSION,
|
|
249
|
+
created_at: new Date().toISOString(),
|
|
250
|
+
bank_transaction_amount: toMoneyValueFromDecimal(
|
|
251
|
+
bankTxn.amount,
|
|
252
|
+
context.analysis.balance_info.current_cleared.currency,
|
|
253
|
+
),
|
|
254
|
+
candidate_total_amount: toMoneyValueFromDecimal(
|
|
255
|
+
candidateTotalAmount,
|
|
256
|
+
context.analysis.balance_info.current_cleared.currency,
|
|
257
|
+
),
|
|
258
|
+
candidate_count: match.candidates?.length ?? 0,
|
|
259
|
+
},
|
|
260
|
+
parameters: {
|
|
261
|
+
issue_type: 'complex_match',
|
|
262
|
+
related_transactions: [
|
|
263
|
+
{
|
|
264
|
+
source: 'bank',
|
|
265
|
+
id: bankTxn.id,
|
|
266
|
+
description: bankTxn.payee,
|
|
267
|
+
},
|
|
268
|
+
...candidateIds.map((id) => ({
|
|
269
|
+
source: 'ynab' as const,
|
|
270
|
+
id,
|
|
271
|
+
description:
|
|
272
|
+
match.candidates?.find((c) => c.ynab_transaction.id === id)?.ynab_transaction
|
|
273
|
+
.payee_name ?? 'Unknown',
|
|
274
|
+
})),
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Create recommendation for near match insight (possible duplicate)
|
|
282
|
+
*/
|
|
283
|
+
function createNearMatchRecommendation(
|
|
284
|
+
insight: ReconciliationInsight,
|
|
285
|
+
context: RecommendationContext,
|
|
286
|
+
): ManualReviewRecommendation {
|
|
287
|
+
return {
|
|
288
|
+
id: randomUUID(),
|
|
289
|
+
action_type: 'manual_review',
|
|
290
|
+
priority: 'medium',
|
|
291
|
+
confidence: CONFIDENCE.NEAR_MATCH_REVIEW,
|
|
292
|
+
message: `Review: ${insight.title}`,
|
|
293
|
+
reason: insight.description,
|
|
294
|
+
estimated_impact: toMoneyValueFromDecimal(
|
|
295
|
+
0,
|
|
296
|
+
context.analysis.balance_info.current_cleared.currency,
|
|
297
|
+
),
|
|
298
|
+
account_id: context.account_id,
|
|
299
|
+
source_insight_id: insight.id,
|
|
300
|
+
metadata: {
|
|
301
|
+
version: RECOMMENDATION_VERSION,
|
|
302
|
+
created_at: new Date().toISOString(),
|
|
303
|
+
current_discrepancy: context.analysis.balance_info.discrepancy,
|
|
304
|
+
insight_severity: insight.severity,
|
|
305
|
+
},
|
|
306
|
+
parameters: {
|
|
307
|
+
issue_type: 'complex_match',
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Create recommendations for repeat amount pattern
|
|
314
|
+
*/
|
|
315
|
+
function createRepeatAmountRecommendations(
|
|
316
|
+
insight: ReconciliationInsight,
|
|
317
|
+
context: RecommendationContext,
|
|
318
|
+
): ManualReviewRecommendation[] {
|
|
319
|
+
// For repeat amounts, suggest manual review since we need to identify the specific transactions
|
|
320
|
+
return [
|
|
321
|
+
{
|
|
322
|
+
id: randomUUID(),
|
|
323
|
+
action_type: 'manual_review',
|
|
324
|
+
priority: 'medium',
|
|
325
|
+
confidence: CONFIDENCE.REPEAT_AMOUNT,
|
|
326
|
+
message: `Review recurring pattern: ${insight.title}`,
|
|
327
|
+
reason: insight.description,
|
|
328
|
+
estimated_impact: toMoneyValueFromDecimal(
|
|
329
|
+
0,
|
|
330
|
+
context.analysis.balance_info.current_cleared.currency,
|
|
331
|
+
),
|
|
332
|
+
account_id: context.account_id,
|
|
333
|
+
source_insight_id: insight.id,
|
|
334
|
+
metadata: {
|
|
335
|
+
version: RECOMMENDATION_VERSION,
|
|
336
|
+
created_at: new Date().toISOString(),
|
|
337
|
+
current_discrepancy: context.analysis.balance_info.discrepancy,
|
|
338
|
+
insight_severity: insight.severity,
|
|
339
|
+
},
|
|
340
|
+
parameters: {
|
|
341
|
+
issue_type: 'complex_match',
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Create manual review recommendation (fallback)
|
|
349
|
+
*/
|
|
350
|
+
function createManualReviewRecommendation(
|
|
351
|
+
insight: ReconciliationInsight,
|
|
352
|
+
context: RecommendationContext,
|
|
353
|
+
): ManualReviewRecommendation {
|
|
354
|
+
return {
|
|
355
|
+
id: randomUUID(),
|
|
356
|
+
action_type: 'manual_review',
|
|
357
|
+
priority: 'low',
|
|
358
|
+
confidence: CONFIDENCE.ANOMALY_REVIEW,
|
|
359
|
+
message: `Review: ${insight.title}`,
|
|
360
|
+
reason: insight.description,
|
|
361
|
+
estimated_impact: toMoneyValueFromDecimal(
|
|
362
|
+
0,
|
|
363
|
+
context.analysis.balance_info.current_cleared.currency,
|
|
364
|
+
),
|
|
365
|
+
account_id: context.account_id,
|
|
366
|
+
source_insight_id: insight.id,
|
|
367
|
+
metadata: {
|
|
368
|
+
version: RECOMMENDATION_VERSION,
|
|
369
|
+
created_at: new Date().toISOString(),
|
|
370
|
+
current_discrepancy: context.analysis.balance_info.discrepancy,
|
|
371
|
+
insight_severity: insight.severity,
|
|
372
|
+
},
|
|
373
|
+
parameters: {
|
|
374
|
+
issue_type: insight.severity === 'critical' ? 'large_discrepancy' : 'unknown',
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Process unmatched transactions into recommendations
|
|
381
|
+
*/
|
|
382
|
+
function processUnmatchedTransactions(context: RecommendationContext): ActionableRecommendation[] {
|
|
383
|
+
const recommendations: ActionableRecommendation[] = [];
|
|
384
|
+
|
|
385
|
+
// Unmatched bank transactions → create_transaction
|
|
386
|
+
for (const bankTxn of context.analysis.unmatched_bank) {
|
|
387
|
+
recommendations.push(createUnmatchedBankRecommendation(bankTxn, context));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Suggested matches → review as potential duplicates or auto-match
|
|
391
|
+
for (const match of context.analysis.suggested_matches) {
|
|
392
|
+
recommendations.push(createSuggestedMatchRecommendation(match, context));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Unmatched YNAB uncleared → update_cleared (lower priority)
|
|
396
|
+
for (const ynabTxn of context.analysis.unmatched_ynab) {
|
|
397
|
+
if (ynabTxn.cleared === 'uncleared') {
|
|
398
|
+
recommendations.push(createUpdateClearedRecommendation(ynabTxn, context));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return recommendations;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Create a create_transaction recommendation for an unmatched bank transaction.
|
|
407
|
+
*
|
|
408
|
+
* Generates a recommendation to create a new YNAB transaction for a bank statement
|
|
409
|
+
* entry that has no corresponding transaction in YNAB. The recommendation includes
|
|
410
|
+
* complete parameters ready for execution via the create_transaction MCP tool.
|
|
411
|
+
*
|
|
412
|
+
* @param txn - The unmatched bank transaction
|
|
413
|
+
* @param context - The recommendation context for account ID and currency
|
|
414
|
+
* @returns create_transaction recommendation with medium priority and 0.8 confidence
|
|
415
|
+
*/
|
|
416
|
+
function createUnmatchedBankRecommendation(
|
|
417
|
+
txn: BankTransaction,
|
|
418
|
+
context: RecommendationContext,
|
|
419
|
+
): CreateTransactionRecommendation {
|
|
420
|
+
const parameters: CreateTransactionRecommendation['parameters'] = {
|
|
421
|
+
account_id: context.account_id,
|
|
422
|
+
date: txn.date,
|
|
423
|
+
amount: toMilli(txn.amount), // Convert dollars to milliunits for create_transaction
|
|
424
|
+
payee_name: txn.payee,
|
|
425
|
+
cleared: 'cleared',
|
|
426
|
+
approved: true,
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
if (txn.memo) {
|
|
430
|
+
parameters.memo = txn.memo;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
id: randomUUID(),
|
|
435
|
+
action_type: 'create_transaction',
|
|
436
|
+
priority: 'medium',
|
|
437
|
+
confidence: CONFIDENCE.UNMATCHED_BANK,
|
|
438
|
+
message: `Create missing transaction: ${txn.payee}`,
|
|
439
|
+
reason: 'Transaction appears on bank statement but not in YNAB',
|
|
440
|
+
estimated_impact: toMoneyValueFromDecimal(
|
|
441
|
+
txn.amount,
|
|
442
|
+
context.analysis.balance_info.current_cleared.currency,
|
|
443
|
+
),
|
|
444
|
+
account_id: context.account_id,
|
|
445
|
+
metadata: {
|
|
446
|
+
version: RECOMMENDATION_VERSION,
|
|
447
|
+
created_at: new Date().toISOString(),
|
|
448
|
+
},
|
|
449
|
+
parameters,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Create an update_cleared recommendation for an unmatched uncleared YNAB transaction.
|
|
455
|
+
*
|
|
456
|
+
* Generates a recommendation to mark an existing YNAB transaction as cleared. This is
|
|
457
|
+
* used when a transaction exists in YNAB but is still marked as "uncleared" and may
|
|
458
|
+
* correspond to a bank statement entry. This is a low-priority suggestion since the
|
|
459
|
+
* transaction already exists and only needs status update.
|
|
460
|
+
*
|
|
461
|
+
* @param txn - The unmatched YNAB transaction (must have cleared status of 'uncleared')
|
|
462
|
+
* @param context - The recommendation context for account ID and currency
|
|
463
|
+
* @returns update_cleared recommendation with low priority and 0.6 confidence
|
|
464
|
+
*/
|
|
465
|
+
function createUpdateClearedRecommendation(
|
|
466
|
+
txn: YNABTransaction,
|
|
467
|
+
context: RecommendationContext,
|
|
468
|
+
): UpdateClearedRecommendation {
|
|
469
|
+
return {
|
|
470
|
+
id: randomUUID(),
|
|
471
|
+
action_type: 'update_cleared',
|
|
472
|
+
priority: 'low',
|
|
473
|
+
confidence: CONFIDENCE.UPDATE_CLEARED,
|
|
474
|
+
message: `Mark transaction as cleared: ${txn.payee_name || 'Unknown'}`,
|
|
475
|
+
reason: 'Transaction exists in YNAB but not yet cleared',
|
|
476
|
+
estimated_impact: toMoneyValueFromDecimal(
|
|
477
|
+
0,
|
|
478
|
+
context.analysis.balance_info.current_cleared.currency,
|
|
479
|
+
),
|
|
480
|
+
account_id: context.account_id,
|
|
481
|
+
metadata: {
|
|
482
|
+
version: RECOMMENDATION_VERSION,
|
|
483
|
+
created_at: new Date().toISOString(),
|
|
484
|
+
},
|
|
485
|
+
parameters: {
|
|
486
|
+
transaction_id: txn.id,
|
|
487
|
+
cleared: 'cleared',
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Sort recommendations by priority and confidence
|
|
494
|
+
*/
|
|
495
|
+
function sortRecommendations(
|
|
496
|
+
recommendations: ActionableRecommendation[],
|
|
497
|
+
): ActionableRecommendation[] {
|
|
498
|
+
return recommendations.sort((a, b) => {
|
|
499
|
+
// Sort by priority first
|
|
500
|
+
const priorityDiff = PRIORITY_ORDER[b.priority] - PRIORITY_ORDER[a.priority];
|
|
501
|
+
if (priorityDiff !== 0) return priorityDiff;
|
|
502
|
+
|
|
503
|
+
// Then by confidence
|
|
504
|
+
return b.confidence - a.confidence;
|
|
505
|
+
});
|
|
506
|
+
}
|