@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
|
@@ -7,11 +7,19 @@ describe('matcher', () => {
|
|
|
7
7
|
|
|
8
8
|
beforeEach(() => {
|
|
9
9
|
config = {
|
|
10
|
+
weights: {
|
|
11
|
+
amount: 0.5,
|
|
12
|
+
date: 0.15,
|
|
13
|
+
payee: 0.35,
|
|
14
|
+
},
|
|
15
|
+
amountToleranceMilliunits: 10,
|
|
10
16
|
dateToleranceDays: 2,
|
|
11
|
-
amountToleranceCents: 1,
|
|
12
|
-
descriptionSimilarityThreshold: 0.8,
|
|
13
17
|
autoMatchThreshold: 90,
|
|
14
|
-
|
|
18
|
+
suggestedMatchThreshold: 60,
|
|
19
|
+
minimumCandidateScore: 40,
|
|
20
|
+
exactAmountBonus: 10,
|
|
21
|
+
exactDateBonus: 5,
|
|
22
|
+
exactPayeeBonus: 10,
|
|
15
23
|
};
|
|
16
24
|
});
|
|
17
25
|
|
|
@@ -21,7 +29,7 @@ describe('matcher', () => {
|
|
|
21
29
|
const bankTxn: BankTransaction = {
|
|
22
30
|
id: 'b1',
|
|
23
31
|
date: '2025-10-15',
|
|
24
|
-
amount: -45.23
|
|
32
|
+
amount: -45230, // milliunits (-45.23)
|
|
25
33
|
payee: 'Shell Gas Station',
|
|
26
34
|
original_csv_row: 2,
|
|
27
35
|
};
|
|
@@ -41,15 +49,15 @@ describe('matcher', () => {
|
|
|
41
49
|
const match = findBestMatch(bankTxn, ynabTxns, new Set(), config);
|
|
42
50
|
|
|
43
51
|
expect(match.confidence).toBe('high');
|
|
44
|
-
expect(match.
|
|
45
|
-
expect(match.
|
|
52
|
+
expect(match.confidenceScore).toBeGreaterThanOrEqual(90);
|
|
53
|
+
expect(match.bestMatch?.ynabTransaction).toEqual(ynabTxns[0]);
|
|
46
54
|
});
|
|
47
55
|
|
|
48
56
|
it('should return high confidence for normalized payee match', () => {
|
|
49
57
|
const bankTxn: BankTransaction = {
|
|
50
58
|
id: 'b1',
|
|
51
59
|
date: '2025-10-15',
|
|
52
|
-
amount: -100.
|
|
60
|
+
amount: -100000, // milliunits (-100.00)
|
|
53
61
|
payee: 'NETFLIX.COM',
|
|
54
62
|
original_csv_row: 2,
|
|
55
63
|
};
|
|
@@ -69,14 +77,14 @@ describe('matcher', () => {
|
|
|
69
77
|
const match = findBestMatch(bankTxn, ynabTxns, new Set(), config);
|
|
70
78
|
|
|
71
79
|
expect(match.confidence).toBe('high');
|
|
72
|
-
expect(match.
|
|
80
|
+
expect(match.confidenceScore).toBeGreaterThanOrEqual(90);
|
|
73
81
|
});
|
|
74
82
|
|
|
75
83
|
it('should handle date within tolerance', () => {
|
|
76
84
|
const bankTxn: BankTransaction = {
|
|
77
85
|
id: 'b1',
|
|
78
86
|
date: '2025-10-15',
|
|
79
|
-
amount: -50.
|
|
87
|
+
amount: -50000, // milliunits (-50.00)
|
|
80
88
|
payee: 'Restaurant',
|
|
81
89
|
original_csv_row: 2,
|
|
82
90
|
};
|
|
@@ -86,8 +94,8 @@ describe('matcher', () => {
|
|
|
86
94
|
id: 'y1',
|
|
87
95
|
date: '2025-10-14', // 1 day difference
|
|
88
96
|
amount: -50000,
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
payee: 'Restaurant',
|
|
98
|
+
categoryName: 'Dining',
|
|
91
99
|
cleared: 'uncleared',
|
|
92
100
|
approved: true,
|
|
93
101
|
},
|
|
@@ -96,16 +104,14 @@ describe('matcher', () => {
|
|
|
96
104
|
const match = findBestMatch(bankTxn, ynabTxns, new Set(), config);
|
|
97
105
|
|
|
98
106
|
expect(match.confidence).toBe('high');
|
|
99
|
-
expect(match.
|
|
107
|
+
expect(match.confidenceScore).toBeGreaterThanOrEqual(90);
|
|
100
108
|
});
|
|
101
|
-
});
|
|
102
109
|
|
|
103
|
-
|
|
104
|
-
it('should return medium confidence for fuzzy payee match', () => {
|
|
110
|
+
it('should return high confidence for fuzzy payee match', () => {
|
|
105
111
|
const bankTxn: BankTransaction = {
|
|
106
112
|
id: 'b1',
|
|
107
113
|
date: '2025-10-20',
|
|
108
|
-
amount: -127.43
|
|
114
|
+
amount: -127430, // milliunits (-127.43)
|
|
109
115
|
payee: 'AMAZON.COM',
|
|
110
116
|
original_csv_row: 2,
|
|
111
117
|
};
|
|
@@ -124,18 +130,19 @@ describe('matcher', () => {
|
|
|
124
130
|
|
|
125
131
|
const match = findBestMatch(bankTxn, ynabTxns, new Set(), config);
|
|
126
132
|
|
|
127
|
-
expect(match.confidence).toBe('
|
|
128
|
-
expect(match.
|
|
129
|
-
expect(match.confidence_score).toBeLessThan(90);
|
|
133
|
+
expect(match.confidence).toBe('high');
|
|
134
|
+
expect(match.confidenceScore).toBeGreaterThanOrEqual(90);
|
|
130
135
|
expect(match.candidates).toBeDefined();
|
|
131
136
|
expect(match.candidates!.length).toBeGreaterThan(0);
|
|
132
137
|
});
|
|
138
|
+
});
|
|
133
139
|
|
|
140
|
+
describe('medium confidence matches (60-89%)', () => {
|
|
134
141
|
it('should provide multiple candidates for medium confidence', () => {
|
|
135
142
|
const bankTxn: BankTransaction = {
|
|
136
143
|
id: 'b1',
|
|
137
144
|
date: '2025-10-15',
|
|
138
|
-
amount: -
|
|
145
|
+
amount: -50000,
|
|
139
146
|
payee: 'Restaurant',
|
|
140
147
|
original_csv_row: 2,
|
|
141
148
|
};
|
|
@@ -174,7 +181,7 @@ describe('matcher', () => {
|
|
|
174
181
|
const bankTxn: BankTransaction = {
|
|
175
182
|
id: 'b1',
|
|
176
183
|
date: '2025-10-15',
|
|
177
|
-
amount: -
|
|
184
|
+
amount: -45230,
|
|
178
185
|
payee: 'Shell',
|
|
179
186
|
original_csv_row: 2,
|
|
180
187
|
};
|
|
@@ -194,15 +201,15 @@ describe('matcher', () => {
|
|
|
194
201
|
const match = findBestMatch(bankTxn, ynabTxns, new Set(), config);
|
|
195
202
|
|
|
196
203
|
expect(match.confidence).toBe('none');
|
|
197
|
-
expect(match.
|
|
198
|
-
expect(match.
|
|
204
|
+
expect(match.confidenceScore).toBe(0);
|
|
205
|
+
expect(match.bestMatch).toBeNull();
|
|
199
206
|
});
|
|
200
207
|
|
|
201
208
|
it('should not match opposite-signed transactions', () => {
|
|
202
209
|
const bankTxn: BankTransaction = {
|
|
203
210
|
id: 'b1',
|
|
204
211
|
date: '2025-10-15',
|
|
205
|
-
amount:
|
|
212
|
+
amount: 50000, // Positive (refund) in milliunits
|
|
206
213
|
payee: 'Amazon',
|
|
207
214
|
original_csv_row: 2,
|
|
208
215
|
};
|
|
@@ -222,7 +229,7 @@ describe('matcher', () => {
|
|
|
222
229
|
const match = findBestMatch(bankTxn, ynabTxns, new Set(), config);
|
|
223
230
|
|
|
224
231
|
expect(match.confidence).toBe('none');
|
|
225
|
-
expect(match.
|
|
232
|
+
expect(match.bestMatch).toBeNull();
|
|
226
233
|
});
|
|
227
234
|
});
|
|
228
235
|
|
|
@@ -231,7 +238,7 @@ describe('matcher', () => {
|
|
|
231
238
|
const bankTxn: BankTransaction = {
|
|
232
239
|
id: 'b1',
|
|
233
240
|
date: '2025-10-15',
|
|
234
|
-
amount: -
|
|
241
|
+
amount: -50000,
|
|
235
242
|
payee: 'Coffee Shop',
|
|
236
243
|
original_csv_row: 2,
|
|
237
244
|
};
|
|
@@ -260,14 +267,14 @@ describe('matcher', () => {
|
|
|
260
267
|
const match = findBestMatch(bankTxn, ynabTxns, new Set(), config);
|
|
261
268
|
|
|
262
269
|
// Should prefer uncleared transaction
|
|
263
|
-
expect(match.
|
|
270
|
+
expect(match.bestMatch?.ynabTransaction.id).toBe('y2');
|
|
264
271
|
});
|
|
265
272
|
|
|
266
273
|
it('should use date proximity as tiebreaker', () => {
|
|
267
274
|
const bankTxn: BankTransaction = {
|
|
268
275
|
id: 'b1',
|
|
269
276
|
date: '2025-10-15',
|
|
270
|
-
amount: -
|
|
277
|
+
amount: -50000,
|
|
271
278
|
payee: 'Store',
|
|
272
279
|
original_csv_row: 2,
|
|
273
280
|
};
|
|
@@ -296,7 +303,7 @@ describe('matcher', () => {
|
|
|
296
303
|
const match = findBestMatch(bankTxn, ynabTxns, new Set(), config);
|
|
297
304
|
|
|
298
305
|
// Should prefer closer date
|
|
299
|
-
expect(match.
|
|
306
|
+
expect(match.bestMatch?.ynabTransaction.id).toBe('y2');
|
|
300
307
|
});
|
|
301
308
|
});
|
|
302
309
|
|
|
@@ -305,7 +312,7 @@ describe('matcher', () => {
|
|
|
305
312
|
const bankTxn: BankTransaction = {
|
|
306
313
|
id: 'b1',
|
|
307
314
|
date: '2025-10-15',
|
|
308
|
-
amount: -
|
|
315
|
+
amount: -45230,
|
|
309
316
|
payee: 'Shell',
|
|
310
317
|
original_csv_row: 2,
|
|
311
318
|
};
|
|
@@ -325,16 +332,16 @@ describe('matcher', () => {
|
|
|
325
332
|
const match = findBestMatch(bankTxn, ynabTxns, new Set(), config);
|
|
326
333
|
|
|
327
334
|
expect(match.confidence).not.toBe('none');
|
|
328
|
-
expect(match.
|
|
335
|
+
expect(match.bestMatch?.ynabTransaction).toBeDefined();
|
|
329
336
|
});
|
|
330
337
|
|
|
331
338
|
it('should not match outside amount tolerance', () => {
|
|
332
|
-
config.
|
|
339
|
+
config.amountToleranceMilliunits = 10; // 1 cent
|
|
333
340
|
|
|
334
341
|
const bankTxn: BankTransaction = {
|
|
335
342
|
id: 'b1',
|
|
336
343
|
date: '2025-10-15',
|
|
337
|
-
amount: -
|
|
344
|
+
amount: -45000,
|
|
338
345
|
payee: 'Shell',
|
|
339
346
|
original_csv_row: 2,
|
|
340
347
|
};
|
|
@@ -362,7 +369,7 @@ describe('matcher', () => {
|
|
|
362
369
|
const bankTxn: BankTransaction = {
|
|
363
370
|
id: 'b1',
|
|
364
371
|
date: '2025-10-15',
|
|
365
|
-
amount: -
|
|
372
|
+
amount: -50000,
|
|
366
373
|
payee: 'Store',
|
|
367
374
|
original_csv_row: 2,
|
|
368
375
|
};
|
|
@@ -383,7 +390,7 @@ describe('matcher', () => {
|
|
|
383
390
|
const match = findBestMatch(bankTxn, ynabTxns, usedIds, config);
|
|
384
391
|
|
|
385
392
|
expect(match.confidence).toBe('none');
|
|
386
|
-
expect(match.
|
|
393
|
+
expect(match.bestMatch).toBeNull();
|
|
387
394
|
});
|
|
388
395
|
});
|
|
389
396
|
});
|
|
@@ -394,14 +401,14 @@ describe('matcher', () => {
|
|
|
394
401
|
{
|
|
395
402
|
id: 'b1',
|
|
396
403
|
date: '2025-10-15',
|
|
397
|
-
amount: -
|
|
404
|
+
amount: -45230,
|
|
398
405
|
payee: 'Shell',
|
|
399
406
|
original_csv_row: 2,
|
|
400
407
|
},
|
|
401
408
|
{
|
|
402
409
|
id: 'b2',
|
|
403
410
|
date: '2025-10-16',
|
|
404
|
-
amount: -
|
|
411
|
+
amount: -100000,
|
|
405
412
|
payee: 'Netflix',
|
|
406
413
|
original_csv_row: 3,
|
|
407
414
|
},
|
|
@@ -431,8 +438,8 @@ describe('matcher', () => {
|
|
|
431
438
|
const matches = findMatches(bankTxns, ynabTxns, config);
|
|
432
439
|
|
|
433
440
|
expect(matches).toHaveLength(2);
|
|
434
|
-
expect(matches[0].
|
|
435
|
-
expect(matches[1].
|
|
441
|
+
expect(matches[0].bankTransaction.id).toBe('b1');
|
|
442
|
+
expect(matches[1].bankTransaction.id).toBe('b2');
|
|
436
443
|
});
|
|
437
444
|
|
|
438
445
|
it('should prevent duplicate matching of YNAB transactions', () => {
|
|
@@ -440,14 +447,14 @@ describe('matcher', () => {
|
|
|
440
447
|
{
|
|
441
448
|
id: 'b1',
|
|
442
449
|
date: '2025-10-15',
|
|
443
|
-
amount: -
|
|
450
|
+
amount: -50000,
|
|
444
451
|
payee: 'Store',
|
|
445
452
|
original_csv_row: 2,
|
|
446
453
|
},
|
|
447
454
|
{
|
|
448
455
|
id: 'b2',
|
|
449
456
|
date: '2025-10-15',
|
|
450
|
-
amount: -
|
|
457
|
+
amount: -50000,
|
|
451
458
|
payee: 'Store',
|
|
452
459
|
original_csv_row: 3,
|
|
453
460
|
},
|
|
@@ -471,7 +478,7 @@ describe('matcher', () => {
|
|
|
471
478
|
|
|
472
479
|
// First should match
|
|
473
480
|
expect(matches[0].confidence).toBe('high');
|
|
474
|
-
expect(matches[0].
|
|
481
|
+
expect(matches[0].bestMatch?.ynabTransaction.id).toBe('y1');
|
|
475
482
|
|
|
476
483
|
// Second should not match (y1 already used)
|
|
477
484
|
expect(matches[1].confidence).toBe('none');
|
|
@@ -482,14 +489,14 @@ describe('matcher', () => {
|
|
|
482
489
|
{
|
|
483
490
|
id: 'b1',
|
|
484
491
|
date: '2025-10-15',
|
|
485
|
-
amount: -
|
|
492
|
+
amount: -45230,
|
|
486
493
|
payee: 'Shell',
|
|
487
494
|
original_csv_row: 2,
|
|
488
495
|
},
|
|
489
496
|
{
|
|
490
497
|
id: 'b2',
|
|
491
498
|
date: '2025-10-16',
|
|
492
|
-
amount: -
|
|
499
|
+
amount: -15990,
|
|
493
500
|
payee: 'NewStore',
|
|
494
501
|
original_csv_row: 3,
|
|
495
502
|
},
|
|
@@ -512,23 +519,31 @@ describe('matcher', () => {
|
|
|
512
519
|
expect(matches).toHaveLength(2);
|
|
513
520
|
expect(matches[0].confidence).toBe('high');
|
|
514
521
|
expect(matches[1].confidence).toBe('none');
|
|
515
|
-
expect(matches[1].
|
|
522
|
+
expect(matches[1].bestMatch).toBeNull();
|
|
516
523
|
});
|
|
517
524
|
|
|
518
525
|
it('should use custom configuration', () => {
|
|
519
526
|
const customConfig: MatchingConfig = {
|
|
527
|
+
weights: {
|
|
528
|
+
amount: 0.5,
|
|
529
|
+
date: 0.15,
|
|
530
|
+
payee: 0.35,
|
|
531
|
+
},
|
|
532
|
+
amountToleranceMilliunits: 100, // 10 cents
|
|
520
533
|
dateToleranceDays: 5,
|
|
521
|
-
amountToleranceCents: 10,
|
|
522
|
-
descriptionSimilarityThreshold: 0.6,
|
|
523
534
|
autoMatchThreshold: 85,
|
|
524
|
-
|
|
535
|
+
suggestedMatchThreshold: 50,
|
|
536
|
+
minimumCandidateScore: 40,
|
|
537
|
+
exactAmountBonus: 10,
|
|
538
|
+
exactDateBonus: 5,
|
|
539
|
+
exactPayeeBonus: 10,
|
|
525
540
|
};
|
|
526
541
|
|
|
527
542
|
const bankTxns: BankTransaction[] = [
|
|
528
543
|
{
|
|
529
544
|
id: 'b1',
|
|
530
545
|
date: '2025-10-15',
|
|
531
|
-
amount: -
|
|
546
|
+
amount: -50000,
|
|
532
547
|
payee: 'Store',
|
|
533
548
|
original_csv_row: 2,
|
|
534
549
|
},
|
|
@@ -565,14 +580,14 @@ describe('matcher', () => {
|
|
|
565
580
|
const match = findBestMatch(bankTxn, [], new Set(), config);
|
|
566
581
|
|
|
567
582
|
expect(match.confidence).toBe('none');
|
|
568
|
-
expect(match.
|
|
583
|
+
expect(match.bestMatch).toBeNull();
|
|
569
584
|
});
|
|
570
585
|
|
|
571
586
|
it('should handle null payee names', () => {
|
|
572
587
|
const bankTxn: BankTransaction = {
|
|
573
588
|
id: 'b1',
|
|
574
589
|
date: '2025-10-15',
|
|
575
|
-
amount: -
|
|
590
|
+
amount: -50000,
|
|
576
591
|
payee: 'Store',
|
|
577
592
|
original_csv_row: 2,
|
|
578
593
|
};
|
|
@@ -599,7 +614,7 @@ describe('matcher', () => {
|
|
|
599
614
|
const bankTxn: BankTransaction = {
|
|
600
615
|
id: 'b1',
|
|
601
616
|
date: '2025-10-15',
|
|
602
|
-
amount: -
|
|
617
|
+
amount: -10, // 1 cent in milliunits
|
|
603
618
|
payee: 'Micro Transaction',
|
|
604
619
|
original_csv_row: 2,
|
|
605
620
|
};
|
|
@@ -617,7 +632,6 @@ describe('matcher', () => {
|
|
|
617
632
|
];
|
|
618
633
|
|
|
619
634
|
const match = findBestMatch(bankTxn, ynabTxns, new Set(), config);
|
|
620
|
-
|
|
621
635
|
expect(match.confidence).toBe('high');
|
|
622
636
|
});
|
|
623
637
|
|
|
@@ -625,7 +639,7 @@ describe('matcher', () => {
|
|
|
625
639
|
const bankTxn: BankTransaction = {
|
|
626
640
|
id: 'b1',
|
|
627
641
|
date: '2025-10-15',
|
|
628
|
-
amount: -
|
|
642
|
+
amount: -10000000, // $10,000 in milliunits
|
|
629
643
|
payee: 'Large Purchase',
|
|
630
644
|
original_csv_row: 2,
|
|
631
645
|
};
|
|
@@ -71,15 +71,22 @@ const createMockContext = (overrides?: Partial<RecommendationContext>): Recommen
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
// Helper to create mock bank transaction
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
amount
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
// NOTE: amount overrides are provided in DECIMAL units (dollars) and converted to milliunits
|
|
75
|
+
const createBankTransaction = (overrides?: Partial<BankTransaction>): BankTransaction => {
|
|
76
|
+
const { amount, ...restOverrides } = overrides ?? {};
|
|
77
|
+
const baseAmount = amount ?? -50.0; // dollars
|
|
78
|
+
const amountMilli = Math.round(baseAmount * 1000); // milliunits
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
id: 'bank-txn-1',
|
|
82
|
+
date: '2024-01-15',
|
|
83
|
+
amount: amountMilli,
|
|
84
|
+
payee: 'Test Store',
|
|
85
|
+
memo: 'Test memo',
|
|
86
|
+
original_csv_row: 1,
|
|
87
|
+
...restOverrides,
|
|
88
|
+
};
|
|
89
|
+
};
|
|
83
90
|
|
|
84
91
|
// Helper to create mock YNAB transaction
|
|
85
92
|
const createYNABTransaction = (overrides?: Partial<YNABTransaction>): YNABTransaction => ({
|
|
@@ -406,11 +413,11 @@ describe('recommendationEngine', () => {
|
|
|
406
413
|
const ynabTxn = createYNABTransaction();
|
|
407
414
|
|
|
408
415
|
const suggestedMatch: TransactionMatch = {
|
|
409
|
-
|
|
410
|
-
|
|
416
|
+
bankTransaction: bankTxn,
|
|
417
|
+
ynabTransaction: ynabTxn,
|
|
411
418
|
confidence: 'medium',
|
|
412
|
-
|
|
413
|
-
|
|
419
|
+
confidenceScore: 75,
|
|
420
|
+
matchReason: 'Fuzzy payee match',
|
|
414
421
|
};
|
|
415
422
|
|
|
416
423
|
const context = createMockContext({
|
|
@@ -435,10 +442,10 @@ describe('recommendationEngine', () => {
|
|
|
435
442
|
const bankTxn = createBankTransaction({ amount: -45.0 });
|
|
436
443
|
|
|
437
444
|
const suggestedMatch: TransactionMatch = {
|
|
438
|
-
|
|
445
|
+
bankTransaction: bankTxn,
|
|
439
446
|
confidence: 'none',
|
|
440
|
-
|
|
441
|
-
|
|
447
|
+
confidenceScore: 0,
|
|
448
|
+
matchReason: 'No match found',
|
|
442
449
|
};
|
|
443
450
|
|
|
444
451
|
const context = createMockContext({
|
|
@@ -472,7 +479,7 @@ describe('recommendationEngine', () => {
|
|
|
472
479
|
});
|
|
473
480
|
|
|
474
481
|
const suggestedMatch: TransactionMatch = {
|
|
475
|
-
|
|
482
|
+
bankTransaction: bankTxn,
|
|
476
483
|
candidates: [
|
|
477
484
|
{
|
|
478
485
|
ynab_transaction: ynabTxn1,
|
|
@@ -488,8 +495,8 @@ describe('recommendationEngine', () => {
|
|
|
488
495
|
},
|
|
489
496
|
],
|
|
490
497
|
confidence: 'medium',
|
|
491
|
-
|
|
492
|
-
|
|
498
|
+
confidenceScore: 60,
|
|
499
|
+
matchReason: 'combination_match',
|
|
493
500
|
};
|
|
494
501
|
|
|
495
502
|
const context = createMockContext({
|
|
@@ -575,10 +582,10 @@ describe('recommendationEngine', () => {
|
|
|
575
582
|
const bankTxn = createBankTransaction({ amount: -99.99 });
|
|
576
583
|
|
|
577
584
|
const suggestedMatch: TransactionMatch = {
|
|
578
|
-
|
|
585
|
+
bankTransaction: bankTxn,
|
|
579
586
|
confidence: 'none',
|
|
580
|
-
|
|
581
|
-
|
|
587
|
+
confidenceScore: 0,
|
|
588
|
+
matchReason: 'No match',
|
|
582
589
|
};
|
|
583
590
|
|
|
584
591
|
const context = createMockContext({
|
|
@@ -662,11 +669,11 @@ describe('recommendationEngine', () => {
|
|
|
662
669
|
const bankTxn = createBankTransaction();
|
|
663
670
|
const ynabTxn = createYNABTransaction({ cleared: 'uncleared' });
|
|
664
671
|
const suggestedMatch: TransactionMatch = {
|
|
665
|
-
|
|
666
|
-
|
|
672
|
+
bankTransaction: bankTxn,
|
|
673
|
+
ynabTransaction: ynabTxn,
|
|
667
674
|
confidence: 'medium',
|
|
668
|
-
|
|
669
|
-
|
|
675
|
+
confidenceScore: 75,
|
|
676
|
+
matchReason: 'Suggested',
|
|
670
677
|
};
|
|
671
678
|
|
|
672
679
|
const insights = [
|
|
@@ -967,11 +974,11 @@ describe('recommendationEngine', () => {
|
|
|
967
974
|
const bankTxn = createBankTransaction({ id: 'b1' });
|
|
968
975
|
const ynabTxn = createYNABTransaction({ id: 'y1', cleared: 'uncleared' });
|
|
969
976
|
const suggestedMatch: TransactionMatch = {
|
|
970
|
-
|
|
971
|
-
|
|
977
|
+
bankTransaction: createBankTransaction({ id: 'b2' }),
|
|
978
|
+
ynabTransaction: createYNABTransaction({ id: 'y2' }),
|
|
972
979
|
confidence: 'medium',
|
|
973
|
-
|
|
974
|
-
|
|
980
|
+
confidenceScore: 75,
|
|
981
|
+
matchReason: 'Suggested',
|
|
975
982
|
};
|
|
976
983
|
const insights = [
|
|
977
984
|
createInsight('near_match', 'warning'),
|
|
@@ -76,7 +76,7 @@ const createBankTransaction = (
|
|
|
76
76
|
): BankTransaction => ({
|
|
77
77
|
id,
|
|
78
78
|
date,
|
|
79
|
-
amount,
|
|
79
|
+
amount: Math.round(amount * 1000),
|
|
80
80
|
payee,
|
|
81
81
|
original_csv_row: 1,
|
|
82
82
|
});
|
|
@@ -278,12 +278,13 @@ describe('reportFormatter', () => {
|
|
|
278
278
|
const analysis = createTestAnalysis({
|
|
279
279
|
suggested_matches: [
|
|
280
280
|
{
|
|
281
|
-
|
|
281
|
+
bankTransaction: createBankTransaction('bank-1', -60.0, 'Amazon', '2025-10-20'),
|
|
282
|
+
ynabTransaction: undefined,
|
|
282
283
|
candidates: [],
|
|
283
284
|
confidence: 'medium',
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
285
|
+
confidenceScore: 75,
|
|
286
|
+
matchReason: 'amount_and_date_fuzzy_payee',
|
|
287
|
+
topConfidence: 75,
|
|
287
288
|
},
|
|
288
289
|
],
|
|
289
290
|
});
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { analyzeReconciliation } from '../../analyzer.js';
|
|
3
3
|
import type { TransactionDetail } from 'ynab';
|
|
4
|
-
import * as
|
|
4
|
+
import * as csvParser from '../../csvParser.js';
|
|
5
5
|
|
|
6
|
-
vi.mock('
|
|
7
|
-
|
|
8
|
-
readCSVFile: vi.fn(),
|
|
6
|
+
vi.mock('../../csvParser.js', () => ({
|
|
7
|
+
parseCSV: vi.fn(),
|
|
9
8
|
}));
|
|
10
9
|
|
|
11
10
|
describe('scenario: zero, negative, and large statements', () => {
|
|
@@ -14,16 +13,36 @@ describe('scenario: zero, negative, and large statements', () => {
|
|
|
14
13
|
});
|
|
15
14
|
|
|
16
15
|
it('handles zero and negative statement balances with mixed unmatched items', () => {
|
|
17
|
-
vi.mocked(
|
|
16
|
+
vi.mocked(csvParser.parseCSV).mockReturnValue({
|
|
18
17
|
transactions: [
|
|
19
|
-
{
|
|
20
|
-
|
|
18
|
+
{
|
|
19
|
+
id: 'b1',
|
|
20
|
+
date: '2025-11-01',
|
|
21
|
+
amount: 0,
|
|
22
|
+
payee: 'Zero Adjustment',
|
|
23
|
+
memo: '',
|
|
24
|
+
sourceRow: 2,
|
|
25
|
+
raw: { date: '2025-11-01', amount: '0', description: 'Zero Adjustment' },
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'b2',
|
|
29
|
+
date: '2025-11-02',
|
|
30
|
+
amount: 2500000,
|
|
31
|
+
payee: 'Interest',
|
|
32
|
+
memo: '',
|
|
33
|
+
sourceRow: 3,
|
|
34
|
+
raw: { date: '2025-11-02', amount: '2500.00', description: 'Interest' },
|
|
35
|
+
},
|
|
21
36
|
],
|
|
22
|
-
format_detected: 'standard',
|
|
23
|
-
delimiter: ',',
|
|
24
|
-
total_rows: 2,
|
|
25
|
-
valid_rows: 2,
|
|
26
37
|
errors: [],
|
|
38
|
+
warnings: [],
|
|
39
|
+
meta: {
|
|
40
|
+
detectedDelimiter: ',',
|
|
41
|
+
detectedColumns: ['Date', 'Description', 'Amount'],
|
|
42
|
+
totalRows: 2,
|
|
43
|
+
validRows: 2,
|
|
44
|
+
skippedRows: 0,
|
|
45
|
+
},
|
|
27
46
|
});
|
|
28
47
|
|
|
29
48
|
const ynabTxns: TransactionDetail[] = [
|