@dizzlkheinz/ynab-mcpb 0.13.1 → 0.15.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/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +3 -0
- package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +3 -0
- package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +1 -0
- package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +5 -0
- package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +1569 -0
- package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +3 -0
- package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +2832 -0
- package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +2709 -0
- package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +2832 -0
- package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +2832 -0
- package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +2709 -0
- package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +1 -0
- package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +5217 -0
- package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +2594 -0
- package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +2594 -0
- package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +231 -0
- package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +2590 -0
- package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +5195 -0
- package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +286 -0
- package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +218 -0
- package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +180 -0
- package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +3 -0
- package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +1 -0
- package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +747 -0
- package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +1 -0
- package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +2594 -0
- package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +2594 -0
- package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +144 -0
- package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +416 -0
- package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +2590 -0
- package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +2590 -0
- package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +2590 -0
- package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +790 -0
- package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +766 -0
- package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +790 -0
- package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +2594 -0
- package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +1000 -0
- package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +3489 -0
- package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +766 -0
- package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +2594 -0
- package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +2456 -0
- package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +2594 -0
- package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +18 -0
- package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +48 -0
- package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +1 -0
- package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +1 -0
- package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +1 -0
- package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +1 -0
- package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +1271 -0
- package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +1570 -0
- package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +2590 -0
- package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +3 -0
- package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +3299 -0
- package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +3299 -0
- package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +1882 -0
- package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +2594 -0
- package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +1 -0
- package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +170 -0
- package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +1 -0
- package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +3 -0
- package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +1 -0
- package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +1 -0
- package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +3 -0
- package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +1 -0
- package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +1 -0
- package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +5 -0
- package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +1 -0
- package/.github/workflows/publish.yml +3 -3
- package/.github/workflows/release.yml +4 -0
- package/CHANGELOG.md +75 -0
- package/NUL +1 -0
- package/dist/bundle/index.cjs +65 -42
- package/dist/server/errorHandler.d.ts +2 -0
- package/dist/server/errorHandler.js +49 -5
- package/dist/tools/reconcileAdapter.js +10 -5
- package/dist/tools/reconciliation/analyzer.d.ts +4 -2
- package/dist/tools/reconciliation/analyzer.js +120 -404
- package/dist/tools/reconciliation/csvParser.d.ts +51 -0
- package/dist/tools/reconciliation/csvParser.js +413 -0
- package/dist/tools/reconciliation/executor.d.ts +8 -0
- package/dist/tools/reconciliation/executor.js +204 -58
- package/dist/tools/reconciliation/index.d.ts +7 -7
- package/dist/tools/reconciliation/index.js +115 -39
- package/dist/tools/reconciliation/matcher.d.ts +24 -3
- package/dist/tools/reconciliation/matcher.js +175 -133
- package/dist/tools/reconciliation/recommendationEngine.js +22 -18
- package/dist/tools/reconciliation/reportFormatter.js +9 -8
- package/dist/tools/reconciliation/signDetector.d.ts +2 -0
- package/dist/tools/reconciliation/signDetector.js +54 -0
- package/dist/tools/reconciliation/types.d.ts +20 -34
- package/dist/tools/reconciliation/types.js +1 -7
- package/dist/tools/reconciliation/ynabAdapter.d.ts +4 -0
- package/dist/tools/reconciliation/ynabAdapter.js +15 -0
- package/dist/types/reconciliation.d.ts +24 -0
- package/dist/types/reconciliation.js +1 -0
- package/docs/guides/ARCHITECTURE.md +12 -129
- package/docs/plans/2025-11-21-v014-hardening.md +153 -0
- package/docs/plans/reconciliation-v2-redesign.md +1571 -0
- package/package.json +6 -1
- package/scripts/test-recommendations.ts +1 -1
- package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +129 -0
- package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +53 -0
- package/src/server/errorHandler.ts +52 -5
- package/src/tools/reconcileAdapter.ts +10 -5
- package/src/tools/reconciliation/__tests__/adapter.test.ts +28 -22
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +114 -180
- package/src/tools/reconciliation/__tests__/csvParser.test.ts +87 -0
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +1 -1
- package/src/tools/reconciliation/__tests__/executor.test.ts +88 -61
- package/src/tools/reconciliation/__tests__/matcher.test.ts +68 -54
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +37 -30
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +6 -5
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +30 -11
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +50 -15
- package/src/tools/reconciliation/__tests__/signDetector.test.ts +211 -0
- package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +61 -0
- package/src/tools/reconciliation/analyzer.ts +174 -545
- package/src/tools/reconciliation/csvParser.ts +617 -0
- package/src/tools/reconciliation/executor.ts +249 -66
- package/src/tools/reconciliation/index.ts +141 -48
- package/src/tools/reconciliation/matcher.ts +234 -214
- package/src/tools/reconciliation/recommendationEngine.ts +23 -19
- package/src/tools/reconciliation/reportFormatter.ts +16 -11
- package/src/tools/reconciliation/signDetector.ts +117 -0
- package/src/tools/reconciliation/types.ts +39 -61
- package/src/tools/reconciliation/ynabAdapter.ts +33 -0
- package/src/types/reconciliation.ts +49 -0
- package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +3662 -0
- package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +0 -8
- package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +0 -3
- package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +0 -3
- package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +0 -1
- package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +0 -12
- package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +0 -1
- package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +0 -1
- package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +0 -5
- package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +0 -1453
- package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +0 -8
- package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +0 -9
- package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +0 -1
- package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +0 -5
- package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +0 -81
- package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +0 -610
- package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +0 -1
- package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +0 -1
- package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +0 -1
- package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +0 -3
- package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +0 -8
- package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +0 -5
- package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +0 -1
- package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +0 -9
- package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +0 -1
- package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +0 -13
- package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +0 -14
- package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +0 -5
- package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +0 -5
- package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +0 -5
- package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +0 -3
- package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +0 -1
- package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +0 -1
- package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +0 -5
- package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +0 -5
- package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +0 -8
- package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +0 -8
- package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +0 -5
- package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +0 -1
- package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +0 -1
- package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +0 -1
- package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +0 -1
- package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +0 -1
- package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +0 -5
- package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +0 -1
- package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +0 -1
- package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +0 -1
- package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +0 -3
- package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +0 -8
- package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +0 -1
- package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +0 -1
- package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +0 -8
- package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +0 -3
- package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +0 -5
- package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +0 -1
- package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +0 -1
- package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +0 -5
- package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +0 -1
- package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +0 -14
- package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +0 -1
- package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +0 -5
- package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +0 -5
- package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +0 -11
- package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +0 -1
- package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +0 -8
- package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +0 -1
- package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +0 -3
- package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +0 -3
- package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +0 -8
- package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +0 -1
- package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +0 -5
- package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +0 -1
- package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +0 -1
- package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +0 -1
- package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +0 -1
- package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +0 -3
- package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +0 -8
- package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +0 -1
- package/.gemini/settings.json +0 -8
- package/WARP.md +0 -245
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* Implements guided reconciliation workflow with conservative matching
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { promises as fs } from 'fs';
|
|
6
7
|
import { z } from 'zod/v4';
|
|
7
8
|
import type * as ynab from 'ynab';
|
|
8
9
|
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
9
10
|
import { withToolErrorHandling } from '../../types/index.js';
|
|
10
11
|
import { analyzeReconciliation } from './analyzer.js';
|
|
11
|
-
import type { MatchingConfig } from './
|
|
12
|
+
import type { MatchingConfig } from './matcher.js';
|
|
12
13
|
import { buildReconciliationPayload } from '../reconcileAdapter.js';
|
|
13
14
|
import {
|
|
14
15
|
executeReconciliation,
|
|
@@ -16,9 +17,11 @@ import {
|
|
|
16
17
|
type LegacyReconciliationResult,
|
|
17
18
|
} from './executor.js';
|
|
18
19
|
import { responseFormatter } from '../../server/responseFormatter.js';
|
|
19
|
-
import {
|
|
20
|
+
import { parseCSV, type ParseCSVOptions, type CSVParseResult } from './csvParser.js';
|
|
20
21
|
import type { DeltaFetcher } from '../deltaFetcher.js';
|
|
21
22
|
import { resolveDeltaFetcherArgs } from '../deltaSupport.js';
|
|
23
|
+
import { detectSignInversion } from './signDetector.js';
|
|
24
|
+
import { normalizeYNABTransactions } from './ynabAdapter.js';
|
|
22
25
|
|
|
23
26
|
// Re-export types for external use
|
|
24
27
|
export type * from './types.js';
|
|
@@ -75,25 +78,17 @@ export const ReconcileAccountSchema = z
|
|
|
75
78
|
|
|
76
79
|
csv_format: z
|
|
77
80
|
.object({
|
|
78
|
-
date_column: z.union([z.string(), z.number()]).optional()
|
|
81
|
+
date_column: z.union([z.string(), z.number()]).optional(),
|
|
79
82
|
amount_column: z.union([z.string(), z.number()]).optional(),
|
|
80
83
|
debit_column: z.union([z.string(), z.number()]).optional(),
|
|
81
84
|
credit_column: z.union([z.string(), z.number()]).optional(),
|
|
82
|
-
description_column: z.union([z.string(), z.number()]).optional()
|
|
83
|
-
date_format: z.string().optional()
|
|
84
|
-
has_header: z.boolean().optional()
|
|
85
|
-
delimiter: z.string().optional()
|
|
85
|
+
description_column: z.union([z.string(), z.number()]).optional(),
|
|
86
|
+
date_format: z.string().optional(),
|
|
87
|
+
has_header: z.boolean().optional(),
|
|
88
|
+
delimiter: z.string().optional(),
|
|
86
89
|
})
|
|
87
90
|
.strict()
|
|
88
|
-
.optional()
|
|
89
|
-
.default(() => ({
|
|
90
|
-
date_column: 'Date',
|
|
91
|
-
amount_column: 'Amount',
|
|
92
|
-
description_column: 'Description',
|
|
93
|
-
date_format: 'MM/DD/YYYY',
|
|
94
|
-
has_header: true,
|
|
95
|
-
delimiter: ',',
|
|
96
|
-
})),
|
|
91
|
+
.optional(),
|
|
97
92
|
|
|
98
93
|
// Statement information
|
|
99
94
|
statement_balance: z.number({
|
|
@@ -106,9 +101,9 @@ export const ReconcileAccountSchema = z
|
|
|
106
101
|
as_of_timezone: z.string().optional(),
|
|
107
102
|
|
|
108
103
|
// Matching configuration (optional)
|
|
109
|
-
date_tolerance_days: z.number().min(0).max(7).optional().default(
|
|
104
|
+
date_tolerance_days: z.number().min(0).max(7).optional().default(7),
|
|
110
105
|
amount_tolerance_cents: z.number().min(0).max(100).optional().default(1),
|
|
111
|
-
auto_match_threshold: z.number().min(0).max(100).optional().default(
|
|
106
|
+
auto_match_threshold: z.number().min(0).max(100).optional().default(85),
|
|
112
107
|
suggestion_threshold: z.number().min(0).max(100).optional().default(60),
|
|
113
108
|
amount_tolerance: z.number().min(0).max(1).optional(),
|
|
114
109
|
|
|
@@ -166,13 +161,21 @@ export async function handleReconcileAccount(
|
|
|
166
161
|
const forceFullRefresh = params.force_full_refresh ?? true;
|
|
167
162
|
return await withToolErrorHandling(
|
|
168
163
|
async () => {
|
|
169
|
-
// Build matching configuration from parameters
|
|
164
|
+
// Build matching configuration from parameters (V2 Format)
|
|
170
165
|
const config: MatchingConfig = {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
166
|
+
weights: {
|
|
167
|
+
amount: 0.5,
|
|
168
|
+
date: 0.15,
|
|
169
|
+
payee: 0.35,
|
|
170
|
+
},
|
|
171
|
+
dateToleranceDays: params.date_tolerance_days ?? 5,
|
|
172
|
+
amountToleranceMilliunits: (params.amount_tolerance_cents ?? 1) * 10,
|
|
173
|
+
autoMatchThreshold: params.auto_match_threshold ?? 90,
|
|
174
|
+
suggestedMatchThreshold: params.suggestion_threshold ?? 60,
|
|
175
|
+
minimumCandidateScore: 40,
|
|
176
|
+
exactAmountBonus: 10,
|
|
177
|
+
exactDateBonus: 5,
|
|
178
|
+
exactPayeeBonus: 10,
|
|
176
179
|
};
|
|
177
180
|
|
|
178
181
|
const accountResult = forceFullRefresh
|
|
@@ -213,37 +216,78 @@ export async function handleReconcileAccount(
|
|
|
213
216
|
const budgetResponse = await ynabAPI.budgets.getBudgetById(params.budget_id);
|
|
214
217
|
const currencyCode = budgetResponse.data.budget?.currency_format?.iso_code ?? 'USD';
|
|
215
218
|
|
|
219
|
+
// Prepare CSV parsing options from request
|
|
220
|
+
const dateFormat = mapCsvDateFormatToHint(params.csv_format?.date_format);
|
|
221
|
+
const csvOptions: ParseCSVOptions = {
|
|
222
|
+
columns: {
|
|
223
|
+
...(params.csv_format?.date_column !== undefined && {
|
|
224
|
+
date: String(params.csv_format.date_column),
|
|
225
|
+
}),
|
|
226
|
+
...(params.csv_format?.amount_column !== undefined && {
|
|
227
|
+
amount: String(params.csv_format.amount_column),
|
|
228
|
+
}),
|
|
229
|
+
...(params.csv_format?.debit_column !== undefined && {
|
|
230
|
+
debit: String(params.csv_format.debit_column),
|
|
231
|
+
}),
|
|
232
|
+
...(params.csv_format?.credit_column !== undefined && {
|
|
233
|
+
credit: String(params.csv_format.credit_column),
|
|
234
|
+
}),
|
|
235
|
+
...(params.csv_format?.description_column !== undefined && {
|
|
236
|
+
description: String(params.csv_format.description_column),
|
|
237
|
+
}),
|
|
238
|
+
},
|
|
239
|
+
...(dateFormat && { dateFormat }),
|
|
240
|
+
...(params.csv_format?.has_header !== undefined && {
|
|
241
|
+
header: params.csv_format.has_header,
|
|
242
|
+
}),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Load CSV content from either inline data or filesystem path
|
|
246
|
+
let csvContent = params.csv_data ?? '';
|
|
247
|
+
if (!csvContent && params.csv_file_path) {
|
|
248
|
+
try {
|
|
249
|
+
csvContent = await fs.readFile(params.csv_file_path, 'utf8');
|
|
250
|
+
} catch (error) {
|
|
251
|
+
const message =
|
|
252
|
+
error instanceof Error && error.message
|
|
253
|
+
? error.message
|
|
254
|
+
: 'Unknown error while reading CSV file';
|
|
255
|
+
throw new Error(`Failed to read CSV file at path ${params.csv_file_path}: ${message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
216
259
|
// Fetch YNAB transactions for the account
|
|
217
260
|
// Auto-detect date range from CSV if not explicitly provided
|
|
218
261
|
let sinceDate: Date;
|
|
262
|
+
let parseResult: CSVParseResult | undefined;
|
|
219
263
|
|
|
220
264
|
if (params.statement_start_date) {
|
|
221
265
|
// User provided explicit start date
|
|
222
266
|
sinceDate = new Date(params.statement_start_date);
|
|
223
267
|
} else {
|
|
224
|
-
// Auto-detect from CSV content
|
|
268
|
+
// Auto-detect from CSV content using new parser
|
|
225
269
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
270
|
+
parseResult = parseCSV(csvContent, {
|
|
271
|
+
...csvOptions,
|
|
272
|
+
invertAmounts: shouldInvertBankAmounts,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (parseResult.transactions.length > 0) {
|
|
276
|
+
// Find min date
|
|
277
|
+
const dates = parseResult.transactions
|
|
278
|
+
.map((t) => new Date(t.date).getTime())
|
|
279
|
+
.filter((t) => !isNaN(t));
|
|
280
|
+
if (dates.length > 0) {
|
|
281
|
+
const minTime = Math.min(...dates);
|
|
282
|
+
const minDateObj = new Date(minTime);
|
|
283
|
+
minDateObj.setDate(minDateObj.getDate() - 7); // 7-day buffer
|
|
284
|
+
sinceDate = minDateObj;
|
|
285
|
+
} else {
|
|
286
|
+
sinceDate = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
sinceDate = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
|
|
290
|
+
}
|
|
247
291
|
} catch {
|
|
248
292
|
// Fallback to 90 days if CSV parsing fails
|
|
249
293
|
sinceDate = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
|
|
@@ -265,6 +309,32 @@ export async function handleReconcileAccount(
|
|
|
265
309
|
|
|
266
310
|
const ynabTransactions = transactionsResult.data;
|
|
267
311
|
|
|
312
|
+
// Smart sign detection: If invert_bank_amounts not explicitly set, auto-detect
|
|
313
|
+
let finalInvertAmounts = shouldInvertBankAmounts;
|
|
314
|
+
if (params.invert_bank_amounts === undefined && csvContent) {
|
|
315
|
+
// Parse CSV without inversion to get raw amounts
|
|
316
|
+
const rawParseResult = parseCSV(csvContent, {
|
|
317
|
+
...csvOptions,
|
|
318
|
+
invertAmounts: false, // Don't invert yet
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (rawParseResult.transactions.length > 0 && ynabTransactions.length > 0) {
|
|
322
|
+
// Normalize YNAB transactions for comparison
|
|
323
|
+
const normalizedYNAB = normalizeYNABTransactions(ynabTransactions);
|
|
324
|
+
|
|
325
|
+
// Detect if signs are mismatched
|
|
326
|
+
const needsInversion = detectSignInversion(rawParseResult.transactions, normalizedYNAB);
|
|
327
|
+
|
|
328
|
+
finalInvertAmounts = needsInversion;
|
|
329
|
+
|
|
330
|
+
// If detection result differs from default, invalidate parseResult
|
|
331
|
+
// to force re-parsing with correct inversion
|
|
332
|
+
if (needsInversion !== shouldInvertBankAmounts && parseResult) {
|
|
333
|
+
parseResult = undefined;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
268
338
|
const auditMetadata = {
|
|
269
339
|
data_freshness: getDataFreshness(transactionsResult, forceFullRefresh),
|
|
270
340
|
data_source: getAuditDataSource(transactionsResult, forceFullRefresh),
|
|
@@ -281,7 +351,7 @@ export async function handleReconcileAccount(
|
|
|
281
351
|
|
|
282
352
|
// Perform analysis
|
|
283
353
|
const analysis = analyzeReconciliation(
|
|
284
|
-
|
|
354
|
+
parseResult ?? csvContent,
|
|
285
355
|
params.csv_file_path,
|
|
286
356
|
ynabTransactions,
|
|
287
357
|
adjustedStatementBalance,
|
|
@@ -289,7 +359,8 @@ export async function handleReconcileAccount(
|
|
|
289
359
|
currencyCode,
|
|
290
360
|
params.account_id,
|
|
291
361
|
params.budget_id,
|
|
292
|
-
|
|
362
|
+
finalInvertAmounts, // Use smart-detected value
|
|
363
|
+
csvOptions,
|
|
293
364
|
);
|
|
294
365
|
|
|
295
366
|
const initialAccount: AccountSnapshot = {
|
|
@@ -359,6 +430,28 @@ export async function handleReconcileAccount(
|
|
|
359
430
|
);
|
|
360
431
|
}
|
|
361
432
|
|
|
433
|
+
function mapCsvDateFormatToHint(
|
|
434
|
+
format: string | undefined,
|
|
435
|
+
): ParseCSVOptions['dateFormat'] | undefined {
|
|
436
|
+
if (!format) {
|
|
437
|
+
return undefined;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const normalized = format.toUpperCase().replace(/[^YMD]/g, '');
|
|
441
|
+
|
|
442
|
+
if (normalized === 'YYYYMMDD' || normalized === 'YYMMDD' || normalized === 'YMD') {
|
|
443
|
+
return 'YMD';
|
|
444
|
+
}
|
|
445
|
+
if (normalized === 'MMDDYYYY' || normalized === 'MDY') {
|
|
446
|
+
return 'MDY';
|
|
447
|
+
}
|
|
448
|
+
if (normalized === 'DDMMYYYY' || normalized === 'DMY') {
|
|
449
|
+
return 'DMY';
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return undefined;
|
|
453
|
+
}
|
|
454
|
+
|
|
362
455
|
function mapCsvFormatForPayload(format: ReconcileAccountRequest['csv_format'] | undefined):
|
|
363
456
|
| {
|
|
364
457
|
delimiter: string;
|