@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.
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 +8 -2
  77. package/dist/tools/reconciliation/analyzer.js +127 -409
  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 +191 -550
  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 +148 -54
  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
@@ -2,7 +2,10 @@ import { describe, it, expect, vi } from 'vitest';
2
2
  import type * as ynab from 'ynab';
3
3
  import type { ReconciliationAnalysis } from '../types.js';
4
4
  import { executeReconciliation, type AccountSnapshot } from '../executor.js';
5
+ import type { NormalizedYnabError } from '../executor.js';
6
+ import { normalizeYnabError, shouldPropagateYnabError } from '../executor.js';
5
7
  import type { ReconcileAccountRequest } from '../index.js';
8
+ import { toMoneyValue } from '../../../utils/money.js';
6
9
 
7
10
  const buildAnalysis = (): ReconciliationAnalysis => ({
8
11
  success: true,
@@ -15,34 +18,35 @@ const buildAnalysis = (): ReconciliationAnalysis => ({
15
18
  suggested_matches: 0,
16
19
  unmatched_bank: 1,
17
20
  unmatched_ynab: 1,
18
- current_cleared_balance: -899.02,
19
- target_statement_balance: -921.24,
20
- discrepancy: 22.22,
21
+ current_cleared_balance: toMoneyValue(-899020, 'USD'),
22
+ target_statement_balance: toMoneyValue(-921240, 'USD'),
23
+ discrepancy: toMoneyValue(22220, 'USD'),
21
24
  discrepancy_explanation: 'Need to add 1 missing transaction',
22
25
  },
23
26
  auto_matches: [
24
27
  {
25
- bank_transaction: {
28
+ bankTransaction: {
26
29
  id: 'bank-1',
27
30
  date: '2025-10-15',
28
- amount: -45.23,
31
+ amount: -45230,
29
32
  payee: 'Shell Gas',
30
- original_csv_row: 2,
33
+ sourceRow: 2,
34
+ raw: { date: '2025-10-15', amount: '-45.23', description: 'Shell Gas' },
31
35
  },
32
- ynab_transaction: {
36
+ ynabTransaction: {
33
37
  id: 'ynab-1',
34
38
  date: '2025-10-14',
35
39
  amount: -45230,
36
- payee_name: 'Shell',
37
- category_name: 'Auto',
40
+ payee: 'Shell',
41
+ categoryName: 'Auto',
38
42
  cleared: 'uncleared',
39
43
  approved: true,
40
44
  memo: null,
41
45
  },
42
46
  candidates: [],
43
47
  confidence: 'high',
44
- confidence_score: 97,
45
- match_reason: 'exact_amount_and_date',
48
+ confidenceScore: 97,
49
+ matchReason: 'exact_amount_and_date',
46
50
  },
47
51
  ],
48
52
  suggested_matches: [],
@@ -50,9 +54,10 @@ const buildAnalysis = (): ReconciliationAnalysis => ({
50
54
  {
51
55
  id: 'bank-2',
52
56
  date: '2025-10-25',
53
- amount: 22.22,
57
+ amount: 22220,
54
58
  payee: 'EvoCarShare',
55
- original_csv_row: 7,
59
+ sourceRow: 7,
60
+ raw: { date: '2025-10-25', amount: '22.22', description: 'EvoCarShare' },
56
61
  },
57
62
  ],
58
63
  unmatched_ynab: [
@@ -60,19 +65,19 @@ const buildAnalysis = (): ReconciliationAnalysis => ({
60
65
  id: 'ynab-2',
61
66
  date: '2025-10-10',
62
67
  amount: -15000,
63
- payee_name: 'Coffee Shop',
64
- category_name: 'Dining',
68
+ payee: 'Coffee Shop',
69
+ categoryName: 'Dining',
65
70
  cleared: 'cleared',
66
71
  approved: true,
67
72
  memo: null,
68
73
  },
69
74
  ],
70
75
  balance_info: {
71
- current_cleared: -899.02,
72
- current_uncleared: -45.23,
73
- current_total: -944.25,
74
- target_statement: -921.24,
75
- discrepancy: 22.22,
76
+ current_cleared: toMoneyValue(-899020, 'USD'),
77
+ current_uncleared: toMoneyValue(-45230, 'USD'),
78
+ current_total: toMoneyValue(-944250, 'USD'),
79
+ target_statement: toMoneyValue(-921240, 'USD'),
80
+ discrepancy: toMoneyValue(22220, 'USD'),
76
81
  on_track: false,
77
82
  },
78
83
  next_steps: ['Review auto matches'],
@@ -87,7 +92,7 @@ const defaultAccountSnapshot: AccountSnapshot = {
87
92
 
88
93
  const buildBulkAnalysis = (
89
94
  count: number,
90
- amount = 10,
95
+ amount = 10000,
91
96
  statementMultiplier = count,
92
97
  ): ReconciliationAnalysis => {
93
98
  const analysis = buildAnalysis();
@@ -108,21 +113,26 @@ const buildBulkAnalysis = (
108
113
  amount,
109
114
  payee: `Bulk Payee ${index}`,
110
115
  memo: `Bulk memo ${index}`,
111
- original_csv_row: index + 1,
116
+ sourceRow: index + 1,
117
+ raw: {
118
+ date: date.toISOString().slice(0, 10),
119
+ amount: String(amount / 1000),
120
+ description: `Bulk Payee ${index}`,
121
+ },
112
122
  };
113
123
  });
114
124
  analysis.summary.unmatched_bank = analysis.unmatched_bank.length;
115
125
  const statementBalance = amount * statementMultiplier;
116
- analysis.summary.current_cleared_balance = 0;
117
- analysis.summary.target_statement_balance = statementBalance;
118
- analysis.summary.discrepancy = statementBalance;
126
+ analysis.summary.current_cleared_balance = toMoneyValue(0, 'USD');
127
+ analysis.summary.target_statement_balance = toMoneyValue(statementBalance, 'USD');
128
+ analysis.summary.discrepancy = toMoneyValue(statementBalance, 'USD');
119
129
  analysis.summary.discrepancy_explanation = 'Bulk test discrepancy';
120
130
  analysis.balance_info = {
121
- current_cleared: 0,
122
- current_uncleared: 0,
123
- current_total: 0,
124
- target_statement: statementBalance,
125
- discrepancy: statementBalance,
131
+ current_cleared: toMoneyValue(0, 'USD'),
132
+ current_uncleared: toMoneyValue(0, 'USD'),
133
+ current_total: toMoneyValue(0, 'USD'),
134
+ target_statement: toMoneyValue(statementBalance, 'USD'),
135
+ discrepancy: toMoneyValue(statementBalance, 'USD'),
126
136
  on_track: false,
127
137
  };
128
138
  analysis.next_steps = [];
@@ -131,13 +141,13 @@ const buildBulkAnalysis = (
131
141
  };
132
142
 
133
143
  const buildBulkParams = (
134
- statementBalance: number,
144
+ statementBalance: MoneyValue, // Expects MoneyValue now from buildBulkAnalysis
135
145
  overrides: Partial<ReconcileAccountRequest> = {},
136
146
  ): ReconcileAccountRequest => ({
137
147
  budget_id: 'budget-bulk',
138
148
  account_id: 'account-bulk',
139
149
  csv_data: 'Date,Payee,Amount',
140
- statement_balance: statementBalance,
150
+ statement_balance: statementBalance.value, // Use decimal value for params
141
151
  statement_date: '2025-10-31',
142
152
  date_tolerance_days: 1,
143
153
  amount_tolerance_cents: 1,
@@ -194,6 +204,20 @@ const createMockYnabAPI = (snapshot: AccountSnapshot = defaultAccountSnapshot) =
194
204
  };
195
205
  };
196
206
 
207
+ describe('error normalization helpers', () => {
208
+ it('normalizes YNAB SDK error objects with status and detail', () => {
209
+ const err = normalizeYnabError({ error: { id: '429', detail: 'Too many requests' } });
210
+ expect(err.status).toBe(429);
211
+ expect(err.message).toContain('Too many requests');
212
+ });
213
+
214
+ it('retains status from Error-like objects and propagates HTTP code decisions', () => {
215
+ const err = normalizeYnabError(Object.assign(new Error('Nope'), { status: 503 }));
216
+ expect(err.status).toBe(503);
217
+ expect(shouldPropagateYnabError(err as NormalizedYnabError)).toBe(true);
218
+ });
219
+ });
220
+
197
221
  describe('executeReconciliation (dry run)', () => {
198
222
  it('produces action plan without calling YNAB APIs when dry_run=true', async () => {
199
223
  const analysis = buildAnalysis();
@@ -235,7 +259,8 @@ describe('executeReconciliation (dry run)', () => {
235
259
  expect(result.summary.transactions_created).toBe(1);
236
260
  expect(result.summary.transactions_updated).toBe(2);
237
261
  expect(result.summary.dates_adjusted).toBe(1);
238
- expect(result.actions_taken).toHaveLength(3);
262
+ // 3 original actions + 2 diagnostic actions (diagnostic_step3_entry + diagnostic_unmatched_ynab)
263
+ expect(result.actions_taken).toHaveLength(5);
239
264
  expect(result.recommendations).toContain(
240
265
  'Dry run only — re-run with dry_run=false to apply these changes',
241
266
  );
@@ -330,68 +355,70 @@ describe('executeReconciliation (ordered halting)', () => {
330
355
  suggested_matches: 0,
331
356
  unmatched_bank: 0,
332
357
  unmatched_ynab: 0,
333
- current_cleared_balance: 90,
334
- target_statement_balance: 100,
335
- discrepancy: -10,
358
+ current_cleared_balance: toMoneyValue(90000, 'USD'),
359
+ target_statement_balance: toMoneyValue(100000, 'USD'),
360
+ discrepancy: toMoneyValue(-10000, 'USD'),
336
361
  discrepancy_explanation: 'Awaiting cleared transactions',
337
362
  },
338
363
  auto_matches: [
339
364
  {
340
- bank_transaction: {
365
+ bankTransaction: {
341
366
  id: 'bank-older',
342
367
  date: '2025-09-15',
343
- amount: 5,
368
+ amount: 5000,
344
369
  payee: 'Older',
345
- original_csv_row: 2,
370
+ sourceRow: 2,
371
+ raw: { date: '2025-09-15', amount: '5.00', description: 'Older' },
346
372
  },
347
- ynab_transaction: {
373
+ ynabTransaction: {
348
374
  id: 'ynab-older',
349
375
  date: '2025-09-14',
350
376
  amount: 5000,
351
- payee_name: 'Older',
352
- category_name: null,
377
+ payee: 'Older',
378
+ categoryName: null,
353
379
  cleared: 'uncleared',
354
380
  approved: true,
355
381
  memo: null,
356
382
  },
357
383
  candidates: [],
358
384
  confidence: 'high',
359
- confidence_score: 95,
360
- match_reason: 'Exact match',
385
+ confidenceScore: 95,
386
+ matchReason: 'Exact match',
361
387
  },
362
388
  {
363
- bank_transaction: {
389
+ bankTransaction: {
364
390
  id: 'bank-newer',
365
391
  date: '2025-10-25',
366
- amount: 10,
392
+ amount: 10000,
367
393
  payee: 'Newer',
368
- original_csv_row: 1,
394
+ sourceRow: 1,
395
+ raw: { date: '2025-10-25', amount: '10.00', description: 'Newer' },
369
396
  },
370
- ynab_transaction: {
397
+ ynabTransaction: {
371
398
  id: 'ynab-newer',
372
399
  date: '2025-10-24',
373
400
  amount: 10000,
374
- payee_name: 'Newer',
375
- category_name: null,
401
+ payee: 'Newer',
402
+ categoryName: null,
376
403
  cleared: 'uncleared',
377
404
  approved: true,
378
405
  memo: null,
379
406
  },
380
407
  candidates: [],
381
408
  confidence: 'high',
382
- confidence_score: 99,
383
- match_reason: 'Exact match',
409
+ confidenceScore: 99,
410
+ matchReason: 'Exact match',
384
411
  },
385
412
  ],
386
413
  suggested_matches: [],
387
414
  unmatched_bank: [],
388
415
  unmatched_ynab: [],
389
416
  balance_info: {
390
- current_cleared: 90,
391
- current_uncleared: 0,
392
- current_total: 90,
393
- target_statement: 100,
394
- discrepancy: -10,
417
+ current_cleared: toMoneyValue(90000, 'USD'),
418
+ current_uncleared: toMoneyValue(0, 'USD'),
419
+ current_total: toMoneyValue(90000, 'USD'),
420
+ target_statement: toMoneyValue(100000, 'USD'),
421
+ discrepancy: toMoneyValue(-10000, 'USD'),
395
422
  on_track: false,
396
423
  },
397
424
  next_steps: [],
@@ -527,7 +554,7 @@ describe('executeReconciliation - bulk create mode', () => {
527
554
  });
528
555
 
529
556
  it('falls back to sequential creation when bulk request fails', async () => {
530
- const analysis = buildBulkAnalysis(3, 8);
557
+ const analysis = buildBulkAnalysis(3, 1000);
531
558
  const params = buildBulkParams(analysis.summary.target_statement_balance);
532
559
  const initialAccount = { ...defaultAccountSnapshot };
533
560
  const { api, mocks } = createMockYnabAPI(initialAccount);
@@ -598,7 +625,7 @@ describe('executeReconciliation - bulk create mode', () => {
598
625
  });
599
626
 
600
627
  it('splits large batches into 100-transaction chunks', async () => {
601
- const analysis = buildBulkAnalysis(150, 5);
628
+ const analysis = buildBulkAnalysis(150, 1000);
602
629
  const params = buildBulkParams(analysis.summary.target_statement_balance);
603
630
  const initialAccount = { ...defaultAccountSnapshot };
604
631
  const { api, mocks } = createMockYnabAPI(initialAccount);
@@ -641,7 +668,7 @@ describe('executeReconciliation - bulk create mode', () => {
641
668
  });
642
669
 
643
670
  it('throws on fatal sequential creation errors surfaced as objects', async () => {
644
- const analysis = buildBulkAnalysis(1, 5);
671
+ const analysis = buildBulkAnalysis(1, 1000);
645
672
  const params = buildBulkParams(analysis.summary.target_statement_balance);
646
673
  const initialAccount = { ...defaultAccountSnapshot };
647
674
  const { api, mocks } = createMockYnabAPI(initialAccount);
@@ -711,7 +738,7 @@ describe('executeReconciliation - bulk create mode', () => {
711
738
  });
712
739
 
713
740
  it('honors halting logic when balance aligns mid-batch', async () => {
714
- const analysis = buildBulkAnalysis(10, 10, 5);
741
+ const analysis = buildBulkAnalysis(10, 1000, 5);
715
742
  const params = buildBulkParams(analysis.summary.target_statement_balance);
716
743
  const initialAccount = { ...defaultAccountSnapshot };
717
744
  const { api, mocks } = createMockYnabAPI(initialAccount);