@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.
Files changed (207) hide show
  1. package/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +3 -0
  2. package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +3 -0
  3. package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +1 -0
  4. package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +5 -0
  5. package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +1569 -0
  6. package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +3 -0
  7. package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +2832 -0
  8. package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +2709 -0
  9. package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +2832 -0
  10. package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +2832 -0
  11. package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +2709 -0
  12. package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +1 -0
  13. package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +5217 -0
  14. package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +2594 -0
  15. package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +2594 -0
  16. package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +231 -0
  17. package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +2590 -0
  18. package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +5195 -0
  19. package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +286 -0
  20. package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +218 -0
  21. package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +180 -0
  22. package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +3 -0
  23. package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +1 -0
  24. package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +747 -0
  25. package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +1 -0
  26. package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +2594 -0
  27. package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +2594 -0
  28. package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +144 -0
  29. package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +416 -0
  30. package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +2590 -0
  31. package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +2590 -0
  32. package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +2590 -0
  33. package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +790 -0
  34. package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +766 -0
  35. package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +790 -0
  36. package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +2594 -0
  37. package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +1000 -0
  38. package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +3489 -0
  39. package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +766 -0
  40. package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +2594 -0
  41. package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +2456 -0
  42. package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +2594 -0
  43. package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +18 -0
  44. package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +48 -0
  45. package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +1 -0
  46. package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +1 -0
  47. package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +1 -0
  48. package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +1 -0
  49. package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +1271 -0
  50. package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +1570 -0
  51. package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +2590 -0
  52. package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +3 -0
  53. package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +3299 -0
  54. package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +3299 -0
  55. package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +1882 -0
  56. package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +2594 -0
  57. package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +1 -0
  58. package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +170 -0
  59. package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +1 -0
  60. package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +3 -0
  61. package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +1 -0
  62. package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +1 -0
  63. package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +3 -0
  64. package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +1 -0
  65. package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +1 -0
  66. package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +5 -0
  67. package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +1 -0
  68. package/.github/workflows/publish.yml +3 -3
  69. package/.github/workflows/release.yml +4 -0
  70. package/CHANGELOG.md +75 -0
  71. package/NUL +1 -0
  72. package/dist/bundle/index.cjs +65 -42
  73. package/dist/server/errorHandler.d.ts +2 -0
  74. package/dist/server/errorHandler.js +49 -5
  75. package/dist/tools/reconcileAdapter.js +10 -5
  76. package/dist/tools/reconciliation/analyzer.d.ts +4 -2
  77. package/dist/tools/reconciliation/analyzer.js +120 -404
  78. package/dist/tools/reconciliation/csvParser.d.ts +51 -0
  79. package/dist/tools/reconciliation/csvParser.js +413 -0
  80. package/dist/tools/reconciliation/executor.d.ts +8 -0
  81. package/dist/tools/reconciliation/executor.js +204 -58
  82. package/dist/tools/reconciliation/index.d.ts +7 -7
  83. package/dist/tools/reconciliation/index.js +115 -39
  84. package/dist/tools/reconciliation/matcher.d.ts +24 -3
  85. package/dist/tools/reconciliation/matcher.js +175 -133
  86. package/dist/tools/reconciliation/recommendationEngine.js +22 -18
  87. package/dist/tools/reconciliation/reportFormatter.js +9 -8
  88. package/dist/tools/reconciliation/signDetector.d.ts +2 -0
  89. package/dist/tools/reconciliation/signDetector.js +54 -0
  90. package/dist/tools/reconciliation/types.d.ts +20 -34
  91. package/dist/tools/reconciliation/types.js +1 -7
  92. package/dist/tools/reconciliation/ynabAdapter.d.ts +4 -0
  93. package/dist/tools/reconciliation/ynabAdapter.js +15 -0
  94. package/dist/types/reconciliation.d.ts +24 -0
  95. package/dist/types/reconciliation.js +1 -0
  96. package/docs/guides/ARCHITECTURE.md +12 -129
  97. package/docs/plans/2025-11-21-v014-hardening.md +153 -0
  98. package/docs/plans/reconciliation-v2-redesign.md +1571 -0
  99. package/package.json +6 -1
  100. package/scripts/test-recommendations.ts +1 -1
  101. package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +129 -0
  102. package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +53 -0
  103. package/src/server/errorHandler.ts +52 -5
  104. package/src/tools/reconcileAdapter.ts +10 -5
  105. package/src/tools/reconciliation/__tests__/adapter.test.ts +28 -22
  106. package/src/tools/reconciliation/__tests__/analyzer.test.ts +114 -180
  107. package/src/tools/reconciliation/__tests__/csvParser.test.ts +87 -0
  108. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +1 -1
  109. package/src/tools/reconciliation/__tests__/executor.test.ts +88 -61
  110. package/src/tools/reconciliation/__tests__/matcher.test.ts +68 -54
  111. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +37 -30
  112. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +6 -5
  113. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +30 -11
  114. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +50 -15
  115. package/src/tools/reconciliation/__tests__/signDetector.test.ts +211 -0
  116. package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +61 -0
  117. package/src/tools/reconciliation/analyzer.ts +174 -545
  118. package/src/tools/reconciliation/csvParser.ts +617 -0
  119. package/src/tools/reconciliation/executor.ts +249 -66
  120. package/src/tools/reconciliation/index.ts +141 -48
  121. package/src/tools/reconciliation/matcher.ts +234 -214
  122. package/src/tools/reconciliation/recommendationEngine.ts +23 -19
  123. package/src/tools/reconciliation/reportFormatter.ts +16 -11
  124. package/src/tools/reconciliation/signDetector.ts +117 -0
  125. package/src/tools/reconciliation/types.ts +39 -61
  126. package/src/tools/reconciliation/ynabAdapter.ts +33 -0
  127. package/src/types/reconciliation.ts +49 -0
  128. package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +3662 -0
  129. package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +0 -8
  130. package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +0 -3
  131. package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +0 -3
  132. package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +0 -1
  133. package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +0 -12
  134. package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +0 -1
  135. package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +0 -1
  136. package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +0 -5
  137. package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +0 -1453
  138. package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +0 -8
  139. package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +0 -9
  140. package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +0 -1
  141. package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +0 -5
  142. package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +0 -81
  143. package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +0 -610
  144. package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +0 -1
  145. package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +0 -1
  146. package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +0 -1
  147. package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +0 -3
  148. package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +0 -8
  149. package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +0 -5
  150. package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +0 -1
  151. package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +0 -9
  152. package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +0 -1
  153. package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +0 -13
  154. package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +0 -14
  155. package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +0 -5
  156. package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +0 -5
  157. package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +0 -5
  158. package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +0 -3
  159. package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +0 -1
  160. package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +0 -1
  161. package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +0 -5
  162. package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +0 -5
  163. package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +0 -8
  164. package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +0 -8
  165. package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +0 -5
  166. package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +0 -1
  167. package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +0 -1
  168. package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +0 -1
  169. package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +0 -1
  170. package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +0 -1
  171. package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +0 -5
  172. package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +0 -1
  173. package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +0 -1
  174. package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +0 -1
  175. package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +0 -3
  176. package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +0 -8
  177. package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +0 -1
  178. package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +0 -1
  179. package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +0 -8
  180. package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +0 -3
  181. package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +0 -5
  182. package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +0 -1
  183. package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +0 -1
  184. package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +0 -5
  185. package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +0 -1
  186. package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +0 -14
  187. package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +0 -1
  188. package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +0 -5
  189. package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +0 -5
  190. package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +0 -11
  191. package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +0 -1
  192. package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +0 -8
  193. package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +0 -1
  194. package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +0 -3
  195. package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +0 -3
  196. package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +0 -8
  197. package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +0 -1
  198. package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +0 -5
  199. package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +0 -1
  200. package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +0 -1
  201. package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +0 -1
  202. package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +0 -1
  203. package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +0 -3
  204. package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +0 -8
  205. package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +0 -1
  206. package/.gemini/settings.json +0 -8
  207. 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
- suggestionThreshold: 60,
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.confidence_score).toBeGreaterThanOrEqual(90);
45
- expect(match.ynab_transaction).toEqual(ynabTxns[0]);
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.0,
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.confidence_score).toBeGreaterThanOrEqual(90);
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.0,
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
- payee_name: 'Restaurant',
90
- category_name: 'Dining',
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.confidence_score).toBeGreaterThanOrEqual(90);
107
+ expect(match.confidenceScore).toBeGreaterThanOrEqual(90);
100
108
  });
101
- });
102
109
 
103
- describe('medium confidence matches (60-89%)', () => {
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('medium');
128
- expect(match.confidence_score).toBeGreaterThanOrEqual(60);
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: -50.0,
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: -45.23,
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.confidence_score).toBe(0);
198
- expect(match.action_hint).toBe('add_to_ynab');
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: 50.0, // Positive (refund)
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.ynab_transaction).toBeUndefined();
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: -50.0,
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.ynab_transaction?.id).toBe('y2');
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: -50.0,
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.ynab_transaction?.id).toBe('y2');
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: -45.23,
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.ynab_transaction).toBeDefined();
335
+ expect(match.bestMatch?.ynabTransaction).toBeDefined();
329
336
  });
330
337
 
331
338
  it('should not match outside amount tolerance', () => {
332
- config.amountToleranceCents = 1;
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: -45.0,
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: -50.0,
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.ynab_transaction).toBeUndefined();
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: -45.23,
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: -100.0,
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].bank_transaction.id).toBe('b1');
435
- expect(matches[1].bank_transaction.id).toBe('b2');
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: -50.0,
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: -50.0,
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].ynab_transaction?.id).toBe('y1');
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: -45.23,
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: -15.99,
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].action_hint).toBe('add_to_ynab');
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
- suggestionThreshold: 50,
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: -50.0,
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.recommendation).toContain('not in YNAB');
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: -50.0,
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: -0.01,
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: -10000.0,
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
- const createBankTransaction = (overrides?: Partial<BankTransaction>): BankTransaction => ({
75
- id: 'bank-txn-1',
76
- date: '2024-01-15',
77
- amount: -50.0,
78
- payee: 'Test Store',
79
- memo: 'Test memo',
80
- original_csv_row: 1,
81
- ...overrides,
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
- bank_transaction: bankTxn,
410
- ynab_transaction: ynabTxn,
416
+ bankTransaction: bankTxn,
417
+ ynabTransaction: ynabTxn,
411
418
  confidence: 'medium',
412
- confidence_score: 75,
413
- match_reason: 'Fuzzy payee match',
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
- bank_transaction: bankTxn,
445
+ bankTransaction: bankTxn,
439
446
  confidence: 'none',
440
- confidence_score: 0,
441
- match_reason: 'No match found',
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
- bank_transaction: bankTxn,
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
- confidence_score: 60,
492
- match_reason: 'combination_match',
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
- bank_transaction: bankTxn,
585
+ bankTransaction: bankTxn,
579
586
  confidence: 'none',
580
- confidence_score: 0,
581
- match_reason: 'No match',
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
- bank_transaction: bankTxn,
666
- ynab_transaction: ynabTxn,
672
+ bankTransaction: bankTxn,
673
+ ynabTransaction: ynabTxn,
667
674
  confidence: 'medium',
668
- confidence_score: 75,
669
- match_reason: 'Suggested',
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
- bank_transaction: createBankTransaction({ id: 'b2' }),
971
- ynab_transaction: createYNABTransaction({ id: 'y2' }),
977
+ bankTransaction: createBankTransaction({ id: 'b2' }),
978
+ ynabTransaction: createYNABTransaction({ id: 'y2' }),
972
979
  confidence: 'medium',
973
- confidence_score: 75,
974
- match_reason: 'Suggested',
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
- bank_transaction: createBankTransaction('bank-1', -60.0, 'Amazon', '2025-10-20'),
281
+ bankTransaction: createBankTransaction('bank-1', -60.0, 'Amazon', '2025-10-20'),
282
+ ynabTransaction: undefined,
282
283
  candidates: [],
283
284
  confidence: 'medium',
284
- confidence_score: 75,
285
- match_reason: 'amount_and_date_fuzzy_payee',
286
- top_confidence: 75,
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 parser from '../../../compareTransactions/parser.js';
4
+ import * as csvParser from '../../csvParser.js';
5
5
 
6
- vi.mock('../../../compareTransactions/parser.js', () => ({
7
- parseBankCSV: vi.fn(),
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(parser.parseBankCSV).mockReturnValue({
16
+ vi.mocked(csvParser.parseCSV).mockReturnValue({
18
17
  transactions: [
19
- { date: '2025-11-01', amount: 0, payee: 'Zero Adjustment', memo: '' },
20
- { date: '2025-11-02', amount: 2500, payee: 'Interest', memo: '' },
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[] = [