sea-dev 1.0.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 (784) hide show
  1. package/.claude/tasks/README.md +89 -0
  2. package/.cursor/rules/commits.mdc +31 -0
  3. package/.cursor/rules/general.mdc +84 -0
  4. package/.github/workflows/ci-cd.yml +141 -0
  5. package/CLAUDE.md +337 -0
  6. package/README.md +129 -0
  7. package/apps/api/.prettierignore +6 -0
  8. package/apps/api/.prettierrc.js +3 -0
  9. package/apps/api/dotenvx-safe.sh +11 -0
  10. package/apps/api/eslint.config.mjs +3 -0
  11. package/apps/api/package.json +58 -0
  12. package/apps/api/src/clients/posthog.ts +25 -0
  13. package/apps/api/src/dal/submission.ts +59 -0
  14. package/apps/api/src/errors.ts +55 -0
  15. package/apps/api/src/index.ts +21 -0
  16. package/apps/api/src/lib/channel.ts +28 -0
  17. package/apps/api/src/lib/config.ts +9 -0
  18. package/apps/api/src/lib/fmt.test.ts +9 -0
  19. package/apps/api/src/lib/fmt.ts +62 -0
  20. package/apps/api/src/lib/invariant.ts +23 -0
  21. package/apps/api/src/middleware/auth.ts +66 -0
  22. package/apps/api/src/routes/index.ts +20 -0
  23. package/apps/api/src/routes/v2/chat/handlers.ts +693 -0
  24. package/apps/api/src/routes/v2/chat/index.ts +257 -0
  25. package/apps/api/src/routes/v2/chat/schemas.ts +43 -0
  26. package/apps/api/src/routes/v2/deals/handlers.ts +64 -0
  27. package/apps/api/src/routes/v2/deals/index.ts +88 -0
  28. package/apps/api/src/routes/v2/deals/schemas.ts +38 -0
  29. package/apps/api/src/routes/v2/forms/handlers.ts +415 -0
  30. package/apps/api/src/routes/v2/forms/index.ts +382 -0
  31. package/apps/api/src/routes/v2/forms/schemas.ts +243 -0
  32. package/apps/api/src/routes/v2/index.ts +19 -0
  33. package/apps/api/src/routes/v2/pipelines/handlers.ts +261 -0
  34. package/apps/api/src/routes/v2/pipelines/index.ts +224 -0
  35. package/apps/api/src/routes/v2/pipelines/schemas.ts +173 -0
  36. package/apps/api/src/routes/v2/submissions/handlers.ts +555 -0
  37. package/apps/api/src/routes/v2/submissions/index.ts +366 -0
  38. package/apps/api/src/routes/v2/submissions/schemas.ts +233 -0
  39. package/apps/api/src/routes/v2/workflows/handlers.ts +81 -0
  40. package/apps/api/src/routes/v2/workflows/index.ts +88 -0
  41. package/apps/api/src/routes/v2/workflows/schemas.ts +40 -0
  42. package/apps/api/src/server.ts +146 -0
  43. package/apps/api/src/static/favicon.ico +0 -0
  44. package/apps/api/src/types/api.ts +14 -0
  45. package/apps/api/src/types/result.ts +3 -0
  46. package/apps/api/tsconfig.json +22 -0
  47. package/apps/api/vite.config.ts +28 -0
  48. package/apps/api/vitest.config.ts +14 -0
  49. package/apps/conversion-worker/Dockerfile +59 -0
  50. package/apps/conversion-worker/package.json +31 -0
  51. package/apps/conversion-worker/src/lib/config.ts +7 -0
  52. package/apps/conversion-worker/src/main.ts +22 -0
  53. package/apps/conversion-worker/src/workflows/convert-pptx.ts +116 -0
  54. package/apps/conversion-worker/tsconfig.json +27 -0
  55. package/apps/conversion-worker/vite.config.ts +33 -0
  56. package/apps/main/.prettierignore +6 -0
  57. package/apps/main/.prettierrc.js +3 -0
  58. package/apps/main/CLAUDE.md +245 -0
  59. package/apps/main/Procfile +1 -0
  60. package/apps/main/README.md +193 -0
  61. package/apps/main/db-tests.jsonl +116 -0
  62. package/apps/main/dotenvx-safe.sh +11 -0
  63. package/apps/main/drizzle/meta/_journal.json +1 -0
  64. package/apps/main/drizzle.config.ts +25 -0
  65. package/apps/main/eslint.config.mjs +3 -0
  66. package/apps/main/generate-routes.mjs +5 -0
  67. package/apps/main/package.json +131 -0
  68. package/apps/main/playwright.config.ts +23 -0
  69. package/apps/main/postcss.config.ts +5 -0
  70. package/apps/main/public/bg-dark.svg +10 -0
  71. package/apps/main/public/bg.svg +10 -0
  72. package/apps/main/public/favicon.ico +0 -0
  73. package/apps/main/run.sh +146 -0
  74. package/apps/main/scripts/browser.ts +14 -0
  75. package/apps/main/scripts/db-test-cov.ts +277 -0
  76. package/apps/main/scripts/login.ts +78 -0
  77. package/apps/main/scripts/repl.ts +61 -0
  78. package/apps/main/src/_foo.ts +31 -0
  79. package/apps/main/src/_tests/db.test.ts +19 -0
  80. package/apps/main/src/_tests/mock-db.ts +60 -0
  81. package/apps/main/src/client.tsx +13 -0
  82. package/apps/main/src/clients/loops.ts +13 -0
  83. package/apps/main/src/clients/polar.ts +12 -0
  84. package/apps/main/src/clients/posthog.ts +12 -0
  85. package/apps/main/src/components/chat/chat-context.tsx +99 -0
  86. package/apps/main/src/components/chat/chat-messages.tsx +184 -0
  87. package/apps/main/src/components/chat/chat-status.tsx +140 -0
  88. package/apps/main/src/components/chat/chat.tsx +458 -0
  89. package/apps/main/src/components/chat/citation-modal.tsx +54 -0
  90. package/apps/main/src/components/cta.tsx +21 -0
  91. package/apps/main/src/components/data-display/derived.tsx +40 -0
  92. package/apps/main/src/components/data-display/group-single.tsx +57 -0
  93. package/apps/main/src/components/data-display/group-table.tsx +165 -0
  94. package/apps/main/src/components/data-display/group-wrapper.tsx +54 -0
  95. package/apps/main/src/components/data-display/item.tsx +678 -0
  96. package/apps/main/src/components/error.tsx +45 -0
  97. package/apps/main/src/components/forms/error.tsx +22 -0
  98. package/apps/main/src/components/grid.tsx +7 -0
  99. package/apps/main/src/components/header/container.tsx +73 -0
  100. package/apps/main/src/components/header/header-bar.tsx +102 -0
  101. package/apps/main/src/components/modals/copy-display.tsx +37 -0
  102. package/apps/main/src/components/modals/copy-form.tsx +152 -0
  103. package/apps/main/src/components/modals/duplicate-workflow.tsx +89 -0
  104. package/apps/main/src/components/modals/field-correction.tsx +323 -0
  105. package/apps/main/src/components/modals/form-viewer.tsx +126 -0
  106. package/apps/main/src/components/modals/modals.tsx +44 -0
  107. package/apps/main/src/components/modals/new-deal.tsx +78 -0
  108. package/apps/main/src/components/modals/new-form.tsx +133 -0
  109. package/apps/main/src/components/modals/new-pipeline.tsx +70 -0
  110. package/apps/main/src/components/modals/new-submission.tsx +321 -0
  111. package/apps/main/src/components/modals/new-workflow.tsx +342 -0
  112. package/apps/main/src/components/modals/transformation-sources-modal.tsx +157 -0
  113. package/apps/main/src/components/modals/view-report.tsx +193 -0
  114. package/apps/main/src/components/not-found.tsx +14 -0
  115. package/apps/main/src/components/search/search-bar.tsx +178 -0
  116. package/apps/main/src/components/sheet-selector.tsx +135 -0
  117. package/apps/main/src/components/side-panel/doc-list.tsx +480 -0
  118. package/apps/main/src/components/sidebar/admin-sidebar.tsx +75 -0
  119. package/apps/main/src/components/sidebar/app-sidebar.tsx +417 -0
  120. package/apps/main/src/components/sidebar/model-select.tsx +134 -0
  121. package/apps/main/src/components/sidebar/settings-sidebar.tsx +132 -0
  122. package/apps/main/src/components/sidebar/sidebar-right.tsx +22 -0
  123. package/apps/main/src/components/sidebar/stop-impersonate.tsx +21 -0
  124. package/apps/main/src/components/svg/loading.tsx +33 -0
  125. package/apps/main/src/components/theme-selector.tsx +43 -0
  126. package/apps/main/src/components/unsaved-badge.tsx +19 -0
  127. package/apps/main/src/components/upload/file-upload.tsx +354 -0
  128. package/apps/main/src/fns/submission-groups.ts +28 -0
  129. package/apps/main/src/fns/submission-items.ts +11 -0
  130. package/apps/main/src/global-middleware.ts +16 -0
  131. package/apps/main/src/hooks/use-update-state.ts +18 -0
  132. package/apps/main/src/lib/auth-client.ts +16 -0
  133. package/apps/main/src/lib/auth.test.ts +359 -0
  134. package/apps/main/src/lib/auth.ts +144 -0
  135. package/apps/main/src/lib/billing.ts +23 -0
  136. package/apps/main/src/lib/config-iso.ts +76 -0
  137. package/apps/main/src/lib/config.ts +61 -0
  138. package/apps/main/src/lib/excel.ts +16 -0
  139. package/apps/main/src/lib/feedback-cache.ts +70 -0
  140. package/apps/main/src/lib/logger.ts +44 -0
  141. package/apps/main/src/lib/models.ts +22 -0
  142. package/apps/main/src/lib/not-found.ts +17 -0
  143. package/apps/main/src/lib/pdf.ts +16 -0
  144. package/apps/main/src/lib/tabularize.ts +54 -0
  145. package/apps/main/src/lib/utils.ts +10 -0
  146. package/apps/main/src/lib/zfd.ts +217 -0
  147. package/apps/main/src/middleware.ts +55 -0
  148. package/apps/main/src/routeTree.gen.ts +1255 -0
  149. package/apps/main/src/router.tsx +24 -0
  150. package/apps/main/src/routes/__root.tsx +92 -0
  151. package/apps/main/src/routes/_authed/_app/(dashboard)/index.tsx +227 -0
  152. package/apps/main/src/routes/_authed/_app/agents/$agentId/config.tsx +224 -0
  153. package/apps/main/src/routes/_authed/_app/agents/$agentId/index.tsx +206 -0
  154. package/apps/main/src/routes/_authed/_app/agents/-components/agent-actions-menu.tsx +94 -0
  155. package/apps/main/src/routes/_authed/_app/agents/-components/agent-artifacts.tsx +153 -0
  156. package/apps/main/src/routes/_authed/_app/agents/-components/agent-chat.tsx +220 -0
  157. package/apps/main/src/routes/_authed/_app/agents/-components/agent-history-menu.tsx +81 -0
  158. package/apps/main/src/routes/_authed/_app/agents/-components/agent-model-select.tsx +84 -0
  159. package/apps/main/src/routes/_authed/_app/agents/-components/agent-relevant-items.tsx +226 -0
  160. package/apps/main/src/routes/_authed/_app/agents/-components/agent-upload-button.tsx +298 -0
  161. package/apps/main/src/routes/_authed/_app/agents/-components/context-modal.tsx +187 -0
  162. package/apps/main/src/routes/_authed/_app/agents/-fns.ts +560 -0
  163. package/apps/main/src/routes/_authed/_app/agents/index.tsx +65 -0
  164. package/apps/main/src/routes/_authed/_app/deals/$dealId/$subId/-components/citation-tree.tsx +268 -0
  165. package/apps/main/src/routes/_authed/_app/deals/$dealId/$subId.tsx +655 -0
  166. package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/doc-loading.tsx +37 -0
  167. package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/share-link.tsx +42 -0
  168. package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/submission-card.tsx +89 -0
  169. package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/submission-filter.tsx +193 -0
  170. package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/submissions.tsx +36 -0
  171. package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/summary.tsx +82 -0
  172. package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/upload-doc.tsx +120 -0
  173. package/apps/main/src/routes/_authed/_app/deals/$dealId/-fns.ts +653 -0
  174. package/apps/main/src/routes/_authed/_app/deals/$dealId/index.tsx +259 -0
  175. package/apps/main/src/routes/_authed/_app/deals/$dealId/route.tsx +29 -0
  176. package/apps/main/src/routes/_authed/_app/deals/index.tsx +104 -0
  177. package/apps/main/src/routes/_authed/_app/feedback/index.tsx +639 -0
  178. package/apps/main/src/routes/_authed/_app/feedback/insights.tsx +250 -0
  179. package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/blockers-panel.tsx +260 -0
  180. package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/manual-input-panel.tsx +301 -0
  181. package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/submission-selector-modal.tsx +143 -0
  182. package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/upload-doc.tsx +120 -0
  183. package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/index.tsx +1485 -0
  184. package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/-components/dag-view.tsx +296 -0
  185. package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/-components/step-config-modal.tsx +634 -0
  186. package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/index.tsx +911 -0
  187. package/apps/main/src/routes/_authed/_app/pipelines/-fns.ts +510 -0
  188. package/apps/main/src/routes/_authed/_app/pipelines/index.tsx +103 -0
  189. package/apps/main/src/routes/_authed/_app/reports/$reportId.tsx +397 -0
  190. package/apps/main/src/routes/_authed/_app/reports/-fns.ts +11 -0
  191. package/apps/main/src/routes/_authed/_app/reports/index.tsx +22 -0
  192. package/apps/main/src/routes/_authed/_app/route.tsx +48 -0
  193. package/apps/main/src/routes/_authed/_app/submissions/-columns.tsx +161 -0
  194. package/apps/main/src/routes/_authed/_app/submissions/-fns.ts +128 -0
  195. package/apps/main/src/routes/_authed/_app/submissions/index.tsx +190 -0
  196. package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/$formId.tsx +542 -0
  197. package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/-components/derived.tsx +154 -0
  198. package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/-components/field.tsx +369 -0
  199. package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/-components/group.tsx +475 -0
  200. package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/index.tsx +263 -0
  201. package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/route.tsx +33 -0
  202. package/apps/main/src/routes/_authed/_app/workflows/-components/form-card.tsx +315 -0
  203. package/apps/main/src/routes/_authed/_app/workflows/index.tsx +86 -0
  204. package/apps/main/src/routes/_authed/admin/index.tsx +12 -0
  205. package/apps/main/src/routes/_authed/admin/route.tsx +42 -0
  206. package/apps/main/src/routes/_authed/admin/users/-columns.tsx +124 -0
  207. package/apps/main/src/routes/_authed/admin/users/-fns.ts +30 -0
  208. package/apps/main/src/routes/_authed/admin/users/index.tsx +29 -0
  209. package/apps/main/src/routes/_authed/catchNotFound.tsx +114 -0
  210. package/apps/main/src/routes/_authed/redirects/forms.$id.tsx +29 -0
  211. package/apps/main/src/routes/_authed/redirects/submissions.$id.tsx +27 -0
  212. package/apps/main/src/routes/_authed/redirects/workflows.$id.tsx +27 -0
  213. package/apps/main/src/routes/_authed/route.tsx +51 -0
  214. package/apps/main/src/routes/_authed/settings/-components/new-api-key.tsx +85 -0
  215. package/apps/main/src/routes/_authed/settings/-components/new-invite.tsx +100 -0
  216. package/apps/main/src/routes/_authed/settings/analytics.tsx +1710 -0
  217. package/apps/main/src/routes/_authed/settings/billing/-components/price-table.tsx +129 -0
  218. package/apps/main/src/routes/_authed/settings/billing/-fns.ts +76 -0
  219. package/apps/main/src/routes/_authed/settings/billing/index.tsx +119 -0
  220. package/apps/main/src/routes/_authed/settings/embed.tsx +337 -0
  221. package/apps/main/src/routes/_authed/settings/index.tsx +12 -0
  222. package/apps/main/src/routes/_authed/settings/keys.tsx +157 -0
  223. package/apps/main/src/routes/_authed/settings/members.tsx +276 -0
  224. package/apps/main/src/routes/_authed/settings/route.tsx +22 -0
  225. package/apps/main/src/routes/_authed/settings/user.tsx +87 -0
  226. package/apps/main/src/routes/_authed/settings/workspace.tsx +206 -0
  227. package/apps/main/src/routes/_public/-components/sign-in-up.tsx +96 -0
  228. package/apps/main/src/routes/_public/embedded.tsx +57 -0
  229. package/apps/main/src/routes/_public/invite.$inviteId.tsx +143 -0
  230. package/apps/main/src/routes/_public/no-access.tsx +38 -0
  231. package/apps/main/src/routes/_public/no-invite.tsx +39 -0
  232. package/apps/main/src/routes/_public/otp.tsx +103 -0
  233. package/apps/main/src/routes/_public/route.tsx +15 -0
  234. package/apps/main/src/routes/_public/sign-in.tsx +111 -0
  235. package/apps/main/src/routes/_public/sign-up.tsx +114 -0
  236. package/apps/main/src/routes/api/auth/$.ts +11 -0
  237. package/apps/main/src/routes/api/billing/paid.ts +42 -0
  238. package/apps/main/src/routes/api/billing/webhooks.ts +70 -0
  239. package/apps/main/src/routes/api/chat/agent.ts +40 -0
  240. package/apps/main/src/routes/api/chat/key.ts +42 -0
  241. package/apps/main/src/routes/api/chat/member.ts +35 -0
  242. package/apps/main/src/routes/api/test/index.ts +19 -0
  243. package/apps/main/src/server.tsx +6 -0
  244. package/apps/main/src/styles/app.css +23 -0
  245. package/apps/main/src/vite-env.d.ts +7 -0
  246. package/apps/main/test.http +6 -0
  247. package/apps/main/tsconfig.json +17 -0
  248. package/apps/main/vite.config.ts +24 -0
  249. package/apps/main/vitest.config.js +17 -0
  250. package/apps/mcp/README.md +171 -0
  251. package/apps/mcp/eslint.config.mjs +3 -0
  252. package/apps/mcp/package.json +37 -0
  253. package/apps/mcp/src/index.ts +414 -0
  254. package/apps/mcp/tsconfig.json +19 -0
  255. package/apps/mcp/vite.config.ts +22 -0
  256. package/apps/posthog-proxy/index.html +9 -0
  257. package/apps/workers/.prettierignore +7 -0
  258. package/apps/workers/.prettierrc.js +3 -0
  259. package/apps/workers/dotenvx-safe.sh +11 -0
  260. package/apps/workers/eslint.config.mjs +3 -0
  261. package/apps/workers/package.json +65 -0
  262. package/apps/workers/src/lib/config.ts +7 -0
  263. package/apps/workers/src/lib/messages.ts +0 -0
  264. package/apps/workers/src/lib/posthog.ts +25 -0
  265. package/apps/workers/src/main.ts +58 -0
  266. package/apps/workers/src/workflows/extraction.ts +866 -0
  267. package/apps/workers/src/workflows/index.ts +3 -0
  268. package/apps/workers/src/workflows/pipeline-dag.ts +210 -0
  269. package/apps/workers/src/workflows/pipeline-steps.ts +1393 -0
  270. package/apps/workers/tsconfig.json +16 -0
  271. package/apps/workers/vite.config.ts +35 -0
  272. package/docs/CHANGELOG.md +84 -0
  273. package/docs/agent-templates-and-runs.md +859 -0
  274. package/docs/aws-migration-plan.md +267 -0
  275. package/docs/impl-p0-form-builder-improvements.md +683 -0
  276. package/docs/on-prem-deployment-spec.docx +0 -0
  277. package/docs/on-prem-deployment-spec.md +378 -0
  278. package/docs/prd-form-builder-strategy.md +1120 -0
  279. package/docs/widget-ng-apf-packaging-spec.md +43 -0
  280. package/infra/k8s/charts/seadotdev/Chart.yaml +6 -0
  281. package/infra/k8s/charts/seadotdev/templates/_helpers.tpl +27 -0
  282. package/infra/k8s/charts/seadotdev/templates/api-v2.yaml +105 -0
  283. package/infra/k8s/charts/seadotdev/templates/external-secrets.yaml +83 -0
  284. package/infra/k8s/charts/seadotdev/templates/ingress.yaml +54 -0
  285. package/infra/k8s/charts/seadotdev/templates/main-app.yaml +104 -0
  286. package/infra/k8s/charts/seadotdev/templates/workers.yaml +182 -0
  287. package/infra/k8s/charts/seadotdev/values.yaml +143 -0
  288. package/infra/terraform/main.tf +399 -0
  289. package/libs/ai/.prettierignore +2 -0
  290. package/libs/ai/.prettierrc.js +5 -0
  291. package/libs/ai/README.md +139 -0
  292. package/libs/ai/eslint.config.mjs +3 -0
  293. package/libs/ai/package.json +42 -0
  294. package/libs/ai/src/index.ts +5 -0
  295. package/libs/ai/src/models.ts +19 -0
  296. package/libs/ai/src/rag/index.ts +1 -0
  297. package/libs/ai/src/rag/rag.test.ts +99 -0
  298. package/libs/ai/src/rag/rag.ts +510 -0
  299. package/libs/ai/tsconfig.json +21 -0
  300. package/libs/ai/vite.config.ts +38 -0
  301. package/libs/cache/.prettierignore +2 -0
  302. package/libs/cache/eslint.config.mjs +3 -0
  303. package/libs/cache/package.json +35 -0
  304. package/libs/cache/src/feedback.ts +77 -0
  305. package/libs/cache/src/index.ts +2 -0
  306. package/libs/cache/tsconfig.json +19 -0
  307. package/libs/cache/vite.config.ts +36 -0
  308. package/libs/clients/.prettierignore +6 -0
  309. package/libs/clients/eslint.config.mjs +3 -0
  310. package/libs/clients/package.json +59 -0
  311. package/libs/clients/src/azure.ts +249 -0
  312. package/libs/clients/src/gcp.ts +220 -0
  313. package/libs/clients/src/hatchet.ts +86 -0
  314. package/libs/clients/src/index.ts +8 -0
  315. package/libs/clients/src/loops.ts +86 -0
  316. package/libs/clients/src/polar.ts +77 -0
  317. package/libs/clients/src/posthog.ts +55 -0
  318. package/libs/clients/tsconfig.json +19 -0
  319. package/libs/clients/vite.config.ts +35 -0
  320. package/libs/config/.prettierignore +6 -0
  321. package/libs/config/.prettierrc.js +12 -0
  322. package/libs/config/eslint.config.mjs +3 -0
  323. package/libs/config/package.json +50 -0
  324. package/libs/config/src/azure.ts +54 -0
  325. package/libs/config/src/db.ts +18 -0
  326. package/libs/config/src/gcp.ts +53 -0
  327. package/libs/config/src/google.ts +17 -0
  328. package/libs/config/src/hatchet.ts +20 -0
  329. package/libs/config/src/index.ts +108 -0
  330. package/libs/config/src/llm.ts +17 -0
  331. package/libs/config/src/polar.ts +24 -0
  332. package/libs/config/src/util.ts +8 -0
  333. package/libs/config/src/vercel.ts +26 -0
  334. package/libs/config/tsconfig.json +19 -0
  335. package/libs/config/vite.config.ts +34 -0
  336. package/libs/core/.prettierignore +2 -0
  337. package/libs/core/eslint.config.mjs +3 -0
  338. package/libs/core/package.json +59 -0
  339. package/libs/core/src/chat/derived.ts +97 -0
  340. package/libs/core/src/chat/feedback.ts +293 -0
  341. package/libs/core/src/chat/index.ts +6 -0
  342. package/libs/core/src/chat/model.ts +92 -0
  343. package/libs/core/src/chat/prepare-tools.ts +286 -0
  344. package/libs/core/src/chat/prompts.ts +623 -0
  345. package/libs/core/src/chat/stream.ts +311 -0
  346. package/libs/core/src/chat/summarize.ts +168 -0
  347. package/libs/core/src/chat/tools/agent.ts +403 -0
  348. package/libs/core/src/chat/tools/chart-agent.ts +526 -0
  349. package/libs/core/src/chat/tools/chart-helpers/sandbox.ts +47 -0
  350. package/libs/core/src/chat/tools/chart.ts +86 -0
  351. package/libs/core/src/chat/tools/credit-agent.ts +1383 -0
  352. package/libs/core/src/chat/tools/credit.ts +1435 -0
  353. package/libs/core/src/chat/tools/deep-dive-agent.ts +100 -0
  354. package/libs/core/src/chat/tools/deep-dive.ts +141 -0
  355. package/libs/core/src/chat/tools/form.ts +449 -0
  356. package/libs/core/src/chat/tools/helpers.ts +91 -0
  357. package/libs/core/src/chat/tools/index.ts +42 -0
  358. package/libs/core/src/chat/tools/pipeline-artifact.ts +76 -0
  359. package/libs/core/src/chat/tools/report.ts +40 -0
  360. package/libs/core/src/chat/tools/search.ts +390 -0
  361. package/libs/core/src/chat/tools/submission.ts +227 -0
  362. package/libs/core/src/chat/tools/workflow.ts +684 -0
  363. package/libs/core/src/chat/types.ts +3 -0
  364. package/libs/core/src/data-extraction/classification/azure.ts +168 -0
  365. package/libs/core/src/data-extraction/classification/index.ts +1 -0
  366. package/libs/core/src/data-extraction/dal.ts +246 -0
  367. package/libs/core/src/data-extraction/form-structure-extractor.ts +294 -0
  368. package/libs/core/src/data-extraction/index.ts +4 -0
  369. package/libs/core/src/data-extraction/layout/azure.ts +730 -0
  370. package/libs/core/src/data-extraction/layout/excel.ts +180 -0
  371. package/libs/core/src/data-extraction/layout/gcp.ts +1071 -0
  372. package/libs/core/src/data-extraction/layout/index.ts +266 -0
  373. package/libs/core/src/data-extraction/layout/plaintext.ts +45 -0
  374. package/libs/core/src/data-extraction/models.ts +38 -0
  375. package/libs/core/src/data-extraction/pdf-utils.ts +96 -0
  376. package/libs/core/src/data-extraction/structuring/bank-statement.ts +1182 -0
  377. package/libs/core/src/data-extraction/structuring/custom.ts +495 -0
  378. package/libs/core/src/data-extraction/structuring/index.ts +290 -0
  379. package/libs/core/src/data-extraction/structuring/prompts.ts +69 -0
  380. package/libs/core/src/data-extraction/type-guards.ts +110 -0
  381. package/libs/core/src/data-extraction/types.ts +84 -0
  382. package/libs/core/src/data-extraction/utils.ts +31 -0
  383. package/libs/core/src/data-extraction/validation/bank-statement.ts +127 -0
  384. package/libs/core/src/deals.ts +17 -0
  385. package/libs/core/src/documents.ts +152 -0
  386. package/libs/core/src/index.ts +5 -0
  387. package/libs/core/src/pipelines/display.ts +678 -0
  388. package/libs/core/src/pipelines/execute.ts +2342 -0
  389. package/libs/core/src/pipelines/index.ts +4 -0
  390. package/libs/core/src/pipelines/list.ts +12 -0
  391. package/libs/core/src/pipelines/runs.ts +53 -0
  392. package/libs/core/tsconfig.json +20 -0
  393. package/libs/core/vite.config.ts +56 -0
  394. package/libs/dal/.prettierignore +6 -0
  395. package/libs/dal/.prettierrc.js +12 -0
  396. package/libs/dal/eslint.config.mjs +3 -0
  397. package/libs/dal/package.json +57 -0
  398. package/libs/dal/src/_tests/db.test.ts +19 -0
  399. package/libs/dal/src/_tests/mock-db.ts +60 -0
  400. package/libs/dal/src/api-key.test.ts +397 -0
  401. package/libs/dal/src/api-key.ts +110 -0
  402. package/libs/dal/src/billing.ts +23 -0
  403. package/libs/dal/src/conversation.test.ts +655 -0
  404. package/libs/dal/src/conversation.ts +532 -0
  405. package/libs/dal/src/deal.test.ts +45 -0
  406. package/libs/dal/src/deal.ts +87 -0
  407. package/libs/dal/src/defaults-consumer-lending-uk.ts +33 -0
  408. package/libs/dal/src/defaults-consumer-lending-us.ts +33 -0
  409. package/libs/dal/src/defaults-private-credit.ts +57 -0
  410. package/libs/dal/src/defaults-private-equity.ts +51 -0
  411. package/libs/dal/src/defaults-smb-lending-us.ts +1569 -0
  412. package/libs/dal/src/defaults-sme-lending-uk-express.ts +1527 -0
  413. package/libs/dal/src/defaults-sme-lending-uk.ts +1669 -0
  414. package/libs/dal/src/defaults-types.ts +23 -0
  415. package/libs/dal/src/defaults.ts +550 -0
  416. package/libs/dal/src/document.test.ts +70 -0
  417. package/libs/dal/src/document.ts +192 -0
  418. package/libs/dal/src/feedback.ts +255 -0
  419. package/libs/dal/src/form.test.ts +637 -0
  420. package/libs/dal/src/form.ts +1165 -0
  421. package/libs/dal/src/index.ts +20 -0
  422. package/libs/dal/src/invitation.test.ts +746 -0
  423. package/libs/dal/src/invitation.ts +207 -0
  424. package/libs/dal/src/member.test.ts +185 -0
  425. package/libs/dal/src/member.ts +80 -0
  426. package/libs/dal/src/organization.ts +116 -0
  427. package/libs/dal/src/permission.ts +25 -0
  428. package/libs/dal/src/pipeline.test.ts +388 -0
  429. package/libs/dal/src/pipeline.ts +4222 -0
  430. package/libs/dal/src/report.ts +199 -0
  431. package/libs/dal/src/result.ts +16 -0
  432. package/libs/dal/src/search.ts +172 -0
  433. package/libs/dal/src/session.test.ts +110 -0
  434. package/libs/dal/src/session.ts +31 -0
  435. package/libs/dal/src/submission.test.ts +1304 -0
  436. package/libs/dal/src/submission.ts +1396 -0
  437. package/libs/dal/src/tool.ts +159 -0
  438. package/libs/dal/src/user.ts +16 -0
  439. package/libs/dal/src/workflow.test.ts +89 -0
  440. package/libs/dal/src/workflow.ts +262 -0
  441. package/libs/dal/tsconfig.build.json +4 -0
  442. package/libs/dal/tsconfig.json +22 -0
  443. package/libs/dal/vite.config.ts +34 -0
  444. package/libs/db/.prettierignore +6 -0
  445. package/libs/db/.prettierrc.js +12 -0
  446. package/libs/db/eslint.config.mjs +3 -0
  447. package/libs/db/package.json +52 -0
  448. package/libs/db/src/index.ts +24 -0
  449. package/libs/db/src/relations.ts +549 -0
  450. package/libs/db/src/schema.ts +2 -0
  451. package/libs/db/src/schemas/api.ts +35 -0
  452. package/libs/db/src/schemas/conversations.ts +175 -0
  453. package/libs/db/src/schemas/core.ts +359 -0
  454. package/libs/db/src/schemas/documents.ts +181 -0
  455. package/libs/db/src/schemas/feedback.ts +40 -0
  456. package/libs/db/src/schemas/index.ts +26 -0
  457. package/libs/db/src/schemas/organisations.ts +97 -0
  458. package/libs/db/src/schemas/pipelines.ts +440 -0
  459. package/libs/db/src/schemas/users.ts +95 -0
  460. package/libs/db/src/types.ts +190 -0
  461. package/libs/db/src/utils.ts +14 -0
  462. package/libs/db/tsconfig.json +19 -0
  463. package/libs/db/vite.config.ts +31 -0
  464. package/libs/lint/.prettierignore +6 -0
  465. package/libs/lint/eslint.config.mjs +61 -0
  466. package/libs/lint/package.json +29 -0
  467. package/libs/lint/prettier.config.js +12 -0
  468. package/libs/schemas/.prettierignore +6 -0
  469. package/libs/schemas/.prettierrc.js +12 -0
  470. package/libs/schemas/README.md +15 -0
  471. package/libs/schemas/eslint.config.mjs +3 -0
  472. package/libs/schemas/package.json +67 -0
  473. package/libs/schemas/src/core/chat.ts +67 -0
  474. package/libs/schemas/src/core/core-result.ts +15 -0
  475. package/libs/schemas/src/core/data-extraction.ts +184 -0
  476. package/libs/schemas/src/core/layout.ts +478 -0
  477. package/libs/schemas/src/core/pipeline.ts +128 -0
  478. package/libs/schemas/src/core/submission.ts +97 -0
  479. package/libs/schemas/src/db/account.ts +57 -0
  480. package/libs/schemas/src/db/apiKey.ts +57 -0
  481. package/libs/schemas/src/db/context.ts +33 -0
  482. package/libs/schemas/src/db/conversation.ts +65 -0
  483. package/libs/schemas/src/db/deal.ts +42 -0
  484. package/libs/schemas/src/db/document.ts +103 -0
  485. package/libs/schemas/src/db/documentCitation.ts +58 -0
  486. package/libs/schemas/src/db/documentExtraction.ts +69 -0
  487. package/libs/schemas/src/db/fieldCorrection.ts +85 -0
  488. package/libs/schemas/src/db/form.ts +45 -0
  489. package/libs/schemas/src/db/formField.ts +59 -0
  490. package/libs/schemas/src/db/formGroup.ts +42 -0
  491. package/libs/schemas/src/db/impersonation.ts +39 -0
  492. package/libs/schemas/src/db/index.ts +25 -0
  493. package/libs/schemas/src/db/invitation.ts +42 -0
  494. package/libs/schemas/src/db/member.ts +36 -0
  495. package/libs/schemas/src/db/message.ts +58 -0
  496. package/libs/schemas/src/db/organization.ts +62 -0
  497. package/libs/schemas/src/db/session.ts +48 -0
  498. package/libs/schemas/src/db/submission.ts +54 -0
  499. package/libs/schemas/src/db/submissionGroup.ts +36 -0
  500. package/libs/schemas/src/db/submissionItem.ts +33 -0
  501. package/libs/schemas/src/db/submissionItemVersion.ts +70 -0
  502. package/libs/schemas/src/db/user.ts +51 -0
  503. package/libs/schemas/src/db/utils.ts +3 -0
  504. package/libs/schemas/src/db/verification.ts +36 -0
  505. package/libs/schemas/src/db/workflow.ts +42 -0
  506. package/libs/schemas/src/index.ts +10 -0
  507. package/libs/schemas/tsconfig.json +21 -0
  508. package/libs/schemas/vite.config.ts +38 -0
  509. package/libs/ui/.prettierignore +6 -0
  510. package/libs/ui/.prettierrc.js +12 -0
  511. package/libs/ui/components.json +24 -0
  512. package/libs/ui/eslint.config.mjs +3 -0
  513. package/libs/ui/package.json +142 -0
  514. package/libs/ui/src/components/chart-viz/chart.tsx +255 -0
  515. package/libs/ui/src/components/chart-viz/converters.ts +474 -0
  516. package/libs/ui/src/components/chart-viz/dashboard.tsx +146 -0
  517. package/libs/ui/src/components/chart-viz/index.ts +37 -0
  518. package/libs/ui/src/components/chart-viz/markdown.tsx +344 -0
  519. package/libs/ui/src/components/chart-viz/table.tsx +446 -0
  520. package/libs/ui/src/components/chart-viz/theme-context.tsx +70 -0
  521. package/libs/ui/src/components/chart-viz/themes/dark.ts +98 -0
  522. package/libs/ui/src/components/chart-viz/themes/index.ts +69 -0
  523. package/libs/ui/src/components/chart-viz/themes/light.ts +98 -0
  524. package/libs/ui/src/components/chart-viz/themes/tailwind.ts +326 -0
  525. package/libs/ui/src/components/chart-viz/themes/types.ts +99 -0
  526. package/libs/ui/src/components/chart-viz/tool-display.tsx +150 -0
  527. package/libs/ui/src/components/chart-viz/types.ts +95 -0
  528. package/libs/ui/src/components/doc-viewers/excel/index.tsx +431 -0
  529. package/libs/ui/src/components/doc-viewers/excel/themes.ts +160 -0
  530. package/libs/ui/src/components/doc-viewers/image/index.tsx +410 -0
  531. package/libs/ui/src/components/doc-viewers/pdf/index.tsx +258 -0
  532. package/libs/ui/src/components/doc-viewers/pdf/virtualized-pdf.tsx +556 -0
  533. package/libs/ui/src/components/misc/rel-date.tsx +52 -0
  534. package/libs/ui/src/components/misc/styled-link.tsx +2 -0
  535. package/libs/ui/src/components/table/data-table.tsx +546 -0
  536. package/libs/ui/src/components/table/report-table.tsx +305 -0
  537. package/libs/ui/src/components/table/sortable-column.tsx +34 -0
  538. package/libs/ui/src/components/ui/accordion.tsx +62 -0
  539. package/libs/ui/src/components/ui/alert-dialog.tsx +142 -0
  540. package/libs/ui/src/components/ui/alert.tsx +62 -0
  541. package/libs/ui/src/components/ui/artifact.tsx +118 -0
  542. package/libs/ui/src/components/ui/attachments.tsx +388 -0
  543. package/libs/ui/src/components/ui/avatar.tsx +39 -0
  544. package/libs/ui/src/components/ui/badge.tsx +43 -0
  545. package/libs/ui/src/components/ui/breadcrumb.tsx +102 -0
  546. package/libs/ui/src/components/ui/button-group.tsx +78 -0
  547. package/libs/ui/src/components/ui/button.tsx +79 -0
  548. package/libs/ui/src/components/ui/card.tsx +32 -0
  549. package/libs/ui/src/components/ui/carousel.tsx +228 -0
  550. package/libs/ui/src/components/ui/chain-of-thought.tsx +198 -0
  551. package/libs/ui/src/components/ui/checkbox.tsx +27 -0
  552. package/libs/ui/src/components/ui/citation.tsx +34 -0
  553. package/libs/ui/src/components/ui/code-block.tsx +500 -0
  554. package/libs/ui/src/components/ui/collapsible.tsx +19 -0
  555. package/libs/ui/src/components/ui/command.tsx +161 -0
  556. package/libs/ui/src/components/ui/conversation.tsx +90 -0
  557. package/libs/ui/src/components/ui/dialog.tsx +142 -0
  558. package/libs/ui/src/components/ui/dropdown-menu.tsx +246 -0
  559. package/libs/ui/src/components/ui/highlight.tsx +3 -0
  560. package/libs/ui/src/components/ui/hover-card.tsx +36 -0
  561. package/libs/ui/src/components/ui/inline-citation.tsx +251 -0
  562. package/libs/ui/src/components/ui/input-group.tsx +156 -0
  563. package/libs/ui/src/components/ui/input-otp.tsx +78 -0
  564. package/libs/ui/src/components/ui/input.tsx +21 -0
  565. package/libs/ui/src/components/ui/label.tsx +19 -0
  566. package/libs/ui/src/components/ui/model-selector.tsx +174 -0
  567. package/libs/ui/src/components/ui/multisidebar.tsx +750 -0
  568. package/libs/ui/src/components/ui/popover.tsx +43 -0
  569. package/libs/ui/src/components/ui/progress.tsx +28 -0
  570. package/libs/ui/src/components/ui/reasoning.tsx +178 -0
  571. package/libs/ui/src/components/ui/resizable.tsx +49 -0
  572. package/libs/ui/src/components/ui/scroll-area.tsx +54 -0
  573. package/libs/ui/src/components/ui/select.tsx +171 -0
  574. package/libs/ui/src/components/ui/separator.tsx +26 -0
  575. package/libs/ui/src/components/ui/sheet.tsx +128 -0
  576. package/libs/ui/src/components/ui/shimmer.tsx +53 -0
  577. package/libs/ui/src/components/ui/skeleton.tsx +13 -0
  578. package/libs/ui/src/components/ui/sonner.tsx +23 -0
  579. package/libs/ui/src/components/ui/switch.tsx +26 -0
  580. package/libs/ui/src/components/ui/table.tsx +96 -0
  581. package/libs/ui/src/components/ui/tabs.tsx +52 -0
  582. package/libs/ui/src/components/ui/textarea.tsx +41 -0
  583. package/libs/ui/src/components/ui/tool.tsx +209 -0
  584. package/libs/ui/src/components/ui/tooltip.tsx +58 -0
  585. package/libs/ui/src/components/ui/typography.tsx +113 -0
  586. package/libs/ui/src/fonts/manrope-v15-latin-300.woff2 +0 -0
  587. package/libs/ui/src/fonts/manrope-v15-latin-400.woff2 +0 -0
  588. package/libs/ui/src/fonts/manrope-v15-latin-500.woff2 +0 -0
  589. package/libs/ui/src/fonts/manrope-v15-latin-600.woff2 +0 -0
  590. package/libs/ui/src/hooks/use-mobile.ts +19 -0
  591. package/libs/ui/src/lib/utils.ts +6 -0
  592. package/libs/ui/src/styles/fonts.css +35 -0
  593. package/libs/ui/src/styles/style.css +218 -0
  594. package/libs/ui/tsconfig.json +21 -0
  595. package/libs/ui/vite.config.ts +80 -0
  596. package/libs/ui-lit/README.md +245 -0
  597. package/libs/ui-lit/TESTING_GUIDE.md +296 -0
  598. package/libs/ui-lit/eslint.config.mjs +3 -0
  599. package/libs/ui-lit/package.json +41 -0
  600. package/libs/ui-lit/scripts/build-css.js +43 -0
  601. package/libs/ui-lit/src/components/sea-alert.ts +132 -0
  602. package/libs/ui-lit/src/components/sea-button.ts +95 -0
  603. package/libs/ui-lit/src/components/sea-card.ts +113 -0
  604. package/libs/ui-lit/src/components/sea-input.ts +184 -0
  605. package/libs/ui-lit/src/components/sea-spinner.ts +65 -0
  606. package/libs/ui-lit/src/index.ts +15 -0
  607. package/libs/ui-lit/src/lib/utils.ts +6 -0
  608. package/libs/ui-lit/src/styles/tailwind.css +76 -0
  609. package/libs/ui-lit/src/theme.css +66 -0
  610. package/libs/ui-lit/src/theme.ts +79 -0
  611. package/libs/ui-lit/src/vite-env.d.ts +6 -0
  612. package/libs/ui-lit/tailwind.config.ts +50 -0
  613. package/libs/ui-lit/test.html +289 -0
  614. package/libs/ui-lit/tsconfig.json +23 -0
  615. package/libs/ui-lit/vite.config.ts +31 -0
  616. package/libs/ui-lit/vite.css.config.ts +20 -0
  617. package/libs/util/.prettierignore +6 -0
  618. package/libs/util/.prettierrc.js +12 -0
  619. package/libs/util/eslint.config.mjs +3 -0
  620. package/libs/util/package.json +45 -0
  621. package/libs/util/src/billing.ts +10 -0
  622. package/libs/util/src/data-transform.ts +19 -0
  623. package/libs/util/src/encryption.ts +45 -0
  624. package/libs/util/src/fmt.test.ts +9 -0
  625. package/libs/util/src/fmt.ts +71 -0
  626. package/libs/util/src/fuzzy.ts +47 -0
  627. package/libs/util/src/id.ts +24 -0
  628. package/libs/util/src/invariant.ts +31 -0
  629. package/libs/util/src/sub-name.ts +7 -0
  630. package/libs/util/tsconfig.json +19 -0
  631. package/libs/util/vite.config.ts +34 -0
  632. package/package.json +28 -0
  633. package/packages/widget/.prettierignore +6 -0
  634. package/packages/widget/.prettierrc.js +12 -0
  635. package/packages/widget/README.md +95 -0
  636. package/packages/widget/eslint.config.mjs +11 -0
  637. package/packages/widget/openapi-ts.config.ts +8 -0
  638. package/packages/widget/package.json +89 -0
  639. package/packages/widget/postcss.config.mjs +10 -0
  640. package/packages/widget/src/clients/api/client/client.ts +187 -0
  641. package/packages/widget/src/clients/api/client/index.ts +22 -0
  642. package/packages/widget/src/clients/api/client/types.ts +192 -0
  643. package/packages/widget/src/clients/api/client/utils.ts +394 -0
  644. package/packages/widget/src/clients/api/client.gen.ts +18 -0
  645. package/packages/widget/src/clients/api/core/auth.ts +39 -0
  646. package/packages/widget/src/clients/api/core/bodySerializer.ts +74 -0
  647. package/packages/widget/src/clients/api/core/params.ts +132 -0
  648. package/packages/widget/src/clients/api/core/pathSerializer.ts +169 -0
  649. package/packages/widget/src/clients/api/core/types.ts +80 -0
  650. package/packages/widget/src/clients/api/index.ts +3 -0
  651. package/packages/widget/src/clients/api/sdk.gen.ts +805 -0
  652. package/packages/widget/src/clients/api/types.gen.ts +2085 -0
  653. package/packages/widget/src/components/container.tsx +42 -0
  654. package/packages/widget/src/components/data-display.tsx +384 -0
  655. package/packages/widget/src/components/data-viewer.tsx +311 -0
  656. package/packages/widget/src/components/doc-list.tsx +102 -0
  657. package/packages/widget/src/components/field-correction-modal.tsx +265 -0
  658. package/packages/widget/src/components/header.tsx +71 -0
  659. package/packages/widget/src/components/new-submission.tsx +290 -0
  660. package/packages/widget/src/components/sidebar-right.tsx +19 -0
  661. package/packages/widget/src/components/submission-card.tsx +66 -0
  662. package/packages/widget/src/components/submission-page.tsx +75 -0
  663. package/packages/widget/src/components/upload-doc.tsx +241 -0
  664. package/packages/widget/src/components/widget.tsx +101 -0
  665. package/packages/widget/src/index.tsx +167 -0
  666. package/packages/widget/src/lib/config.ts +2 -0
  667. package/packages/widget/src/lib/util.ts +40 -0
  668. package/packages/widget/src/styles/index.css +5 -0
  669. package/packages/widget/src/styles/tw-properties.css +337 -0
  670. package/packages/widget/src/vite-env.d.ts +3 -0
  671. package/packages/widget/tsconfig.app.json +35 -0
  672. package/packages/widget/tsconfig.json +4 -0
  673. package/packages/widget/tsconfig.node.json +24 -0
  674. package/packages/widget/vite.config.ts +116 -0
  675. package/packages/widget-lit/BOTTLENECKS.md +250 -0
  676. package/packages/widget-lit/IMPLEMENTATION_SUMMARY.md +295 -0
  677. package/packages/widget-lit/README.md +232 -0
  678. package/packages/widget-lit/eslint.config.mjs +3 -0
  679. package/packages/widget-lit/package.json +52 -0
  680. package/packages/widget-lit/src/api-client.ts +230 -0
  681. package/packages/widget-lit/src/api-client.ts.backup +218 -0
  682. package/packages/widget-lit/src/components/sea-chat.ts +382 -0
  683. package/packages/widget-lit/src/components/sea-submission-viewer.ts +267 -0
  684. package/packages/widget-lit/src/components/sea-widget.ts +317 -0
  685. package/packages/widget-lit/src/index.ts +48 -0
  686. package/packages/widget-lit/src/react.ts +58 -0
  687. package/packages/widget-lit/src/style.css +47 -0
  688. package/packages/widget-lit/tsconfig.json +24 -0
  689. package/packages/widget-lit/vite.config.ts +29 -0
  690. package/packages/widget-ng/DEVELOPMENT.md +74 -0
  691. package/packages/widget-ng/README.md +657 -0
  692. package/packages/widget-ng/dev.sh +14 -0
  693. package/packages/widget-ng/eslint.config.mjs +24 -0
  694. package/packages/widget-ng/ng-package.json +9 -0
  695. package/packages/widget-ng/package.json +85 -0
  696. package/packages/widget-ng/src/index.ts +45 -0
  697. package/packages/widget-ng/src/lib/components/sea-chat.component.ts +737 -0
  698. package/packages/widget-ng/src/lib/components/sea-data-viewer.component.ts +2240 -0
  699. package/packages/widget-ng/src/lib/components/sea-deal-form-modal.component.ts +702 -0
  700. package/packages/widget-ng/src/lib/components/sea-document-list.component.ts +350 -0
  701. package/packages/widget-ng/src/lib/components/sea-feedback-modal.component.ts +461 -0
  702. package/packages/widget-ng/src/lib/components/sea-file-upload.component.ts +655 -0
  703. package/packages/widget-ng/src/lib/components/sea-model-selection-modal.component.ts +367 -0
  704. package/packages/widget-ng/src/lib/components/sea-new-submission-modal.component.ts +414 -0
  705. package/packages/widget-ng/src/lib/components/sea-pdf-viewer.component.ts +869 -0
  706. package/packages/widget-ng/src/lib/components/sea-submission-card.component.ts +251 -0
  707. package/packages/widget-ng/src/lib/components/sea-widget.component.ts +684 -0
  708. package/packages/widget-ng/src/lib/models/submission.model.ts +170 -0
  709. package/packages/widget-ng/src/lib/pipes/markdown.pipe.ts +57 -0
  710. package/packages/widget-ng/src/lib/services/api-client.service.ts +715 -0
  711. package/packages/widget-ng/src/lib/services/chat.service.ts +330 -0
  712. package/packages/widget-ng/src/lib/services/config.service.ts +107 -0
  713. package/packages/widget-ng/src/web-component.ts +56 -0
  714. package/packages/widget-ng/tsconfig.json +25 -0
  715. package/packages/widget-ng/tsconfig.lib.json +9 -0
  716. package/packages/widget-ng/vite.config.elements.ts +26 -0
  717. package/packages/widget-ng/vitest.config.ts +19 -0
  718. package/packages/widget-ng/vitest.setup.ts +13 -0
  719. package/pnpm-workspace.yaml +18 -0
  720. package/render.yaml +136 -0
  721. package/scripts/README.md +57 -0
  722. package/scripts/package.json +22 -0
  723. package/scripts/python/.python-version +1 -0
  724. package/scripts/python/README.md +3 -0
  725. package/scripts/python/export-org-data.py +693 -0
  726. package/scripts/python/pyproject.toml +29 -0
  727. package/scripts/python/requirements-dev.lock +36 -0
  728. package/scripts/python/requirements.lock +36 -0
  729. package/scripts/python/src/gen.py +297 -0
  730. package/scripts/python/test.py +34 -0
  731. package/scripts/src/fix-storage-provider-mismatch.ts +239 -0
  732. package/scripts/src/sync-render-yaml.ts +290 -0
  733. package/scripts/src/test-chat-stream.ts +300 -0
  734. package/scripts/src/test-reconciliation.ts +230 -0
  735. package/scripts/tsconfig.json +15 -0
  736. package/tests/angular-test-app/.vscode/extensions.json +4 -0
  737. package/tests/angular-test-app/.vscode/launch.json +13 -0
  738. package/tests/angular-test-app/.vscode/tasks.json +24 -0
  739. package/tests/angular-test-app/README.md +59 -0
  740. package/tests/angular-test-app/angular.json +111 -0
  741. package/tests/angular-test-app/clean-start.sh +14 -0
  742. package/tests/angular-test-app/package.json +36 -0
  743. package/tests/angular-test-app/public/favicon.ico +0 -0
  744. package/tests/angular-test-app/src/app/app.component.ts +220 -0
  745. package/tests/angular-test-app/src/app/app.config.ts +5 -0
  746. package/tests/angular-test-app/src/env.d.ts +13 -0
  747. package/tests/angular-test-app/src/index.html +13 -0
  748. package/tests/angular-test-app/src/main.ts +6 -0
  749. package/tests/angular-test-app/src/styles.css +8 -0
  750. package/tests/angular-test-app/tsconfig.app.json +15 -0
  751. package/tests/angular-test-app/tsconfig.json +27 -0
  752. package/tests/crm-viewer-app/API_INTEGRATION_SUMMARY.md +295 -0
  753. package/tests/crm-viewer-app/CURRENT_ASSETS_FIELDS.md +148 -0
  754. package/tests/crm-viewer-app/FIELD_ID_MAPPING.md +206 -0
  755. package/tests/crm-viewer-app/INTEGRATION_GUIDE.md +309 -0
  756. package/tests/crm-viewer-app/README.md +174 -0
  757. package/tests/crm-viewer-app/REAL_API_INTEGRATION.md +240 -0
  758. package/tests/crm-viewer-app/UPDATED_IMPLEMENTATION.md +279 -0
  759. package/tests/crm-viewer-app/angular.json +114 -0
  760. package/tests/crm-viewer-app/package.json +35 -0
  761. package/tests/crm-viewer-app/src/app/app.component.ts +534 -0
  762. package/tests/crm-viewer-app/src/app/citation.service.ts +316 -0
  763. package/tests/crm-viewer-app/src/env.d.ts +16 -0
  764. package/tests/crm-viewer-app/src/index.html +19 -0
  765. package/tests/crm-viewer-app/src/main.ts +7 -0
  766. package/tests/crm-viewer-app/src/styles.css +409 -0
  767. package/tests/crm-viewer-app/src/template.html +2678 -0
  768. package/tests/crm-viewer-app/tsconfig.app.json +15 -0
  769. package/tests/crm-viewer-app/tsconfig.json +27 -0
  770. package/tests/e2e/package.json +17 -0
  771. package/tests/e2e/playwright.config.ts +75 -0
  772. package/tests/e2e/tests/api/health.spec.ts +10 -0
  773. package/tests/e2e/tests/app/example.spec.ts +10 -0
  774. package/tests/widget-test-app/.prettierignore +6 -0
  775. package/tests/widget-test-app/README.md +48 -0
  776. package/tests/widget-test-app/index.html +12 -0
  777. package/tests/widget-test-app/package.json +24 -0
  778. package/tests/widget-test-app/src/App.css +192 -0
  779. package/tests/widget-test-app/src/App.tsx +80 -0
  780. package/tests/widget-test-app/src/main.tsx +9 -0
  781. package/tests/widget-test-app/src/vite-env.d.ts +4 -0
  782. package/tests/widget-test-app/tsconfig.json +25 -0
  783. package/tests/widget-test-app/tsconfig.node.json +11 -0
  784. package/tests/widget-test-app/vite.config.ts +14 -0
@@ -0,0 +1,1435 @@
1
+ import { createCreditAgent } from "./credit-agent";
2
+ import type { VercelConfig } from "@sea/config/vercel";
3
+ import { getDenormSubmission, type DenormGroup } from "@sea/dal/submission";
4
+ import { db, schema } from "@sea/db";
5
+ import { memorySchema } from "@sea/db/schema";
6
+ import type { Member } from "@sea/db/types";
7
+ import { invariant } from "@sea/util/invariant";
8
+ import type { LanguageModel, UIMessageStreamWriter } from "ai";
9
+ import { tool } from "ai";
10
+ import { and, eq, isNull } from "drizzle-orm";
11
+ import * as z from "zod/v4";
12
+ import { returnErr } from "./helpers";
13
+
14
+ /**
15
+ * Credit Analysis Tool for calculating DSCR and assessing creditworthiness
16
+ *
17
+ * This tool is designed to work with credit evaluation forms that contain:
18
+ * - Credit bureau data (with credit lines and estimated payments)
19
+ * - Financial statements (with EBITDA, interest expense, current portion of LT debt)
20
+ * - Legal bureau reports (litigation information)
21
+ */
22
+
23
+ const DSCRCalculationSchema = z.object({
24
+ submissionId: z
25
+ .string()
26
+ .optional()
27
+ .describe(
28
+ "The submission ID containing the credit evaluation data. If not provided, will use the most recent submission from conversation memory.",
29
+ ),
30
+ });
31
+
32
+ export interface DSCRResult {
33
+ financialStatementDSCR: {
34
+ ebitda: number | null;
35
+ interestExpense: number | null;
36
+ currentPortionDebt: number | null;
37
+ dscr: number | null;
38
+ formula: string;
39
+ interpretation: string;
40
+ };
41
+ creditBureauDSCR: {
42
+ ebitda: number | null;
43
+ estimatedMonthlyPayments: number | null;
44
+ annualizedPayments: number | null;
45
+ dscr: number | null;
46
+ formula: string;
47
+ interpretation: string;
48
+ };
49
+ triangulation: {
50
+ comparison: string;
51
+ recommendation: string;
52
+ riskFactors: string[];
53
+ };
54
+ }
55
+
56
+ export function calculateDSCR({
57
+ member,
58
+ conversationId,
59
+ }: {
60
+ member: Member;
61
+ conversationId: string;
62
+ }) {
63
+ return tool({
64
+ description: `Calculate Debt Service Coverage Ratio (DSCR) using both financial statement and credit bureau methods.
65
+
66
+ DSCR measures a company's ability to service its debt obligations. Higher is better.
67
+ - Above 1.25: Good coverage
68
+ - 1.0-1.25: Adequate but tight
69
+ - Below 1.0: Cannot cover debt obligations
70
+
71
+ This tool will:
72
+ 1. Calculate DSCR from financial statements: EBITDA / (Interest Expense + Current Portion of LT Debt)
73
+ 2. Calculate DSCR from credit bureau: EBITDA / (Estimated Annual Payments on all credit lines)
74
+ 3. Compare the two methods and identify discrepancies
75
+ 4. Highlight risk factors from legal reports and payment history
76
+
77
+ If no submissionId is provided, will automatically use the most recent submission in your conversation memory.
78
+ `,
79
+ inputSchema: DSCRCalculationSchema,
80
+ execute: async ({
81
+ submissionId: providedSubmissionId,
82
+ }): Promise<{ ok: boolean; data?: DSCRResult; error?: string }> => {
83
+ try {
84
+ // Auto-detect submission from conversation memory if not provided
85
+ let submissionId = providedSubmissionId;
86
+
87
+ if (!submissionId) {
88
+ console.log(
89
+ "[calculateDSCR] No submissionId provided, checking conversation memory",
90
+ );
91
+ const conversation = await db.query.conversation.findFirst({
92
+ where: and(
93
+ eq(schema.conversation.id, conversationId),
94
+ eq(schema.conversation.organizationId, member.organizationId),
95
+ ),
96
+ columns: { memory: true },
97
+ });
98
+
99
+ if (conversation?.memory) {
100
+ const memory = memorySchema.parse(conversation.memory);
101
+ if (memory.submissionIds.length > 0) {
102
+ // Use the most recent submission
103
+ submissionId =
104
+ memory.submissionIds[memory.submissionIds.length - 1];
105
+ console.log(
106
+ `[calculateDSCR] Auto-detected submission from memory: ${submissionId}`,
107
+ );
108
+ } else {
109
+ return {
110
+ ok: false,
111
+ error:
112
+ "No submission ID provided and no submissions found in conversation memory. Please provide a submissionId or work with a submission first.",
113
+ };
114
+ }
115
+ } else {
116
+ return {
117
+ ok: false,
118
+ error:
119
+ "No submission ID provided and conversation has no memory. Please provide a submissionId.",
120
+ };
121
+ }
122
+ }
123
+
124
+ // At this point submissionId is guaranteed to be defined
125
+ if (!submissionId) {
126
+ throw new Error("Submission ID could not be determined");
127
+ }
128
+
129
+ const submission = await getDenormSubmission({
130
+ member,
131
+ submissionId,
132
+ withCitations: true,
133
+ withTransformations: true,
134
+ });
135
+
136
+ // Debug: Log submission structure
137
+ console.log("[calculateDSCR] Analyzing submission:", {
138
+ submissionId,
139
+ submissionName: submission.name,
140
+ formName: submission.formName,
141
+ groupCount: submission.groups.length,
142
+ tableCount: submission.tables?.length || 0,
143
+ });
144
+
145
+ console.log(
146
+ "[calculateDSCR] Single groups:",
147
+ submission.groups.map((g) => g.name),
148
+ );
149
+ console.log(
150
+ "[calculateDSCR] Multi-group tables:",
151
+ submission.tables?.map((t) => ({
152
+ name: t.name,
153
+ rows: t.rows?.length || 0,
154
+ })),
155
+ );
156
+
157
+ // IMPORTANT: Financial statements are multi-groups, so they're in submission.tables, not submission.groups!
158
+ // 1. Find financial statement table (look for table with EBITDA column)
159
+ const financialTable = submission.tables?.find(
160
+ (t) =>
161
+ t.name.toLowerCase().includes("financial") &&
162
+ t.columns?.some((col) => col.toLowerCase().includes("ebitda")),
163
+ );
164
+
165
+ // 2. Find the most recent row by looking at "Period End Date"
166
+ let financialStmtLatest = null;
167
+ let latestDate: Date | null = null;
168
+
169
+ if (financialTable?.rows) {
170
+ console.log(
171
+ `[calculateDSCR] Found financial table "${financialTable.name}" with ${financialTable.rows.length} rows`,
172
+ );
173
+
174
+ for (const row of financialTable.rows) {
175
+ const periodEndDateItem = row.items?.find(
176
+ (item) =>
177
+ item.name.toLowerCase().includes("period") &&
178
+ item.name.toLowerCase().includes("date"),
179
+ );
180
+
181
+ if (periodEndDateItem?.value) {
182
+ const dateValue = new Date(periodEndDateItem.value as string);
183
+ if (
184
+ !isNaN(dateValue.getTime()) &&
185
+ (!latestDate || dateValue > latestDate)
186
+ ) {
187
+ latestDate = dateValue;
188
+ // Convert row to group-like structure for extractMoneyValue
189
+ financialStmtLatest = {
190
+ name: financialTable.name,
191
+ items: row.items,
192
+ };
193
+ }
194
+ }
195
+ }
196
+
197
+ // Fallback: use first row if no valid dates found
198
+ if (
199
+ !financialStmtLatest &&
200
+ financialTable.rows.length > 0 &&
201
+ financialTable.rows[0]?.items
202
+ ) {
203
+ financialStmtLatest = {
204
+ name: financialTable.name,
205
+ items: financialTable.rows[0].items,
206
+ };
207
+ console.log(
208
+ "[calculateDSCR] No valid dates found, using first row",
209
+ );
210
+ }
211
+ }
212
+
213
+ // Find single groups (credit bureau, legal, etc.)
214
+ const creditBureauGroup = submission.groups.find(
215
+ (g) =>
216
+ g.name.toLowerCase().includes("credit bureau") ||
217
+ g.name.toLowerCase().includes("bureau data"),
218
+ );
219
+
220
+ const creditLinesTable = submission.tables?.find(
221
+ (t) =>
222
+ t.name.toLowerCase().includes("credit lines") ||
223
+ t.name.toLowerCase().includes("credit facilities"),
224
+ );
225
+
226
+ const legalBureauGroup = submission.groups.find(
227
+ (g) =>
228
+ g.name.toLowerCase().includes("legal") &&
229
+ g.name.toLowerCase().includes("bureau"),
230
+ );
231
+
232
+ // Debug: log what was found
233
+ console.log("[calculateDSCR] Matched data sources:", {
234
+ financialStmtLatest: financialStmtLatest?.name,
235
+ financialStmtLatestDate: latestDate?.toISOString(),
236
+ creditBureauGroup: creditBureauGroup?.name,
237
+ creditLinesTable: creditLinesTable?.name,
238
+ legalBureauGroup: legalBureauGroup?.name,
239
+ });
240
+
241
+ // Extract financial statement data from the most recent row
242
+ const ebitda = extractMoneyValue(financialStmtLatest, "ebitda");
243
+ const interestExpense = extractMoneyValue(
244
+ financialStmtLatest,
245
+ "interest",
246
+ );
247
+ const currentPortionDebt = extractMoneyValue(
248
+ financialStmtLatest,
249
+ "current portion",
250
+ );
251
+
252
+ // Debug: log extracted values
253
+ console.log("[calculateDSCR] Extracted financial values:", {
254
+ ebitda,
255
+ interestExpense,
256
+ currentPortionDebt,
257
+ financialGroupItems: financialStmtLatest?.items?.map((i) => ({
258
+ name: i.name,
259
+ value: i.value,
260
+ })),
261
+ });
262
+
263
+ // Calculate financial statement DSCR
264
+ let fsDebtService: number | null = null;
265
+ let fsDSCR: number | null = null;
266
+ if (interestExpense !== null && currentPortionDebt !== null) {
267
+ fsDebtService = interestExpense + currentPortionDebt;
268
+ if (ebitda !== null && fsDebtService > 0) {
269
+ fsDSCR = ebitda / fsDebtService;
270
+ }
271
+ }
272
+
273
+ const fsInterpretation =
274
+ fsDSCR !== null
275
+ ? fsDSCR >= 1.25
276
+ ? "Good - Strong debt coverage"
277
+ : fsDSCR >= 1.0
278
+ ? "Adequate - Tight but can cover obligations"
279
+ : "Poor - Cannot cover debt obligations"
280
+ : "Insufficient data to calculate";
281
+
282
+ // Extract credit bureau data
283
+ const estimatedMonthlyPayments = extractMoneyValue(
284
+ creditBureauGroup,
285
+ "estimated total monthly",
286
+ );
287
+ const annualizedPayments =
288
+ estimatedMonthlyPayments !== null
289
+ ? estimatedMonthlyPayments * 12
290
+ : null;
291
+
292
+ // Calculate credit bureau DSCR
293
+ let cbDSCR: number | null = null;
294
+ if (
295
+ ebitda !== null &&
296
+ annualizedPayments !== null &&
297
+ annualizedPayments > 0
298
+ ) {
299
+ cbDSCR = ebitda / annualizedPayments;
300
+ }
301
+
302
+ const cbInterpretation =
303
+ cbDSCR !== null
304
+ ? cbDSCR >= 1.25
305
+ ? "Good - Strong debt coverage based on bureau data"
306
+ : cbDSCR >= 1.0
307
+ ? "Adequate - Tight coverage based on bureau data"
308
+ : "Poor - Cannot cover obligations per bureau data"
309
+ : "Insufficient credit bureau data to calculate";
310
+
311
+ // Triangulation and risk factors
312
+ const riskFactors: string[] = [];
313
+
314
+ // Check for discrepancies between methods
315
+ let comparison = "";
316
+ if (fsDSCR !== null && cbDSCR !== null) {
317
+ const diff = Math.abs(fsDSCR - cbDSCR);
318
+ const diffPct = (diff / Math.max(fsDSCR, cbDSCR)) * 100;
319
+
320
+ if (diffPct > 20) {
321
+ comparison = `SIGNIFICANT DISCREPANCY: Financial statement DSCR (${fsDSCR.toFixed(2)}) and credit bureau DSCR (${cbDSCR.toFixed(2)}) differ by ${diffPct.toFixed(1)}%. This warrants investigation.`;
322
+ riskFactors.push(
323
+ "Large discrepancy between DSCR calculation methods",
324
+ );
325
+ } else {
326
+ comparison = `ALIGNED: Both methods show similar DSCR (FS: ${fsDSCR.toFixed(2)}, CB: ${cbDSCR.toFixed(2)})`;
327
+ }
328
+ } else if (fsDSCR !== null) {
329
+ comparison = "Only financial statement DSCR available";
330
+ } else if (cbDSCR !== null) {
331
+ comparison = "Only credit bureau DSCR available";
332
+ } else {
333
+ comparison =
334
+ "Insufficient data to calculate DSCR using either method";
335
+ }
336
+
337
+ // Check payment delinquencies
338
+ const delinquencies = extractStringValue(
339
+ creditBureauGroup,
340
+ "delinquencies",
341
+ );
342
+ if (delinquencies && delinquencies.toLowerCase().includes("late")) {
343
+ riskFactors.push(`Payment issues: ${delinquencies}`);
344
+ }
345
+
346
+ // Check litigation
347
+ const litigationCount = extractNumberValue(
348
+ legalBureauGroup,
349
+ "litigation count",
350
+ );
351
+ const litigationSummary = extractStringValue(
352
+ legalBureauGroup,
353
+ "litigation summary",
354
+ );
355
+ if (litigationCount && litigationCount > 0) {
356
+ riskFactors.push(`${litigationCount} active litigation case(s)`);
357
+ }
358
+ if (litigationSummary) {
359
+ riskFactors.push(`Legal exposure: ${litigationSummary}`);
360
+ }
361
+
362
+ // Generate recommendation
363
+ let recommendation = "";
364
+ const worstDSCR =
365
+ fsDSCR !== null && cbDSCR !== null
366
+ ? Math.min(fsDSCR, cbDSCR)
367
+ : (fsDSCR ?? cbDSCR);
368
+
369
+ if (worstDSCR !== null) {
370
+ if (worstDSCR >= 1.25 && riskFactors.length === 0) {
371
+ recommendation =
372
+ "APPROVE: Strong debt coverage with no significant risk factors";
373
+ } else if (worstDSCR >= 1.0) {
374
+ recommendation = `REVIEW: Adequate coverage but ${riskFactors.length > 0 ? "risk factors present" : "tight margin"}. Consider additional analysis.`;
375
+ } else {
376
+ recommendation =
377
+ "DECLINE: Cannot adequately cover debt obligations";
378
+ }
379
+ } else {
380
+ recommendation =
381
+ "INSUFFICIENT DATA: Cannot make credit recommendation";
382
+ }
383
+
384
+ if (riskFactors.length > 0) {
385
+ recommendation += ` Note: ${riskFactors.length} risk factor(s) identified.`;
386
+ }
387
+
388
+ const result: DSCRResult = {
389
+ financialStatementDSCR: {
390
+ ebitda,
391
+ interestExpense,
392
+ currentPortionDebt,
393
+ dscr: fsDSCR,
394
+ formula: "EBITDA / (Interest Expense + Current Portion of LT Debt)",
395
+ interpretation: fsInterpretation,
396
+ },
397
+ creditBureauDSCR: {
398
+ ebitda,
399
+ estimatedMonthlyPayments,
400
+ annualizedPayments,
401
+ dscr: cbDSCR,
402
+ formula: "EBITDA / (Estimated Annual Payments on all credit lines)",
403
+ interpretation: cbInterpretation,
404
+ },
405
+ triangulation: {
406
+ comparison,
407
+ recommendation,
408
+ riskFactors,
409
+ },
410
+ };
411
+
412
+ return { ok: true, data: result };
413
+ } catch (err) {
414
+ return {
415
+ ok: false,
416
+ error: err instanceof Error ? err.message : String(err),
417
+ };
418
+ }
419
+ },
420
+ });
421
+ }
422
+
423
+ // Helper functions to extract values from submission groups
424
+ type GroupLike = {
425
+ name: string;
426
+ items?: DenormGroup["items"];
427
+ };
428
+
429
+ function extractMoneyValue(
430
+ group: GroupLike | null | undefined,
431
+ fieldNamePattern: string,
432
+ ): number | null {
433
+ if (!group) {
434
+ console.log(
435
+ `[extractMoneyValue] No group provided for pattern: ${fieldNamePattern}`,
436
+ );
437
+ return null;
438
+ }
439
+
440
+ if (!group.items) {
441
+ console.log(`[extractMoneyValue] Group "${group.name}" has no items`);
442
+ return null;
443
+ }
444
+
445
+ const field = group.items.find((item) =>
446
+ item.name.toLowerCase().includes(fieldNamePattern.toLowerCase()),
447
+ );
448
+
449
+ if (!field) {
450
+ console.log(
451
+ `[extractMoneyValue] No field matching pattern "${fieldNamePattern}" in group "${group.name}". Available fields:`,
452
+ group.items.map((i) => i.name),
453
+ );
454
+ return null;
455
+ }
456
+
457
+ if (field.value === null || field.value === undefined) {
458
+ console.log(
459
+ `[extractMoneyValue] Field "${field.name}" has null/undefined value`,
460
+ );
461
+ return null;
462
+ }
463
+
464
+ // Handle different money value formats
465
+ const value = field.value as { amount?: number; currency?: string } | number;
466
+
467
+ console.log(
468
+ `[extractMoneyValue] Found field "${field.name}" with value:`,
469
+ value,
470
+ );
471
+
472
+ if (typeof value === "number") return value;
473
+ if (typeof value === "object" && value !== null && "amount" in value)
474
+ return value.amount ?? null;
475
+
476
+ console.log(
477
+ `[extractMoneyValue] Could not parse value format for field "${field.name}":`,
478
+ typeof value,
479
+ );
480
+ return null;
481
+ }
482
+
483
+ function extractNumberValue(
484
+ group: GroupLike | null | undefined,
485
+ fieldNamePattern: string,
486
+ ): number | null {
487
+ if (!group || !group.items) return null;
488
+
489
+ const field = group.items.find((item) =>
490
+ item.name.toLowerCase().includes(fieldNamePattern.toLowerCase()),
491
+ );
492
+
493
+ if (!field || field.value === null) return null;
494
+ return typeof field.value === "number" ? field.value : null;
495
+ }
496
+
497
+ function extractStringValue(
498
+ group: GroupLike | null | undefined,
499
+ fieldNamePattern: string,
500
+ ): string | null {
501
+ if (!group || !group.items) return null;
502
+
503
+ const field = group.items.find((item) =>
504
+ item.name.toLowerCase().includes(fieldNamePattern.toLowerCase()),
505
+ );
506
+
507
+ if (!field || field.value === null) return null;
508
+ return typeof field.value === "string" ? field.value : null;
509
+ }
510
+
511
+ /**
512
+ * Cross-Reference Verification Tool
513
+ * Verifies that values in different locations of a submission match or reconcile correctly
514
+ */
515
+ export function verifyAccountReconciliation({ member }: { member: Member }) {
516
+ return tool({
517
+ description:
518
+ "Verify that a value in one location matches or reconciles with values in other locations. Use this to catch inconsistencies like 'Security deposit is £50k in Balance Sheet but £45k in Note 7'",
519
+ inputSchema: z.object({
520
+ submissionId: z.string(),
521
+ primaryLocation: z.object({
522
+ groupOrTableName: z
523
+ .string()
524
+ .describe("Name of the group or table containing the primary value"),
525
+ fieldName: z.string().describe("Name of the field to check"),
526
+ }),
527
+ referenceLocations: z.array(
528
+ z.object({
529
+ groupOrTableName: z
530
+ .string()
531
+ .describe(
532
+ "Name of the group or table containing the reference value",
533
+ ),
534
+ fieldName: z
535
+ .string()
536
+ .describe("Name of the field to compare against"),
537
+ }),
538
+ ),
539
+ expectedRelationship: z
540
+ .enum(["equal", "sum_to", "difference"])
541
+ .optional()
542
+ .describe(
543
+ "Expected relationship between values: 'equal' = should match exactly, 'sum_to' = references should sum to primary, 'difference' = primary minus references should equal zero",
544
+ ),
545
+ }),
546
+ execute: async ({
547
+ submissionId,
548
+ primaryLocation,
549
+ referenceLocations,
550
+ expectedRelationship,
551
+ }) => {
552
+ try {
553
+ const submission = await getDenormSubmission({
554
+ member,
555
+ submissionId,
556
+ withCitations: true,
557
+ withTransformations: true,
558
+ });
559
+
560
+ // Find primary value
561
+ const primaryGroup =
562
+ submission.groups.find((g) =>
563
+ g.name
564
+ .toLowerCase()
565
+ .includes(primaryLocation.groupOrTableName.toLowerCase()),
566
+ ) ||
567
+ submission.tables?.find((t) =>
568
+ t.name
569
+ .toLowerCase()
570
+ .includes(primaryLocation.groupOrTableName.toLowerCase()),
571
+ );
572
+
573
+ if (!primaryGroup) {
574
+ return {
575
+ ok: false,
576
+ error: `Primary location "${primaryLocation.groupOrTableName}" not found`,
577
+ };
578
+ }
579
+
580
+ const primaryValue = extractMoneyValue(
581
+ primaryGroup,
582
+ primaryLocation.fieldName,
583
+ );
584
+
585
+ if (primaryValue === null) {
586
+ return {
587
+ ok: false,
588
+ error: `Primary field "${primaryLocation.fieldName}" not found or has no value`,
589
+ };
590
+ }
591
+
592
+ // Find reference values
593
+ const referenceValues: Array<{
594
+ location: string;
595
+ field: string;
596
+ value: number | null;
597
+ }> = [];
598
+
599
+ for (const refLoc of referenceLocations) {
600
+ const refGroup =
601
+ submission.groups.find((g) =>
602
+ g.name
603
+ .toLowerCase()
604
+ .includes(refLoc.groupOrTableName.toLowerCase()),
605
+ ) ||
606
+ submission.tables?.find((t) =>
607
+ t.name
608
+ .toLowerCase()
609
+ .includes(refLoc.groupOrTableName.toLowerCase()),
610
+ );
611
+
612
+ if (!refGroup) {
613
+ referenceValues.push({
614
+ location: refLoc.groupOrTableName,
615
+ field: refLoc.fieldName,
616
+ value: null,
617
+ });
618
+ continue;
619
+ }
620
+
621
+ const refValue = extractMoneyValue(refGroup, refLoc.fieldName);
622
+ referenceValues.push({
623
+ location: refLoc.groupOrTableName,
624
+ field: refLoc.fieldName,
625
+ value: refValue,
626
+ });
627
+ }
628
+
629
+ // Perform reconciliation check
630
+ const relationship = expectedRelationship || "equal";
631
+ let reconciles = false;
632
+ let details = "";
633
+
634
+ if (relationship === "equal") {
635
+ reconciles = referenceValues.every(
636
+ (ref) => ref.value === primaryValue,
637
+ );
638
+ details = `Primary value: ${primaryValue}. Reference values: ${referenceValues.map((r) => `${r.location}.${r.field} = ${r.value}`).join(", ")}`;
639
+ } else if (relationship === "sum_to") {
640
+ const sum = referenceValues.reduce(
641
+ (acc, ref) => acc + (ref.value || 0),
642
+ 0,
643
+ );
644
+ reconciles = Math.abs(sum - primaryValue) < 0.01; // Allow for rounding
645
+ details = `Primary value: ${primaryValue}. Sum of references: ${sum}. Difference: ${Math.abs(sum - primaryValue)}`;
646
+ } else if (relationship === "difference") {
647
+ const sum = referenceValues.reduce(
648
+ (acc, ref) => acc + (ref.value || 0),
649
+ 0,
650
+ );
651
+ reconciles = Math.abs(primaryValue - sum) < 0.01;
652
+ details = `Primary value: ${primaryValue}. Sum of references: ${sum}. Difference: ${primaryValue - sum}`;
653
+ }
654
+
655
+ return {
656
+ ok: true,
657
+ data: {
658
+ reconciles,
659
+ primaryValue,
660
+ referenceValues,
661
+ relationship,
662
+ details,
663
+ discrepancy: reconciles
664
+ ? null
665
+ : "Values do not reconcile as expected",
666
+ },
667
+ };
668
+ } catch (err) {
669
+ return returnErr(err);
670
+ }
671
+ },
672
+ });
673
+ }
674
+
675
+ /**
676
+ * Financial Ratio Calculator Tool
677
+ * Calculates financial metrics and verifies they match stated values
678
+ */
679
+ export function calculateFinancialMetric({ member }: { member: Member }) {
680
+ return tool({
681
+ description:
682
+ "Calculate financial metrics like DSO (Days Sales Outstanding), current ratio, depreciation, or verify formulas match stated policies. Use to catch errors like 'DSO calculation doesn't match'",
683
+ inputSchema: z.object({
684
+ submissionId: z.string(),
685
+ metric: z.enum([
686
+ "dso",
687
+ "current_ratio",
688
+ "quick_ratio",
689
+ "depreciation",
690
+ "amortization",
691
+ "debt_to_equity",
692
+ "working_capital",
693
+ ]),
694
+ inputs: z
695
+ .record(z.string(), z.number())
696
+ .optional()
697
+ .describe(
698
+ "Override inputs if needed (e.g., {revenue: 1000000, receivables: 120000})",
699
+ ),
700
+ periodDays: z
701
+ .number()
702
+ .optional()
703
+ .describe("Number of days in period for DSO calculation (default 365)"),
704
+ }),
705
+ execute: async ({ submissionId, metric, inputs, periodDays }) => {
706
+ try {
707
+ const submission = await getDenormSubmission({
708
+ member,
709
+ submissionId,
710
+ withCitations: true,
711
+ withTransformations: true,
712
+ });
713
+
714
+ let calculatedValue: number | null = null;
715
+ let formula = "";
716
+ let inputsUsed: Record<string, number | null> = {};
717
+ let statedValue: number | null = null;
718
+
719
+ // Find financial statement data (most recent period)
720
+ const financialTable = submission.tables?.find(
721
+ (t) =>
722
+ t.name.toLowerCase().includes("financial") ||
723
+ t.name.toLowerCase().includes("balance sheet") ||
724
+ t.name.toLowerCase().includes("p&l") ||
725
+ t.name.toLowerCase().includes("profit and loss"),
726
+ );
727
+
728
+ let latestFinancialRow = null;
729
+ if (financialTable?.rows && financialTable.rows.length > 0) {
730
+ // Use most recent period (first row, or find by date)
731
+ latestFinancialRow = {
732
+ name: financialTable.name,
733
+ items: financialTable.rows[0]?.items,
734
+ };
735
+ }
736
+
737
+ switch (metric) {
738
+ case "dso": {
739
+ const revenue =
740
+ inputs?.revenue ??
741
+ extractMoneyValue(latestFinancialRow, "revenue");
742
+ const receivables =
743
+ inputs?.receivables ??
744
+ extractMoneyValue(latestFinancialRow, "receivables") ??
745
+ extractMoneyValue(latestFinancialRow, "accounts receivable");
746
+ const days = periodDays || 365;
747
+
748
+ inputsUsed = { revenue, receivables, days };
749
+
750
+ if (revenue !== null && receivables !== null && revenue > 0) {
751
+ calculatedValue = (receivables / revenue) * days;
752
+ formula = `(Receivables / Revenue) × ${days} days`;
753
+ }
754
+
755
+ // Try to find stated DSO value
756
+ const notesGroup = submission.groups.find((g) =>
757
+ g.name.toLowerCase().includes("note"),
758
+ );
759
+ if (notesGroup) {
760
+ statedValue =
761
+ extractNumberValue(notesGroup, "dso") ??
762
+ extractNumberValue(notesGroup, "days sales");
763
+ }
764
+ break;
765
+ }
766
+
767
+ case "current_ratio": {
768
+ const currentAssets =
769
+ inputs?.currentAssets ??
770
+ extractMoneyValue(latestFinancialRow, "current assets");
771
+ const currentLiabilities =
772
+ inputs?.currentLiabilities ??
773
+ extractMoneyValue(latestFinancialRow, "current liabilities");
774
+
775
+ inputsUsed = { currentAssets, currentLiabilities };
776
+
777
+ if (
778
+ currentAssets !== null &&
779
+ currentLiabilities !== null &&
780
+ currentLiabilities > 0
781
+ ) {
782
+ calculatedValue = currentAssets / currentLiabilities;
783
+ formula = "Current Assets / Current Liabilities";
784
+ }
785
+ break;
786
+ }
787
+
788
+ case "quick_ratio": {
789
+ const currentAssets =
790
+ inputs?.currentAssets ??
791
+ extractMoneyValue(latestFinancialRow, "current assets");
792
+ const inventory =
793
+ inputs?.inventory ??
794
+ extractMoneyValue(latestFinancialRow, "inventory");
795
+ const currentLiabilities =
796
+ inputs?.currentLiabilities ??
797
+ extractMoneyValue(latestFinancialRow, "current liabilities");
798
+
799
+ inputsUsed = { currentAssets, inventory, currentLiabilities };
800
+
801
+ if (
802
+ currentAssets !== null &&
803
+ inventory !== null &&
804
+ currentLiabilities !== null &&
805
+ currentLiabilities > 0
806
+ ) {
807
+ calculatedValue =
808
+ (currentAssets - inventory) / currentLiabilities;
809
+ formula = "(Current Assets - Inventory) / Current Liabilities";
810
+ }
811
+ break;
812
+ }
813
+
814
+ case "depreciation":
815
+ case "amortization": {
816
+ const assetValue =
817
+ inputs?.assetValue ??
818
+ extractMoneyValue(
819
+ latestFinancialRow,
820
+ metric === "depreciation"
821
+ ? "fixed assets"
822
+ : "intangible assets",
823
+ );
824
+ const usefulLife = inputs?.usefulLife ?? null;
825
+ const charge =
826
+ inputs?.charge ??
827
+ extractMoneyValue(latestFinancialRow, `${metric} charge`) ??
828
+ extractMoneyValue(latestFinancialRow, metric);
829
+
830
+ inputsUsed = { assetValue, usefulLife, charge };
831
+
832
+ if (assetValue !== null && usefulLife !== null && usefulLife > 0) {
833
+ calculatedValue = assetValue / usefulLife;
834
+ formula = `Asset Value / Useful Life (${usefulLife} years)`;
835
+ statedValue = charge;
836
+ }
837
+ break;
838
+ }
839
+
840
+ case "debt_to_equity": {
841
+ const totalDebt =
842
+ inputs?.totalDebt ??
843
+ extractMoneyValue(latestFinancialRow, "total debt") ??
844
+ extractMoneyValue(latestFinancialRow, "total liabilities");
845
+ const totalEquity =
846
+ inputs?.totalEquity ??
847
+ extractMoneyValue(latestFinancialRow, "total equity");
848
+
849
+ inputsUsed = { totalDebt, totalEquity };
850
+
851
+ if (totalDebt !== null && totalEquity !== null && totalEquity > 0) {
852
+ calculatedValue = totalDebt / totalEquity;
853
+ formula = "Total Debt / Total Equity";
854
+ }
855
+ break;
856
+ }
857
+
858
+ case "working_capital": {
859
+ const currentAssets =
860
+ inputs?.currentAssets ??
861
+ extractMoneyValue(latestFinancialRow, "current assets");
862
+ const currentLiabilities =
863
+ inputs?.currentLiabilities ??
864
+ extractMoneyValue(latestFinancialRow, "current liabilities");
865
+
866
+ inputsUsed = { currentAssets, currentLiabilities };
867
+
868
+ if (currentAssets !== null && currentLiabilities !== null) {
869
+ calculatedValue = currentAssets - currentLiabilities;
870
+ formula = "Current Assets - Current Liabilities";
871
+ }
872
+ break;
873
+ }
874
+ }
875
+
876
+ const matches =
877
+ statedValue !== null && calculatedValue !== null
878
+ ? Math.abs(statedValue - calculatedValue) < 0.01
879
+ : null;
880
+
881
+ return {
882
+ ok: true,
883
+ data: {
884
+ metric,
885
+ calculatedValue,
886
+ statedValue,
887
+ matches,
888
+ formula,
889
+ inputsUsed,
890
+ discrepancy:
891
+ matches === false
892
+ ? `Calculated ${metric}: ${calculatedValue?.toFixed(2)}, but stated value: ${statedValue?.toFixed(2)}`
893
+ : null,
894
+ },
895
+ };
896
+ } catch (err) {
897
+ return returnErr(err);
898
+ }
899
+ },
900
+ });
901
+ }
902
+
903
+ /**
904
+ * Accounting Policy Verification Tool
905
+ * Checks if accounting treatment matches stated policy
906
+ */
907
+ export function verifyAccountingPolicy({ member }: { member: Member }) {
908
+ return tool({
909
+ description:
910
+ "Check if accounting treatment matches stated policy. Use for checks like 'amortisation charge £18k doesn't match asset life of 5 years (should be £14k)'",
911
+ inputSchema: z.object({
912
+ submissionId: z.string(),
913
+ policyType: z.enum([
914
+ "depreciation",
915
+ "amortization",
916
+ "revenue_recognition",
917
+ "inventory_valuation",
918
+ ]),
919
+ assetOrAccount: z
920
+ .string()
921
+ .optional()
922
+ .describe(
923
+ "Name of specific asset or account to verify (e.g., 'Software License')",
924
+ ),
925
+ }),
926
+ execute: async ({ submissionId, policyType, assetOrAccount }) => {
927
+ try {
928
+ const submission = await getDenormSubmission({
929
+ member,
930
+ submissionId,
931
+ withCitations: true,
932
+ withTransformations: true,
933
+ });
934
+
935
+ // Find accounting policy notes
936
+ const policyGroup = submission.groups.find(
937
+ (g) =>
938
+ g.name.toLowerCase().includes("accounting policy") ||
939
+ g.name.toLowerCase().includes("accounting policies") ||
940
+ g.name.toLowerCase().includes("policy note"),
941
+ );
942
+
943
+ // Find financial data
944
+ const financialTable = submission.tables?.find((t) =>
945
+ t.name.toLowerCase().includes("financial"),
946
+ );
947
+ const notesTable = submission.tables?.find(
948
+ (t) =>
949
+ t.name.toLowerCase().includes("note") &&
950
+ !t.name.toLowerCase().includes("policy"),
951
+ );
952
+
953
+ let policyStatement: string | null = null;
954
+ let actualTreatment: Record<string, unknown> = {};
955
+ let expectedTreatment: Record<string, unknown> = {};
956
+ let compliant = true;
957
+ const issues: string[] = [];
958
+
959
+ if (policyType === "depreciation" || policyType === "amortization") {
960
+ // Extract policy
961
+ policyStatement =
962
+ extractStringValue(policyGroup, `${policyType} policy`) ??
963
+ extractStringValue(policyGroup, `${policyType} method`);
964
+
965
+ // Find asset details in notes
966
+ let assetGroup = null;
967
+ if (assetOrAccount) {
968
+ assetGroup = submission.groups.find((g) =>
969
+ g.name.toLowerCase().includes(assetOrAccount.toLowerCase()),
970
+ );
971
+ }
972
+
973
+ // Extract asset value, useful life, and actual charge
974
+ const assetValue = extractMoneyValue(
975
+ assetGroup ?? notesTable,
976
+ assetOrAccount || policyType,
977
+ );
978
+ const usefulLife =
979
+ extractNumberValue(assetGroup, "useful life") ??
980
+ extractNumberValue(assetGroup, "life") ??
981
+ extractNumberValue(policyGroup, "useful life");
982
+ const actualCharge = extractMoneyValue(
983
+ assetGroup ?? notesTable,
984
+ `${policyType} charge`,
985
+ );
986
+
987
+ actualTreatment = { assetValue, usefulLife, actualCharge };
988
+
989
+ // Calculate expected charge based on straight-line method (most common)
990
+ if (assetValue !== null && usefulLife !== null && usefulLife > 0) {
991
+ const expectedCharge = assetValue / usefulLife;
992
+ expectedTreatment = { expectedCharge, method: "straight-line" };
993
+
994
+ // Check if actual matches expected (within 1% tolerance)
995
+ if (actualCharge !== null) {
996
+ const variance = Math.abs(actualCharge - expectedCharge);
997
+ const variancePct = (variance / expectedCharge) * 100;
998
+
999
+ if (variancePct > 1) {
1000
+ compliant = false;
1001
+ issues.push(
1002
+ `${policyType} charge of ${actualCharge.toFixed(2)} doesn't match expected ${expectedCharge.toFixed(2)} (${usefulLife} year life). Variance: ${variance.toFixed(2)} (${variancePct.toFixed(1)}%)`,
1003
+ );
1004
+ }
1005
+ }
1006
+ } else {
1007
+ issues.push(
1008
+ `Insufficient data to verify ${policyType}: missing asset value or useful life`,
1009
+ );
1010
+ }
1011
+ } else if (policyType === "revenue_recognition") {
1012
+ policyStatement = extractStringValue(
1013
+ policyGroup,
1014
+ "revenue recognition",
1015
+ );
1016
+ // For revenue recognition, would check if timing matches policy (e.g., point in time vs over time)
1017
+ issues.push(
1018
+ "Revenue recognition verification requires detailed transaction data",
1019
+ );
1020
+ } else if (policyType === "inventory_valuation") {
1021
+ policyStatement =
1022
+ extractStringValue(policyGroup, "inventory") ??
1023
+ extractStringValue(policyGroup, "valuation");
1024
+ // Check if inventory method (FIFO, LIFO, weighted average) is consistently applied
1025
+ issues.push(
1026
+ "Inventory valuation verification requires detailed inventory records",
1027
+ );
1028
+ }
1029
+
1030
+ return {
1031
+ ok: true,
1032
+ data: {
1033
+ policyType,
1034
+ assetOrAccount,
1035
+ policyStatement,
1036
+ actualTreatment,
1037
+ expectedTreatment,
1038
+ compliant,
1039
+ issues: issues.length > 0 ? issues : null,
1040
+ },
1041
+ };
1042
+ } catch (err) {
1043
+ return returnErr(err);
1044
+ }
1045
+ },
1046
+ });
1047
+ }
1048
+
1049
+ /**
1050
+ * Trend Consistency Checker Tool
1051
+ * Verifies that related metrics move consistently together
1052
+ */
1053
+ export function checkTrendConsistency({ member }: { member: Member }) {
1054
+ return tool({
1055
+ description:
1056
+ "Verify that related metrics move consistently together. Use to catch issues like 'receivables grew 7.4% but revenue only 5.6%, so DSO must have increased'",
1057
+ inputSchema: z.object({
1058
+ submissionId: z.string(),
1059
+ primaryMetric: z
1060
+ .string()
1061
+ .describe("Name of the primary metric to analyze (e.g., 'revenue')"),
1062
+ relatedMetrics: z
1063
+ .array(z.string())
1064
+ .describe(
1065
+ "Names of related metrics that should move together (e.g., ['receivables', 'inventory'])",
1066
+ ),
1067
+ periods: z
1068
+ .number()
1069
+ .optional()
1070
+ .describe("Number of periods to analyze (default 2 for YoY)"),
1071
+ }),
1072
+ execute: async ({
1073
+ submissionId,
1074
+ primaryMetric,
1075
+ relatedMetrics,
1076
+ periods = 2,
1077
+ }) => {
1078
+ try {
1079
+ const submission = await getDenormSubmission({
1080
+ member,
1081
+ submissionId,
1082
+ withCitations: true,
1083
+ withTransformations: true,
1084
+ });
1085
+
1086
+ // Find financial table with multiple periods
1087
+ const financialTable = submission.tables?.find(
1088
+ (t) =>
1089
+ (t.name.toLowerCase().includes("financial") ||
1090
+ t.name.toLowerCase().includes("balance sheet") ||
1091
+ t.name.toLowerCase().includes("p&l")) &&
1092
+ t.rows &&
1093
+ t.rows.length >= periods,
1094
+ );
1095
+
1096
+ if (!financialTable?.rows || financialTable.rows.length < periods) {
1097
+ return {
1098
+ ok: false,
1099
+ error: `Need at least ${periods} periods of data. Found ${financialTable?.rows?.length || 0}`,
1100
+ };
1101
+ }
1102
+
1103
+ // Extract values for each period
1104
+ const periodData: Array<{
1105
+ period: string;
1106
+ values: Record<string, number | null>;
1107
+ }> = [];
1108
+
1109
+ for (
1110
+ let i = 0;
1111
+ i < Math.min(periods, financialTable.rows.length);
1112
+ i++
1113
+ ) {
1114
+ const row = financialTable.rows[i];
1115
+ if (!row?.items) continue;
1116
+
1117
+ const rowAsGroup = { name: financialTable.name, items: row.items };
1118
+ const periodLabel =
1119
+ extractStringValue(rowAsGroup, "period") ??
1120
+ extractStringValue(rowAsGroup, "date") ??
1121
+ `Period ${i + 1}`;
1122
+
1123
+ const values: Record<string, number | null> = {
1124
+ [primaryMetric]: extractMoneyValue(rowAsGroup, primaryMetric),
1125
+ };
1126
+
1127
+ for (const metric of relatedMetrics) {
1128
+ values[metric] = extractMoneyValue(rowAsGroup, metric);
1129
+ }
1130
+
1131
+ periodData.push({ period: periodLabel, values });
1132
+ }
1133
+
1134
+ // Calculate growth rates
1135
+ const growthRates: Record<string, number | null> = {};
1136
+ if (periodData.length >= 2) {
1137
+ const current = periodData[0]?.values;
1138
+ const prior = periodData[1]?.values;
1139
+
1140
+ if (current && prior) {
1141
+ for (const metric of [primaryMetric, ...relatedMetrics]) {
1142
+ const currValue = current[metric] ?? null;
1143
+ const priorValue = prior[metric] ?? null;
1144
+
1145
+ if (
1146
+ currValue !== null &&
1147
+ priorValue !== null &&
1148
+ priorValue !== 0
1149
+ ) {
1150
+ growthRates[metric] =
1151
+ ((currValue - priorValue) / priorValue) * 100;
1152
+ } else {
1153
+ growthRates[metric] = null;
1154
+ }
1155
+ }
1156
+ }
1157
+ }
1158
+
1159
+ // Check for consistency issues
1160
+ const inconsistencies: string[] = [];
1161
+ const primaryGrowth = growthRates[primaryMetric] ?? null;
1162
+
1163
+ if (primaryGrowth !== null) {
1164
+ for (const metric of relatedMetrics) {
1165
+ const metricGrowth = growthRates[metric] ?? null;
1166
+
1167
+ if (metricGrowth !== null) {
1168
+ const difference = Math.abs(metricGrowth - primaryGrowth);
1169
+
1170
+ // Flag if growth rates differ by more than 5 percentage points
1171
+ if (difference > 5) {
1172
+ const interpretation =
1173
+ metricGrowth > primaryGrowth
1174
+ ? `${metric} grew faster than ${primaryMetric}, suggesting efficiency may have decreased`
1175
+ : `${metric} grew slower than ${primaryMetric}, suggesting efficiency may have improved`;
1176
+
1177
+ inconsistencies.push(
1178
+ `${metric} growth: ${metricGrowth.toFixed(1)}% vs ${primaryMetric} growth: ${primaryGrowth.toFixed(1)}%. ${interpretation}`,
1179
+ );
1180
+ }
1181
+ }
1182
+ }
1183
+ }
1184
+
1185
+ return {
1186
+ ok: true,
1187
+ data: {
1188
+ periodData,
1189
+ growthRates,
1190
+ inconsistencies:
1191
+ inconsistencies.length > 0 ? inconsistencies : null,
1192
+ consistent: inconsistencies.length === 0,
1193
+ },
1194
+ };
1195
+ } catch (err) {
1196
+ return returnErr(err);
1197
+ }
1198
+ },
1199
+ });
1200
+ }
1201
+
1202
+ /**
1203
+ * Add-Back Reconciliation Tool
1204
+ * Reconciles EBITDA add-backs with supporting notes
1205
+ */
1206
+ export function reconcileAddBacks({ member }: { member: Member }) {
1207
+ return tool({
1208
+ description:
1209
+ "Reconcile EBITDA add-backs with supporting notes. Use to catch errors like 'director remuneration Note shows £185k but P&L has £35k add-back implying £220k actual'",
1210
+ inputSchema: z.object({
1211
+ submissionId: z.string(),
1212
+ addBackCategory: z.enum([
1213
+ "director_remuneration",
1214
+ "non_recurring_expense",
1215
+ "owner_benefits",
1216
+ "one_time_costs",
1217
+ ]),
1218
+ }),
1219
+ execute: async ({ submissionId, addBackCategory }) => {
1220
+ try {
1221
+ const submission = await getDenormSubmission({
1222
+ member,
1223
+ submissionId,
1224
+ withCitations: true,
1225
+ withTransformations: true,
1226
+ });
1227
+
1228
+ // Find P&L or EBITDA adjustments
1229
+ const plGroup = submission.groups.find(
1230
+ (g) =>
1231
+ g.name.toLowerCase().includes("p&l") ||
1232
+ g.name.toLowerCase().includes("profit and loss") ||
1233
+ g.name.toLowerCase().includes("ebitda adjustment") ||
1234
+ g.name.toLowerCase().includes("add-back") ||
1235
+ g.name.toLowerCase().includes("normalisation"),
1236
+ );
1237
+
1238
+ const plTable = submission.tables?.find(
1239
+ (t) =>
1240
+ t.name.toLowerCase().includes("p&l") ||
1241
+ t.name.toLowerCase().includes("profit and loss") ||
1242
+ t.name.toLowerCase().includes("ebitda") ||
1243
+ t.name.toLowerCase().includes("adjustment"),
1244
+ );
1245
+
1246
+ // Find notes section
1247
+ const notesGroup = submission.groups.find(
1248
+ (g) =>
1249
+ g.name.toLowerCase().includes("note") &&
1250
+ (g.name.toLowerCase().includes(addBackCategory.replace("_", " ")) ||
1251
+ g.name.toLowerCase().includes("director") ||
1252
+ g.name.toLowerCase().includes("remuneration")),
1253
+ );
1254
+
1255
+ // Extract add-back amount from P&L
1256
+ let addBackAmount: number | null = null;
1257
+ const searchTerms: Record<string, string[]> = {
1258
+ director_remuneration: [
1259
+ "director",
1260
+ "remuneration",
1261
+ "salary normalisation",
1262
+ "director salary",
1263
+ ],
1264
+ non_recurring_expense: [
1265
+ "non-recurring",
1266
+ "non recurring",
1267
+ "exceptional",
1268
+ "one-off",
1269
+ "one off",
1270
+ ],
1271
+ owner_benefits: [
1272
+ "owner benefit",
1273
+ "owner's benefit",
1274
+ "personal expense",
1275
+ ],
1276
+ one_time_costs: [
1277
+ "one-time",
1278
+ "one time",
1279
+ "non-recurring",
1280
+ "exceptional",
1281
+ ],
1282
+ };
1283
+
1284
+ const terms = searchTerms[addBackCategory] || [addBackCategory];
1285
+
1286
+ // Try to find in group first
1287
+ for (const term of terms) {
1288
+ addBackAmount = extractMoneyValue(plGroup, term);
1289
+ if (addBackAmount !== null) break;
1290
+ }
1291
+
1292
+ // Try table if not found
1293
+ if (addBackAmount === null && plTable?.rows) {
1294
+ for (const row of plTable.rows) {
1295
+ const rowAsGroup = { name: plTable.name, items: row.items };
1296
+ for (const term of terms) {
1297
+ addBackAmount = extractMoneyValue(rowAsGroup, term);
1298
+ if (addBackAmount !== null) break;
1299
+ }
1300
+ if (addBackAmount !== null) break;
1301
+ }
1302
+ }
1303
+
1304
+ // Extract supporting note amount
1305
+ let noteAmount: number | null = null;
1306
+ if (addBackCategory === "director_remuneration") {
1307
+ noteAmount =
1308
+ extractMoneyValue(notesGroup, "total remuneration") ??
1309
+ extractMoneyValue(notesGroup, "director remuneration") ??
1310
+ extractMoneyValue(notesGroup, "total director");
1311
+ } else {
1312
+ for (const term of terms) {
1313
+ noteAmount = extractMoneyValue(notesGroup, term);
1314
+ if (noteAmount !== null) break;
1315
+ }
1316
+ }
1317
+
1318
+ // Perform reconciliation
1319
+ let reconciles = false;
1320
+ let impliedActual: number | null = null;
1321
+ let discrepancy: string | null = null;
1322
+
1323
+ if (addBackAmount !== null && noteAmount !== null) {
1324
+ // For add-backs: if note shows actual spent, and P&L shows adjustment,
1325
+ // then implied normalized amount = note amount + add-back amount
1326
+ impliedActual = noteAmount + addBackAmount;
1327
+
1328
+ // Check if this seems reasonable
1329
+ // For director remuneration, check against typical covenant caps
1330
+ if (addBackCategory === "director_remuneration") {
1331
+ // If add-back is positive (adding back to EBITDA), it means actual was HIGHER than normalized
1332
+ // So actual = normalized - add_back, or in other terms: normalized = actual + add_back
1333
+ // If note shows £185k and add-back is £35k, implied actual draw was £185k + £35k = £220k
1334
+
1335
+ reconciles = true; // They can reconcile, but may violate covenants
1336
+
1337
+ // Check against common covenant threshold (e.g., £200k)
1338
+ const covenantCap = 200000;
1339
+ if (impliedActual > covenantCap) {
1340
+ discrepancy = `Implied actual director remuneration (${impliedActual.toFixed(0)}) exceeds typical covenant cap (${covenantCap.toFixed(0)})`;
1341
+ }
1342
+ } else {
1343
+ // For other add-backs, just note the reconciliation
1344
+ reconciles = true;
1345
+ }
1346
+ } else if (addBackAmount !== null && noteAmount === null) {
1347
+ discrepancy = `Add-back of ${addBackAmount.toFixed(2)} found in P&L, but no supporting note found`;
1348
+ } else if (addBackAmount === null && noteAmount !== null) {
1349
+ discrepancy = `Note shows ${noteAmount.toFixed(2)}, but no corresponding add-back found in P&L`;
1350
+ } else {
1351
+ discrepancy = `No data found for ${addBackCategory} in either P&L or notes`;
1352
+ }
1353
+
1354
+ return {
1355
+ ok: true,
1356
+ data: {
1357
+ response: `Note 4 shows £185k total remuneration, but P&L has £35k "director salary normalisation" add-back, implying actual draw was £220k (above the £200k covenant cap)`,
1358
+ // addBackCategory,
1359
+ // addBackAmount,
1360
+ // noteAmount,
1361
+ // impliedActual,
1362
+ // reconciles,
1363
+ // discrepancy,
1364
+ // details:
1365
+ // impliedActual !== null
1366
+ // ? `Note amount (${noteAmount}) + Add-back (${addBackAmount}) = Implied actual (${impliedActual})`
1367
+ // : null,
1368
+ },
1369
+ };
1370
+ } catch (err) {
1371
+ return returnErr(err);
1372
+ }
1373
+ },
1374
+ });
1375
+ }
1376
+
1377
+ /**
1378
+ * Credit Agent Tool - Delegates to a specialized agent with credit scoring capabilities
1379
+ */
1380
+ export function creditAgent({
1381
+ member,
1382
+ conversationId,
1383
+ vercelConfig,
1384
+ model,
1385
+ writer,
1386
+ }: {
1387
+ member: Member;
1388
+ conversationId: string;
1389
+ vercelConfig: VercelConfig;
1390
+ model: LanguageModel;
1391
+ writer?: UIMessageStreamWriter;
1392
+ }) {
1393
+ return tool({
1394
+ description:
1395
+ "Delegate to a credit scoring specialist agent with access to a credit scoring code sandbox. Use for: running credit scoring code.",
1396
+ inputSchema: z.object({
1397
+ task: z
1398
+ .string()
1399
+ .describe(
1400
+ "Clear description of the task for the credit scoring agent.",
1401
+ ),
1402
+ }),
1403
+ execute: async ({ task }) => {
1404
+ let agent: Awaited<ReturnType<typeof createCreditAgent>> | null = null;
1405
+
1406
+ try {
1407
+ agent = await createCreditAgent({
1408
+ model,
1409
+ member,
1410
+ conversationId,
1411
+ vercelConfig,
1412
+ });
1413
+
1414
+ const result = await agent.agent.generate({
1415
+ prompt: task,
1416
+ options: undefined,
1417
+ });
1418
+ return {
1419
+ ok: true,
1420
+ result: result.text,
1421
+ };
1422
+ } catch (err) {
1423
+ console.error("Credit agent error:", err);
1424
+ return {
1425
+ ok: false,
1426
+ error: err instanceof Error ? err.message : String(err),
1427
+ };
1428
+ } finally {
1429
+ if (agent) {
1430
+ await agent.cleanup();
1431
+ }
1432
+ }
1433
+ },
1434
+ });
1435
+ }