@dizzlkheinz/ynab-mcpb 0.13.1 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +8 -2
- package/dist/tools/reconciliation/analyzer.js +127 -409
- 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 +191 -550
- package/src/tools/reconciliation/csvParser.ts +617 -0
- package/src/tools/reconciliation/executor.ts +249 -66
- package/src/tools/reconciliation/index.ts +148 -54
- 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),
|
|
@@ -279,9 +349,15 @@ export async function handleReconcileAccount(
|
|
|
279
349
|
},
|
|
280
350
|
};
|
|
281
351
|
|
|
352
|
+
const initialAccount: AccountSnapshot = {
|
|
353
|
+
balance: accountData.balance,
|
|
354
|
+
cleared_balance: accountData.cleared_balance,
|
|
355
|
+
uncleared_balance: accountData.uncleared_balance,
|
|
356
|
+
};
|
|
357
|
+
|
|
282
358
|
// Perform analysis
|
|
283
359
|
const analysis = analyzeReconciliation(
|
|
284
|
-
|
|
360
|
+
parseResult ?? csvContent,
|
|
285
361
|
params.csv_file_path,
|
|
286
362
|
ynabTransactions,
|
|
287
363
|
adjustedStatementBalance,
|
|
@@ -289,15 +365,11 @@ export async function handleReconcileAccount(
|
|
|
289
365
|
currencyCode,
|
|
290
366
|
params.account_id,
|
|
291
367
|
params.budget_id,
|
|
292
|
-
|
|
368
|
+
finalInvertAmounts, // Use smart-detected value
|
|
369
|
+
csvOptions,
|
|
370
|
+
initialAccount,
|
|
293
371
|
);
|
|
294
372
|
|
|
295
|
-
const initialAccount: AccountSnapshot = {
|
|
296
|
-
balance: accountData.balance,
|
|
297
|
-
cleared_balance: accountData.cleared_balance,
|
|
298
|
-
uncleared_balance: accountData.uncleared_balance,
|
|
299
|
-
};
|
|
300
|
-
|
|
301
373
|
let executionData: LegacyReconciliationResult | undefined;
|
|
302
374
|
const wantsBalanceVerification = Boolean(params.statement_date);
|
|
303
375
|
const shouldExecute =
|
|
@@ -359,6 +431,28 @@ export async function handleReconcileAccount(
|
|
|
359
431
|
);
|
|
360
432
|
}
|
|
361
433
|
|
|
434
|
+
function mapCsvDateFormatToHint(
|
|
435
|
+
format: string | undefined,
|
|
436
|
+
): ParseCSVOptions['dateFormat'] | undefined {
|
|
437
|
+
if (!format) {
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const normalized = format.toUpperCase().replace(/[^YMD]/g, '');
|
|
442
|
+
|
|
443
|
+
if (normalized === 'YYYYMMDD' || normalized === 'YYMMDD' || normalized === 'YMD') {
|
|
444
|
+
return 'YMD';
|
|
445
|
+
}
|
|
446
|
+
if (normalized === 'MMDDYYYY' || normalized === 'MDY') {
|
|
447
|
+
return 'MDY';
|
|
448
|
+
}
|
|
449
|
+
if (normalized === 'DDMMYYYY' || normalized === 'DMY') {
|
|
450
|
+
return 'DMY';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
|
|
362
456
|
function mapCsvFormatForPayload(format: ReconcileAccountRequest['csv_format'] | undefined):
|
|
363
457
|
| {
|
|
364
458
|
delimiter: string;
|