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.
- package/.claude/tasks/README.md +89 -0
- package/.cursor/rules/commits.mdc +31 -0
- package/.cursor/rules/general.mdc +84 -0
- package/.github/workflows/ci-cd.yml +141 -0
- package/CLAUDE.md +337 -0
- package/README.md +129 -0
- package/apps/api/.prettierignore +6 -0
- package/apps/api/.prettierrc.js +3 -0
- package/apps/api/dotenvx-safe.sh +11 -0
- package/apps/api/eslint.config.mjs +3 -0
- package/apps/api/package.json +58 -0
- package/apps/api/src/clients/posthog.ts +25 -0
- package/apps/api/src/dal/submission.ts +59 -0
- package/apps/api/src/errors.ts +55 -0
- package/apps/api/src/index.ts +21 -0
- package/apps/api/src/lib/channel.ts +28 -0
- package/apps/api/src/lib/config.ts +9 -0
- package/apps/api/src/lib/fmt.test.ts +9 -0
- package/apps/api/src/lib/fmt.ts +62 -0
- package/apps/api/src/lib/invariant.ts +23 -0
- package/apps/api/src/middleware/auth.ts +66 -0
- package/apps/api/src/routes/index.ts +20 -0
- package/apps/api/src/routes/v2/chat/handlers.ts +693 -0
- package/apps/api/src/routes/v2/chat/index.ts +257 -0
- package/apps/api/src/routes/v2/chat/schemas.ts +43 -0
- package/apps/api/src/routes/v2/deals/handlers.ts +64 -0
- package/apps/api/src/routes/v2/deals/index.ts +88 -0
- package/apps/api/src/routes/v2/deals/schemas.ts +38 -0
- package/apps/api/src/routes/v2/forms/handlers.ts +415 -0
- package/apps/api/src/routes/v2/forms/index.ts +382 -0
- package/apps/api/src/routes/v2/forms/schemas.ts +243 -0
- package/apps/api/src/routes/v2/index.ts +19 -0
- package/apps/api/src/routes/v2/pipelines/handlers.ts +261 -0
- package/apps/api/src/routes/v2/pipelines/index.ts +224 -0
- package/apps/api/src/routes/v2/pipelines/schemas.ts +173 -0
- package/apps/api/src/routes/v2/submissions/handlers.ts +555 -0
- package/apps/api/src/routes/v2/submissions/index.ts +366 -0
- package/apps/api/src/routes/v2/submissions/schemas.ts +233 -0
- package/apps/api/src/routes/v2/workflows/handlers.ts +81 -0
- package/apps/api/src/routes/v2/workflows/index.ts +88 -0
- package/apps/api/src/routes/v2/workflows/schemas.ts +40 -0
- package/apps/api/src/server.ts +146 -0
- package/apps/api/src/static/favicon.ico +0 -0
- package/apps/api/src/types/api.ts +14 -0
- package/apps/api/src/types/result.ts +3 -0
- package/apps/api/tsconfig.json +22 -0
- package/apps/api/vite.config.ts +28 -0
- package/apps/api/vitest.config.ts +14 -0
- package/apps/conversion-worker/Dockerfile +59 -0
- package/apps/conversion-worker/package.json +31 -0
- package/apps/conversion-worker/src/lib/config.ts +7 -0
- package/apps/conversion-worker/src/main.ts +22 -0
- package/apps/conversion-worker/src/workflows/convert-pptx.ts +116 -0
- package/apps/conversion-worker/tsconfig.json +27 -0
- package/apps/conversion-worker/vite.config.ts +33 -0
- package/apps/main/.prettierignore +6 -0
- package/apps/main/.prettierrc.js +3 -0
- package/apps/main/CLAUDE.md +245 -0
- package/apps/main/Procfile +1 -0
- package/apps/main/README.md +193 -0
- package/apps/main/db-tests.jsonl +116 -0
- package/apps/main/dotenvx-safe.sh +11 -0
- package/apps/main/drizzle/meta/_journal.json +1 -0
- package/apps/main/drizzle.config.ts +25 -0
- package/apps/main/eslint.config.mjs +3 -0
- package/apps/main/generate-routes.mjs +5 -0
- package/apps/main/package.json +131 -0
- package/apps/main/playwright.config.ts +23 -0
- package/apps/main/postcss.config.ts +5 -0
- package/apps/main/public/bg-dark.svg +10 -0
- package/apps/main/public/bg.svg +10 -0
- package/apps/main/public/favicon.ico +0 -0
- package/apps/main/run.sh +146 -0
- package/apps/main/scripts/browser.ts +14 -0
- package/apps/main/scripts/db-test-cov.ts +277 -0
- package/apps/main/scripts/login.ts +78 -0
- package/apps/main/scripts/repl.ts +61 -0
- package/apps/main/src/_foo.ts +31 -0
- package/apps/main/src/_tests/db.test.ts +19 -0
- package/apps/main/src/_tests/mock-db.ts +60 -0
- package/apps/main/src/client.tsx +13 -0
- package/apps/main/src/clients/loops.ts +13 -0
- package/apps/main/src/clients/polar.ts +12 -0
- package/apps/main/src/clients/posthog.ts +12 -0
- package/apps/main/src/components/chat/chat-context.tsx +99 -0
- package/apps/main/src/components/chat/chat-messages.tsx +184 -0
- package/apps/main/src/components/chat/chat-status.tsx +140 -0
- package/apps/main/src/components/chat/chat.tsx +458 -0
- package/apps/main/src/components/chat/citation-modal.tsx +54 -0
- package/apps/main/src/components/cta.tsx +21 -0
- package/apps/main/src/components/data-display/derived.tsx +40 -0
- package/apps/main/src/components/data-display/group-single.tsx +57 -0
- package/apps/main/src/components/data-display/group-table.tsx +165 -0
- package/apps/main/src/components/data-display/group-wrapper.tsx +54 -0
- package/apps/main/src/components/data-display/item.tsx +678 -0
- package/apps/main/src/components/error.tsx +45 -0
- package/apps/main/src/components/forms/error.tsx +22 -0
- package/apps/main/src/components/grid.tsx +7 -0
- package/apps/main/src/components/header/container.tsx +73 -0
- package/apps/main/src/components/header/header-bar.tsx +102 -0
- package/apps/main/src/components/modals/copy-display.tsx +37 -0
- package/apps/main/src/components/modals/copy-form.tsx +152 -0
- package/apps/main/src/components/modals/duplicate-workflow.tsx +89 -0
- package/apps/main/src/components/modals/field-correction.tsx +323 -0
- package/apps/main/src/components/modals/form-viewer.tsx +126 -0
- package/apps/main/src/components/modals/modals.tsx +44 -0
- package/apps/main/src/components/modals/new-deal.tsx +78 -0
- package/apps/main/src/components/modals/new-form.tsx +133 -0
- package/apps/main/src/components/modals/new-pipeline.tsx +70 -0
- package/apps/main/src/components/modals/new-submission.tsx +321 -0
- package/apps/main/src/components/modals/new-workflow.tsx +342 -0
- package/apps/main/src/components/modals/transformation-sources-modal.tsx +157 -0
- package/apps/main/src/components/modals/view-report.tsx +193 -0
- package/apps/main/src/components/not-found.tsx +14 -0
- package/apps/main/src/components/search/search-bar.tsx +178 -0
- package/apps/main/src/components/sheet-selector.tsx +135 -0
- package/apps/main/src/components/side-panel/doc-list.tsx +480 -0
- package/apps/main/src/components/sidebar/admin-sidebar.tsx +75 -0
- package/apps/main/src/components/sidebar/app-sidebar.tsx +417 -0
- package/apps/main/src/components/sidebar/model-select.tsx +134 -0
- package/apps/main/src/components/sidebar/settings-sidebar.tsx +132 -0
- package/apps/main/src/components/sidebar/sidebar-right.tsx +22 -0
- package/apps/main/src/components/sidebar/stop-impersonate.tsx +21 -0
- package/apps/main/src/components/svg/loading.tsx +33 -0
- package/apps/main/src/components/theme-selector.tsx +43 -0
- package/apps/main/src/components/unsaved-badge.tsx +19 -0
- package/apps/main/src/components/upload/file-upload.tsx +354 -0
- package/apps/main/src/fns/submission-groups.ts +28 -0
- package/apps/main/src/fns/submission-items.ts +11 -0
- package/apps/main/src/global-middleware.ts +16 -0
- package/apps/main/src/hooks/use-update-state.ts +18 -0
- package/apps/main/src/lib/auth-client.ts +16 -0
- package/apps/main/src/lib/auth.test.ts +359 -0
- package/apps/main/src/lib/auth.ts +144 -0
- package/apps/main/src/lib/billing.ts +23 -0
- package/apps/main/src/lib/config-iso.ts +76 -0
- package/apps/main/src/lib/config.ts +61 -0
- package/apps/main/src/lib/excel.ts +16 -0
- package/apps/main/src/lib/feedback-cache.ts +70 -0
- package/apps/main/src/lib/logger.ts +44 -0
- package/apps/main/src/lib/models.ts +22 -0
- package/apps/main/src/lib/not-found.ts +17 -0
- package/apps/main/src/lib/pdf.ts +16 -0
- package/apps/main/src/lib/tabularize.ts +54 -0
- package/apps/main/src/lib/utils.ts +10 -0
- package/apps/main/src/lib/zfd.ts +217 -0
- package/apps/main/src/middleware.ts +55 -0
- package/apps/main/src/routeTree.gen.ts +1255 -0
- package/apps/main/src/router.tsx +24 -0
- package/apps/main/src/routes/__root.tsx +92 -0
- package/apps/main/src/routes/_authed/_app/(dashboard)/index.tsx +227 -0
- package/apps/main/src/routes/_authed/_app/agents/$agentId/config.tsx +224 -0
- package/apps/main/src/routes/_authed/_app/agents/$agentId/index.tsx +206 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-actions-menu.tsx +94 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-artifacts.tsx +153 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-chat.tsx +220 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-history-menu.tsx +81 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-model-select.tsx +84 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-relevant-items.tsx +226 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/agent-upload-button.tsx +298 -0
- package/apps/main/src/routes/_authed/_app/agents/-components/context-modal.tsx +187 -0
- package/apps/main/src/routes/_authed/_app/agents/-fns.ts +560 -0
- package/apps/main/src/routes/_authed/_app/agents/index.tsx +65 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/$subId/-components/citation-tree.tsx +268 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/$subId.tsx +655 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/doc-loading.tsx +37 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/share-link.tsx +42 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/submission-card.tsx +89 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/submission-filter.tsx +193 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/submissions.tsx +36 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/summary.tsx +82 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-components/upload-doc.tsx +120 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/-fns.ts +653 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/index.tsx +259 -0
- package/apps/main/src/routes/_authed/_app/deals/$dealId/route.tsx +29 -0
- package/apps/main/src/routes/_authed/_app/deals/index.tsx +104 -0
- package/apps/main/src/routes/_authed/_app/feedback/index.tsx +639 -0
- package/apps/main/src/routes/_authed/_app/feedback/insights.tsx +250 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/blockers-panel.tsx +260 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/manual-input-panel.tsx +301 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/submission-selector-modal.tsx +143 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/-components/upload-doc.tsx +120 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/$runId/index.tsx +1485 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/-components/dag-view.tsx +296 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/-components/step-config-modal.tsx +634 -0
- package/apps/main/src/routes/_authed/_app/pipelines/$pipelineId/index.tsx +911 -0
- package/apps/main/src/routes/_authed/_app/pipelines/-fns.ts +510 -0
- package/apps/main/src/routes/_authed/_app/pipelines/index.tsx +103 -0
- package/apps/main/src/routes/_authed/_app/reports/$reportId.tsx +397 -0
- package/apps/main/src/routes/_authed/_app/reports/-fns.ts +11 -0
- package/apps/main/src/routes/_authed/_app/reports/index.tsx +22 -0
- package/apps/main/src/routes/_authed/_app/route.tsx +48 -0
- package/apps/main/src/routes/_authed/_app/submissions/-columns.tsx +161 -0
- package/apps/main/src/routes/_authed/_app/submissions/-fns.ts +128 -0
- package/apps/main/src/routes/_authed/_app/submissions/index.tsx +190 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/$formId.tsx +542 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/-components/derived.tsx +154 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/-components/field.tsx +369 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/-components/group.tsx +475 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/index.tsx +263 -0
- package/apps/main/src/routes/_authed/_app/workflows/$wfSlug/route.tsx +33 -0
- package/apps/main/src/routes/_authed/_app/workflows/-components/form-card.tsx +315 -0
- package/apps/main/src/routes/_authed/_app/workflows/index.tsx +86 -0
- package/apps/main/src/routes/_authed/admin/index.tsx +12 -0
- package/apps/main/src/routes/_authed/admin/route.tsx +42 -0
- package/apps/main/src/routes/_authed/admin/users/-columns.tsx +124 -0
- package/apps/main/src/routes/_authed/admin/users/-fns.ts +30 -0
- package/apps/main/src/routes/_authed/admin/users/index.tsx +29 -0
- package/apps/main/src/routes/_authed/catchNotFound.tsx +114 -0
- package/apps/main/src/routes/_authed/redirects/forms.$id.tsx +29 -0
- package/apps/main/src/routes/_authed/redirects/submissions.$id.tsx +27 -0
- package/apps/main/src/routes/_authed/redirects/workflows.$id.tsx +27 -0
- package/apps/main/src/routes/_authed/route.tsx +51 -0
- package/apps/main/src/routes/_authed/settings/-components/new-api-key.tsx +85 -0
- package/apps/main/src/routes/_authed/settings/-components/new-invite.tsx +100 -0
- package/apps/main/src/routes/_authed/settings/analytics.tsx +1710 -0
- package/apps/main/src/routes/_authed/settings/billing/-components/price-table.tsx +129 -0
- package/apps/main/src/routes/_authed/settings/billing/-fns.ts +76 -0
- package/apps/main/src/routes/_authed/settings/billing/index.tsx +119 -0
- package/apps/main/src/routes/_authed/settings/embed.tsx +337 -0
- package/apps/main/src/routes/_authed/settings/index.tsx +12 -0
- package/apps/main/src/routes/_authed/settings/keys.tsx +157 -0
- package/apps/main/src/routes/_authed/settings/members.tsx +276 -0
- package/apps/main/src/routes/_authed/settings/route.tsx +22 -0
- package/apps/main/src/routes/_authed/settings/user.tsx +87 -0
- package/apps/main/src/routes/_authed/settings/workspace.tsx +206 -0
- package/apps/main/src/routes/_public/-components/sign-in-up.tsx +96 -0
- package/apps/main/src/routes/_public/embedded.tsx +57 -0
- package/apps/main/src/routes/_public/invite.$inviteId.tsx +143 -0
- package/apps/main/src/routes/_public/no-access.tsx +38 -0
- package/apps/main/src/routes/_public/no-invite.tsx +39 -0
- package/apps/main/src/routes/_public/otp.tsx +103 -0
- package/apps/main/src/routes/_public/route.tsx +15 -0
- package/apps/main/src/routes/_public/sign-in.tsx +111 -0
- package/apps/main/src/routes/_public/sign-up.tsx +114 -0
- package/apps/main/src/routes/api/auth/$.ts +11 -0
- package/apps/main/src/routes/api/billing/paid.ts +42 -0
- package/apps/main/src/routes/api/billing/webhooks.ts +70 -0
- package/apps/main/src/routes/api/chat/agent.ts +40 -0
- package/apps/main/src/routes/api/chat/key.ts +42 -0
- package/apps/main/src/routes/api/chat/member.ts +35 -0
- package/apps/main/src/routes/api/test/index.ts +19 -0
- package/apps/main/src/server.tsx +6 -0
- package/apps/main/src/styles/app.css +23 -0
- package/apps/main/src/vite-env.d.ts +7 -0
- package/apps/main/test.http +6 -0
- package/apps/main/tsconfig.json +17 -0
- package/apps/main/vite.config.ts +24 -0
- package/apps/main/vitest.config.js +17 -0
- package/apps/mcp/README.md +171 -0
- package/apps/mcp/eslint.config.mjs +3 -0
- package/apps/mcp/package.json +37 -0
- package/apps/mcp/src/index.ts +414 -0
- package/apps/mcp/tsconfig.json +19 -0
- package/apps/mcp/vite.config.ts +22 -0
- package/apps/posthog-proxy/index.html +9 -0
- package/apps/workers/.prettierignore +7 -0
- package/apps/workers/.prettierrc.js +3 -0
- package/apps/workers/dotenvx-safe.sh +11 -0
- package/apps/workers/eslint.config.mjs +3 -0
- package/apps/workers/package.json +65 -0
- package/apps/workers/src/lib/config.ts +7 -0
- package/apps/workers/src/lib/messages.ts +0 -0
- package/apps/workers/src/lib/posthog.ts +25 -0
- package/apps/workers/src/main.ts +58 -0
- package/apps/workers/src/workflows/extraction.ts +866 -0
- package/apps/workers/src/workflows/index.ts +3 -0
- package/apps/workers/src/workflows/pipeline-dag.ts +210 -0
- package/apps/workers/src/workflows/pipeline-steps.ts +1393 -0
- package/apps/workers/tsconfig.json +16 -0
- package/apps/workers/vite.config.ts +35 -0
- package/docs/CHANGELOG.md +84 -0
- package/docs/agent-templates-and-runs.md +859 -0
- package/docs/aws-migration-plan.md +267 -0
- package/docs/impl-p0-form-builder-improvements.md +683 -0
- package/docs/on-prem-deployment-spec.docx +0 -0
- package/docs/on-prem-deployment-spec.md +378 -0
- package/docs/prd-form-builder-strategy.md +1120 -0
- package/docs/widget-ng-apf-packaging-spec.md +43 -0
- package/infra/k8s/charts/seadotdev/Chart.yaml +6 -0
- package/infra/k8s/charts/seadotdev/templates/_helpers.tpl +27 -0
- package/infra/k8s/charts/seadotdev/templates/api-v2.yaml +105 -0
- package/infra/k8s/charts/seadotdev/templates/external-secrets.yaml +83 -0
- package/infra/k8s/charts/seadotdev/templates/ingress.yaml +54 -0
- package/infra/k8s/charts/seadotdev/templates/main-app.yaml +104 -0
- package/infra/k8s/charts/seadotdev/templates/workers.yaml +182 -0
- package/infra/k8s/charts/seadotdev/values.yaml +143 -0
- package/infra/terraform/main.tf +399 -0
- package/libs/ai/.prettierignore +2 -0
- package/libs/ai/.prettierrc.js +5 -0
- package/libs/ai/README.md +139 -0
- package/libs/ai/eslint.config.mjs +3 -0
- package/libs/ai/package.json +42 -0
- package/libs/ai/src/index.ts +5 -0
- package/libs/ai/src/models.ts +19 -0
- package/libs/ai/src/rag/index.ts +1 -0
- package/libs/ai/src/rag/rag.test.ts +99 -0
- package/libs/ai/src/rag/rag.ts +510 -0
- package/libs/ai/tsconfig.json +21 -0
- package/libs/ai/vite.config.ts +38 -0
- package/libs/cache/.prettierignore +2 -0
- package/libs/cache/eslint.config.mjs +3 -0
- package/libs/cache/package.json +35 -0
- package/libs/cache/src/feedback.ts +77 -0
- package/libs/cache/src/index.ts +2 -0
- package/libs/cache/tsconfig.json +19 -0
- package/libs/cache/vite.config.ts +36 -0
- package/libs/clients/.prettierignore +6 -0
- package/libs/clients/eslint.config.mjs +3 -0
- package/libs/clients/package.json +59 -0
- package/libs/clients/src/azure.ts +249 -0
- package/libs/clients/src/gcp.ts +220 -0
- package/libs/clients/src/hatchet.ts +86 -0
- package/libs/clients/src/index.ts +8 -0
- package/libs/clients/src/loops.ts +86 -0
- package/libs/clients/src/polar.ts +77 -0
- package/libs/clients/src/posthog.ts +55 -0
- package/libs/clients/tsconfig.json +19 -0
- package/libs/clients/vite.config.ts +35 -0
- package/libs/config/.prettierignore +6 -0
- package/libs/config/.prettierrc.js +12 -0
- package/libs/config/eslint.config.mjs +3 -0
- package/libs/config/package.json +50 -0
- package/libs/config/src/azure.ts +54 -0
- package/libs/config/src/db.ts +18 -0
- package/libs/config/src/gcp.ts +53 -0
- package/libs/config/src/google.ts +17 -0
- package/libs/config/src/hatchet.ts +20 -0
- package/libs/config/src/index.ts +108 -0
- package/libs/config/src/llm.ts +17 -0
- package/libs/config/src/polar.ts +24 -0
- package/libs/config/src/util.ts +8 -0
- package/libs/config/src/vercel.ts +26 -0
- package/libs/config/tsconfig.json +19 -0
- package/libs/config/vite.config.ts +34 -0
- package/libs/core/.prettierignore +2 -0
- package/libs/core/eslint.config.mjs +3 -0
- package/libs/core/package.json +59 -0
- package/libs/core/src/chat/derived.ts +97 -0
- package/libs/core/src/chat/feedback.ts +293 -0
- package/libs/core/src/chat/index.ts +6 -0
- package/libs/core/src/chat/model.ts +92 -0
- package/libs/core/src/chat/prepare-tools.ts +286 -0
- package/libs/core/src/chat/prompts.ts +623 -0
- package/libs/core/src/chat/stream.ts +311 -0
- package/libs/core/src/chat/summarize.ts +168 -0
- package/libs/core/src/chat/tools/agent.ts +403 -0
- package/libs/core/src/chat/tools/chart-agent.ts +526 -0
- package/libs/core/src/chat/tools/chart-helpers/sandbox.ts +47 -0
- package/libs/core/src/chat/tools/chart.ts +86 -0
- package/libs/core/src/chat/tools/credit-agent.ts +1383 -0
- package/libs/core/src/chat/tools/credit.ts +1435 -0
- package/libs/core/src/chat/tools/deep-dive-agent.ts +100 -0
- package/libs/core/src/chat/tools/deep-dive.ts +141 -0
- package/libs/core/src/chat/tools/form.ts +449 -0
- package/libs/core/src/chat/tools/helpers.ts +91 -0
- package/libs/core/src/chat/tools/index.ts +42 -0
- package/libs/core/src/chat/tools/pipeline-artifact.ts +76 -0
- package/libs/core/src/chat/tools/report.ts +40 -0
- package/libs/core/src/chat/tools/search.ts +390 -0
- package/libs/core/src/chat/tools/submission.ts +227 -0
- package/libs/core/src/chat/tools/workflow.ts +684 -0
- package/libs/core/src/chat/types.ts +3 -0
- package/libs/core/src/data-extraction/classification/azure.ts +168 -0
- package/libs/core/src/data-extraction/classification/index.ts +1 -0
- package/libs/core/src/data-extraction/dal.ts +246 -0
- package/libs/core/src/data-extraction/form-structure-extractor.ts +294 -0
- package/libs/core/src/data-extraction/index.ts +4 -0
- package/libs/core/src/data-extraction/layout/azure.ts +730 -0
- package/libs/core/src/data-extraction/layout/excel.ts +180 -0
- package/libs/core/src/data-extraction/layout/gcp.ts +1071 -0
- package/libs/core/src/data-extraction/layout/index.ts +266 -0
- package/libs/core/src/data-extraction/layout/plaintext.ts +45 -0
- package/libs/core/src/data-extraction/models.ts +38 -0
- package/libs/core/src/data-extraction/pdf-utils.ts +96 -0
- package/libs/core/src/data-extraction/structuring/bank-statement.ts +1182 -0
- package/libs/core/src/data-extraction/structuring/custom.ts +495 -0
- package/libs/core/src/data-extraction/structuring/index.ts +290 -0
- package/libs/core/src/data-extraction/structuring/prompts.ts +69 -0
- package/libs/core/src/data-extraction/type-guards.ts +110 -0
- package/libs/core/src/data-extraction/types.ts +84 -0
- package/libs/core/src/data-extraction/utils.ts +31 -0
- package/libs/core/src/data-extraction/validation/bank-statement.ts +127 -0
- package/libs/core/src/deals.ts +17 -0
- package/libs/core/src/documents.ts +152 -0
- package/libs/core/src/index.ts +5 -0
- package/libs/core/src/pipelines/display.ts +678 -0
- package/libs/core/src/pipelines/execute.ts +2342 -0
- package/libs/core/src/pipelines/index.ts +4 -0
- package/libs/core/src/pipelines/list.ts +12 -0
- package/libs/core/src/pipelines/runs.ts +53 -0
- package/libs/core/tsconfig.json +20 -0
- package/libs/core/vite.config.ts +56 -0
- package/libs/dal/.prettierignore +6 -0
- package/libs/dal/.prettierrc.js +12 -0
- package/libs/dal/eslint.config.mjs +3 -0
- package/libs/dal/package.json +57 -0
- package/libs/dal/src/_tests/db.test.ts +19 -0
- package/libs/dal/src/_tests/mock-db.ts +60 -0
- package/libs/dal/src/api-key.test.ts +397 -0
- package/libs/dal/src/api-key.ts +110 -0
- package/libs/dal/src/billing.ts +23 -0
- package/libs/dal/src/conversation.test.ts +655 -0
- package/libs/dal/src/conversation.ts +532 -0
- package/libs/dal/src/deal.test.ts +45 -0
- package/libs/dal/src/deal.ts +87 -0
- package/libs/dal/src/defaults-consumer-lending-uk.ts +33 -0
- package/libs/dal/src/defaults-consumer-lending-us.ts +33 -0
- package/libs/dal/src/defaults-private-credit.ts +57 -0
- package/libs/dal/src/defaults-private-equity.ts +51 -0
- package/libs/dal/src/defaults-smb-lending-us.ts +1569 -0
- package/libs/dal/src/defaults-sme-lending-uk-express.ts +1527 -0
- package/libs/dal/src/defaults-sme-lending-uk.ts +1669 -0
- package/libs/dal/src/defaults-types.ts +23 -0
- package/libs/dal/src/defaults.ts +550 -0
- package/libs/dal/src/document.test.ts +70 -0
- package/libs/dal/src/document.ts +192 -0
- package/libs/dal/src/feedback.ts +255 -0
- package/libs/dal/src/form.test.ts +637 -0
- package/libs/dal/src/form.ts +1165 -0
- package/libs/dal/src/index.ts +20 -0
- package/libs/dal/src/invitation.test.ts +746 -0
- package/libs/dal/src/invitation.ts +207 -0
- package/libs/dal/src/member.test.ts +185 -0
- package/libs/dal/src/member.ts +80 -0
- package/libs/dal/src/organization.ts +116 -0
- package/libs/dal/src/permission.ts +25 -0
- package/libs/dal/src/pipeline.test.ts +388 -0
- package/libs/dal/src/pipeline.ts +4222 -0
- package/libs/dal/src/report.ts +199 -0
- package/libs/dal/src/result.ts +16 -0
- package/libs/dal/src/search.ts +172 -0
- package/libs/dal/src/session.test.ts +110 -0
- package/libs/dal/src/session.ts +31 -0
- package/libs/dal/src/submission.test.ts +1304 -0
- package/libs/dal/src/submission.ts +1396 -0
- package/libs/dal/src/tool.ts +159 -0
- package/libs/dal/src/user.ts +16 -0
- package/libs/dal/src/workflow.test.ts +89 -0
- package/libs/dal/src/workflow.ts +262 -0
- package/libs/dal/tsconfig.build.json +4 -0
- package/libs/dal/tsconfig.json +22 -0
- package/libs/dal/vite.config.ts +34 -0
- package/libs/db/.prettierignore +6 -0
- package/libs/db/.prettierrc.js +12 -0
- package/libs/db/eslint.config.mjs +3 -0
- package/libs/db/package.json +52 -0
- package/libs/db/src/index.ts +24 -0
- package/libs/db/src/relations.ts +549 -0
- package/libs/db/src/schema.ts +2 -0
- package/libs/db/src/schemas/api.ts +35 -0
- package/libs/db/src/schemas/conversations.ts +175 -0
- package/libs/db/src/schemas/core.ts +359 -0
- package/libs/db/src/schemas/documents.ts +181 -0
- package/libs/db/src/schemas/feedback.ts +40 -0
- package/libs/db/src/schemas/index.ts +26 -0
- package/libs/db/src/schemas/organisations.ts +97 -0
- package/libs/db/src/schemas/pipelines.ts +440 -0
- package/libs/db/src/schemas/users.ts +95 -0
- package/libs/db/src/types.ts +190 -0
- package/libs/db/src/utils.ts +14 -0
- package/libs/db/tsconfig.json +19 -0
- package/libs/db/vite.config.ts +31 -0
- package/libs/lint/.prettierignore +6 -0
- package/libs/lint/eslint.config.mjs +61 -0
- package/libs/lint/package.json +29 -0
- package/libs/lint/prettier.config.js +12 -0
- package/libs/schemas/.prettierignore +6 -0
- package/libs/schemas/.prettierrc.js +12 -0
- package/libs/schemas/README.md +15 -0
- package/libs/schemas/eslint.config.mjs +3 -0
- package/libs/schemas/package.json +67 -0
- package/libs/schemas/src/core/chat.ts +67 -0
- package/libs/schemas/src/core/core-result.ts +15 -0
- package/libs/schemas/src/core/data-extraction.ts +184 -0
- package/libs/schemas/src/core/layout.ts +478 -0
- package/libs/schemas/src/core/pipeline.ts +128 -0
- package/libs/schemas/src/core/submission.ts +97 -0
- package/libs/schemas/src/db/account.ts +57 -0
- package/libs/schemas/src/db/apiKey.ts +57 -0
- package/libs/schemas/src/db/context.ts +33 -0
- package/libs/schemas/src/db/conversation.ts +65 -0
- package/libs/schemas/src/db/deal.ts +42 -0
- package/libs/schemas/src/db/document.ts +103 -0
- package/libs/schemas/src/db/documentCitation.ts +58 -0
- package/libs/schemas/src/db/documentExtraction.ts +69 -0
- package/libs/schemas/src/db/fieldCorrection.ts +85 -0
- package/libs/schemas/src/db/form.ts +45 -0
- package/libs/schemas/src/db/formField.ts +59 -0
- package/libs/schemas/src/db/formGroup.ts +42 -0
- package/libs/schemas/src/db/impersonation.ts +39 -0
- package/libs/schemas/src/db/index.ts +25 -0
- package/libs/schemas/src/db/invitation.ts +42 -0
- package/libs/schemas/src/db/member.ts +36 -0
- package/libs/schemas/src/db/message.ts +58 -0
- package/libs/schemas/src/db/organization.ts +62 -0
- package/libs/schemas/src/db/session.ts +48 -0
- package/libs/schemas/src/db/submission.ts +54 -0
- package/libs/schemas/src/db/submissionGroup.ts +36 -0
- package/libs/schemas/src/db/submissionItem.ts +33 -0
- package/libs/schemas/src/db/submissionItemVersion.ts +70 -0
- package/libs/schemas/src/db/user.ts +51 -0
- package/libs/schemas/src/db/utils.ts +3 -0
- package/libs/schemas/src/db/verification.ts +36 -0
- package/libs/schemas/src/db/workflow.ts +42 -0
- package/libs/schemas/src/index.ts +10 -0
- package/libs/schemas/tsconfig.json +21 -0
- package/libs/schemas/vite.config.ts +38 -0
- package/libs/ui/.prettierignore +6 -0
- package/libs/ui/.prettierrc.js +12 -0
- package/libs/ui/components.json +24 -0
- package/libs/ui/eslint.config.mjs +3 -0
- package/libs/ui/package.json +142 -0
- package/libs/ui/src/components/chart-viz/chart.tsx +255 -0
- package/libs/ui/src/components/chart-viz/converters.ts +474 -0
- package/libs/ui/src/components/chart-viz/dashboard.tsx +146 -0
- package/libs/ui/src/components/chart-viz/index.ts +37 -0
- package/libs/ui/src/components/chart-viz/markdown.tsx +344 -0
- package/libs/ui/src/components/chart-viz/table.tsx +446 -0
- package/libs/ui/src/components/chart-viz/theme-context.tsx +70 -0
- package/libs/ui/src/components/chart-viz/themes/dark.ts +98 -0
- package/libs/ui/src/components/chart-viz/themes/index.ts +69 -0
- package/libs/ui/src/components/chart-viz/themes/light.ts +98 -0
- package/libs/ui/src/components/chart-viz/themes/tailwind.ts +326 -0
- package/libs/ui/src/components/chart-viz/themes/types.ts +99 -0
- package/libs/ui/src/components/chart-viz/tool-display.tsx +150 -0
- package/libs/ui/src/components/chart-viz/types.ts +95 -0
- package/libs/ui/src/components/doc-viewers/excel/index.tsx +431 -0
- package/libs/ui/src/components/doc-viewers/excel/themes.ts +160 -0
- package/libs/ui/src/components/doc-viewers/image/index.tsx +410 -0
- package/libs/ui/src/components/doc-viewers/pdf/index.tsx +258 -0
- package/libs/ui/src/components/doc-viewers/pdf/virtualized-pdf.tsx +556 -0
- package/libs/ui/src/components/misc/rel-date.tsx +52 -0
- package/libs/ui/src/components/misc/styled-link.tsx +2 -0
- package/libs/ui/src/components/table/data-table.tsx +546 -0
- package/libs/ui/src/components/table/report-table.tsx +305 -0
- package/libs/ui/src/components/table/sortable-column.tsx +34 -0
- package/libs/ui/src/components/ui/accordion.tsx +62 -0
- package/libs/ui/src/components/ui/alert-dialog.tsx +142 -0
- package/libs/ui/src/components/ui/alert.tsx +62 -0
- package/libs/ui/src/components/ui/artifact.tsx +118 -0
- package/libs/ui/src/components/ui/attachments.tsx +388 -0
- package/libs/ui/src/components/ui/avatar.tsx +39 -0
- package/libs/ui/src/components/ui/badge.tsx +43 -0
- package/libs/ui/src/components/ui/breadcrumb.tsx +102 -0
- package/libs/ui/src/components/ui/button-group.tsx +78 -0
- package/libs/ui/src/components/ui/button.tsx +79 -0
- package/libs/ui/src/components/ui/card.tsx +32 -0
- package/libs/ui/src/components/ui/carousel.tsx +228 -0
- package/libs/ui/src/components/ui/chain-of-thought.tsx +198 -0
- package/libs/ui/src/components/ui/checkbox.tsx +27 -0
- package/libs/ui/src/components/ui/citation.tsx +34 -0
- package/libs/ui/src/components/ui/code-block.tsx +500 -0
- package/libs/ui/src/components/ui/collapsible.tsx +19 -0
- package/libs/ui/src/components/ui/command.tsx +161 -0
- package/libs/ui/src/components/ui/conversation.tsx +90 -0
- package/libs/ui/src/components/ui/dialog.tsx +142 -0
- package/libs/ui/src/components/ui/dropdown-menu.tsx +246 -0
- package/libs/ui/src/components/ui/highlight.tsx +3 -0
- package/libs/ui/src/components/ui/hover-card.tsx +36 -0
- package/libs/ui/src/components/ui/inline-citation.tsx +251 -0
- package/libs/ui/src/components/ui/input-group.tsx +156 -0
- package/libs/ui/src/components/ui/input-otp.tsx +78 -0
- package/libs/ui/src/components/ui/input.tsx +21 -0
- package/libs/ui/src/components/ui/label.tsx +19 -0
- package/libs/ui/src/components/ui/model-selector.tsx +174 -0
- package/libs/ui/src/components/ui/multisidebar.tsx +750 -0
- package/libs/ui/src/components/ui/popover.tsx +43 -0
- package/libs/ui/src/components/ui/progress.tsx +28 -0
- package/libs/ui/src/components/ui/reasoning.tsx +178 -0
- package/libs/ui/src/components/ui/resizable.tsx +49 -0
- package/libs/ui/src/components/ui/scroll-area.tsx +54 -0
- package/libs/ui/src/components/ui/select.tsx +171 -0
- package/libs/ui/src/components/ui/separator.tsx +26 -0
- package/libs/ui/src/components/ui/sheet.tsx +128 -0
- package/libs/ui/src/components/ui/shimmer.tsx +53 -0
- package/libs/ui/src/components/ui/skeleton.tsx +13 -0
- package/libs/ui/src/components/ui/sonner.tsx +23 -0
- package/libs/ui/src/components/ui/switch.tsx +26 -0
- package/libs/ui/src/components/ui/table.tsx +96 -0
- package/libs/ui/src/components/ui/tabs.tsx +52 -0
- package/libs/ui/src/components/ui/textarea.tsx +41 -0
- package/libs/ui/src/components/ui/tool.tsx +209 -0
- package/libs/ui/src/components/ui/tooltip.tsx +58 -0
- package/libs/ui/src/components/ui/typography.tsx +113 -0
- package/libs/ui/src/fonts/manrope-v15-latin-300.woff2 +0 -0
- package/libs/ui/src/fonts/manrope-v15-latin-400.woff2 +0 -0
- package/libs/ui/src/fonts/manrope-v15-latin-500.woff2 +0 -0
- package/libs/ui/src/fonts/manrope-v15-latin-600.woff2 +0 -0
- package/libs/ui/src/hooks/use-mobile.ts +19 -0
- package/libs/ui/src/lib/utils.ts +6 -0
- package/libs/ui/src/styles/fonts.css +35 -0
- package/libs/ui/src/styles/style.css +218 -0
- package/libs/ui/tsconfig.json +21 -0
- package/libs/ui/vite.config.ts +80 -0
- package/libs/ui-lit/README.md +245 -0
- package/libs/ui-lit/TESTING_GUIDE.md +296 -0
- package/libs/ui-lit/eslint.config.mjs +3 -0
- package/libs/ui-lit/package.json +41 -0
- package/libs/ui-lit/scripts/build-css.js +43 -0
- package/libs/ui-lit/src/components/sea-alert.ts +132 -0
- package/libs/ui-lit/src/components/sea-button.ts +95 -0
- package/libs/ui-lit/src/components/sea-card.ts +113 -0
- package/libs/ui-lit/src/components/sea-input.ts +184 -0
- package/libs/ui-lit/src/components/sea-spinner.ts +65 -0
- package/libs/ui-lit/src/index.ts +15 -0
- package/libs/ui-lit/src/lib/utils.ts +6 -0
- package/libs/ui-lit/src/styles/tailwind.css +76 -0
- package/libs/ui-lit/src/theme.css +66 -0
- package/libs/ui-lit/src/theme.ts +79 -0
- package/libs/ui-lit/src/vite-env.d.ts +6 -0
- package/libs/ui-lit/tailwind.config.ts +50 -0
- package/libs/ui-lit/test.html +289 -0
- package/libs/ui-lit/tsconfig.json +23 -0
- package/libs/ui-lit/vite.config.ts +31 -0
- package/libs/ui-lit/vite.css.config.ts +20 -0
- package/libs/util/.prettierignore +6 -0
- package/libs/util/.prettierrc.js +12 -0
- package/libs/util/eslint.config.mjs +3 -0
- package/libs/util/package.json +45 -0
- package/libs/util/src/billing.ts +10 -0
- package/libs/util/src/data-transform.ts +19 -0
- package/libs/util/src/encryption.ts +45 -0
- package/libs/util/src/fmt.test.ts +9 -0
- package/libs/util/src/fmt.ts +71 -0
- package/libs/util/src/fuzzy.ts +47 -0
- package/libs/util/src/id.ts +24 -0
- package/libs/util/src/invariant.ts +31 -0
- package/libs/util/src/sub-name.ts +7 -0
- package/libs/util/tsconfig.json +19 -0
- package/libs/util/vite.config.ts +34 -0
- package/package.json +28 -0
- package/packages/widget/.prettierignore +6 -0
- package/packages/widget/.prettierrc.js +12 -0
- package/packages/widget/README.md +95 -0
- package/packages/widget/eslint.config.mjs +11 -0
- package/packages/widget/openapi-ts.config.ts +8 -0
- package/packages/widget/package.json +89 -0
- package/packages/widget/postcss.config.mjs +10 -0
- package/packages/widget/src/clients/api/client/client.ts +187 -0
- package/packages/widget/src/clients/api/client/index.ts +22 -0
- package/packages/widget/src/clients/api/client/types.ts +192 -0
- package/packages/widget/src/clients/api/client/utils.ts +394 -0
- package/packages/widget/src/clients/api/client.gen.ts +18 -0
- package/packages/widget/src/clients/api/core/auth.ts +39 -0
- package/packages/widget/src/clients/api/core/bodySerializer.ts +74 -0
- package/packages/widget/src/clients/api/core/params.ts +132 -0
- package/packages/widget/src/clients/api/core/pathSerializer.ts +169 -0
- package/packages/widget/src/clients/api/core/types.ts +80 -0
- package/packages/widget/src/clients/api/index.ts +3 -0
- package/packages/widget/src/clients/api/sdk.gen.ts +805 -0
- package/packages/widget/src/clients/api/types.gen.ts +2085 -0
- package/packages/widget/src/components/container.tsx +42 -0
- package/packages/widget/src/components/data-display.tsx +384 -0
- package/packages/widget/src/components/data-viewer.tsx +311 -0
- package/packages/widget/src/components/doc-list.tsx +102 -0
- package/packages/widget/src/components/field-correction-modal.tsx +265 -0
- package/packages/widget/src/components/header.tsx +71 -0
- package/packages/widget/src/components/new-submission.tsx +290 -0
- package/packages/widget/src/components/sidebar-right.tsx +19 -0
- package/packages/widget/src/components/submission-card.tsx +66 -0
- package/packages/widget/src/components/submission-page.tsx +75 -0
- package/packages/widget/src/components/upload-doc.tsx +241 -0
- package/packages/widget/src/components/widget.tsx +101 -0
- package/packages/widget/src/index.tsx +167 -0
- package/packages/widget/src/lib/config.ts +2 -0
- package/packages/widget/src/lib/util.ts +40 -0
- package/packages/widget/src/styles/index.css +5 -0
- package/packages/widget/src/styles/tw-properties.css +337 -0
- package/packages/widget/src/vite-env.d.ts +3 -0
- package/packages/widget/tsconfig.app.json +35 -0
- package/packages/widget/tsconfig.json +4 -0
- package/packages/widget/tsconfig.node.json +24 -0
- package/packages/widget/vite.config.ts +116 -0
- package/packages/widget-lit/BOTTLENECKS.md +250 -0
- package/packages/widget-lit/IMPLEMENTATION_SUMMARY.md +295 -0
- package/packages/widget-lit/README.md +232 -0
- package/packages/widget-lit/eslint.config.mjs +3 -0
- package/packages/widget-lit/package.json +52 -0
- package/packages/widget-lit/src/api-client.ts +230 -0
- package/packages/widget-lit/src/api-client.ts.backup +218 -0
- package/packages/widget-lit/src/components/sea-chat.ts +382 -0
- package/packages/widget-lit/src/components/sea-submission-viewer.ts +267 -0
- package/packages/widget-lit/src/components/sea-widget.ts +317 -0
- package/packages/widget-lit/src/index.ts +48 -0
- package/packages/widget-lit/src/react.ts +58 -0
- package/packages/widget-lit/src/style.css +47 -0
- package/packages/widget-lit/tsconfig.json +24 -0
- package/packages/widget-lit/vite.config.ts +29 -0
- package/packages/widget-ng/DEVELOPMENT.md +74 -0
- package/packages/widget-ng/README.md +657 -0
- package/packages/widget-ng/dev.sh +14 -0
- package/packages/widget-ng/eslint.config.mjs +24 -0
- package/packages/widget-ng/ng-package.json +9 -0
- package/packages/widget-ng/package.json +85 -0
- package/packages/widget-ng/src/index.ts +45 -0
- package/packages/widget-ng/src/lib/components/sea-chat.component.ts +737 -0
- package/packages/widget-ng/src/lib/components/sea-data-viewer.component.ts +2240 -0
- package/packages/widget-ng/src/lib/components/sea-deal-form-modal.component.ts +702 -0
- package/packages/widget-ng/src/lib/components/sea-document-list.component.ts +350 -0
- package/packages/widget-ng/src/lib/components/sea-feedback-modal.component.ts +461 -0
- package/packages/widget-ng/src/lib/components/sea-file-upload.component.ts +655 -0
- package/packages/widget-ng/src/lib/components/sea-model-selection-modal.component.ts +367 -0
- package/packages/widget-ng/src/lib/components/sea-new-submission-modal.component.ts +414 -0
- package/packages/widget-ng/src/lib/components/sea-pdf-viewer.component.ts +869 -0
- package/packages/widget-ng/src/lib/components/sea-submission-card.component.ts +251 -0
- package/packages/widget-ng/src/lib/components/sea-widget.component.ts +684 -0
- package/packages/widget-ng/src/lib/models/submission.model.ts +170 -0
- package/packages/widget-ng/src/lib/pipes/markdown.pipe.ts +57 -0
- package/packages/widget-ng/src/lib/services/api-client.service.ts +715 -0
- package/packages/widget-ng/src/lib/services/chat.service.ts +330 -0
- package/packages/widget-ng/src/lib/services/config.service.ts +107 -0
- package/packages/widget-ng/src/web-component.ts +56 -0
- package/packages/widget-ng/tsconfig.json +25 -0
- package/packages/widget-ng/tsconfig.lib.json +9 -0
- package/packages/widget-ng/vite.config.elements.ts +26 -0
- package/packages/widget-ng/vitest.config.ts +19 -0
- package/packages/widget-ng/vitest.setup.ts +13 -0
- package/pnpm-workspace.yaml +18 -0
- package/render.yaml +136 -0
- package/scripts/README.md +57 -0
- package/scripts/package.json +22 -0
- package/scripts/python/.python-version +1 -0
- package/scripts/python/README.md +3 -0
- package/scripts/python/export-org-data.py +693 -0
- package/scripts/python/pyproject.toml +29 -0
- package/scripts/python/requirements-dev.lock +36 -0
- package/scripts/python/requirements.lock +36 -0
- package/scripts/python/src/gen.py +297 -0
- package/scripts/python/test.py +34 -0
- package/scripts/src/fix-storage-provider-mismatch.ts +239 -0
- package/scripts/src/sync-render-yaml.ts +290 -0
- package/scripts/src/test-chat-stream.ts +300 -0
- package/scripts/src/test-reconciliation.ts +230 -0
- package/scripts/tsconfig.json +15 -0
- package/tests/angular-test-app/.vscode/extensions.json +4 -0
- package/tests/angular-test-app/.vscode/launch.json +13 -0
- package/tests/angular-test-app/.vscode/tasks.json +24 -0
- package/tests/angular-test-app/README.md +59 -0
- package/tests/angular-test-app/angular.json +111 -0
- package/tests/angular-test-app/clean-start.sh +14 -0
- package/tests/angular-test-app/package.json +36 -0
- package/tests/angular-test-app/public/favicon.ico +0 -0
- package/tests/angular-test-app/src/app/app.component.ts +220 -0
- package/tests/angular-test-app/src/app/app.config.ts +5 -0
- package/tests/angular-test-app/src/env.d.ts +13 -0
- package/tests/angular-test-app/src/index.html +13 -0
- package/tests/angular-test-app/src/main.ts +6 -0
- package/tests/angular-test-app/src/styles.css +8 -0
- package/tests/angular-test-app/tsconfig.app.json +15 -0
- package/tests/angular-test-app/tsconfig.json +27 -0
- package/tests/crm-viewer-app/API_INTEGRATION_SUMMARY.md +295 -0
- package/tests/crm-viewer-app/CURRENT_ASSETS_FIELDS.md +148 -0
- package/tests/crm-viewer-app/FIELD_ID_MAPPING.md +206 -0
- package/tests/crm-viewer-app/INTEGRATION_GUIDE.md +309 -0
- package/tests/crm-viewer-app/README.md +174 -0
- package/tests/crm-viewer-app/REAL_API_INTEGRATION.md +240 -0
- package/tests/crm-viewer-app/UPDATED_IMPLEMENTATION.md +279 -0
- package/tests/crm-viewer-app/angular.json +114 -0
- package/tests/crm-viewer-app/package.json +35 -0
- package/tests/crm-viewer-app/src/app/app.component.ts +534 -0
- package/tests/crm-viewer-app/src/app/citation.service.ts +316 -0
- package/tests/crm-viewer-app/src/env.d.ts +16 -0
- package/tests/crm-viewer-app/src/index.html +19 -0
- package/tests/crm-viewer-app/src/main.ts +7 -0
- package/tests/crm-viewer-app/src/styles.css +409 -0
- package/tests/crm-viewer-app/src/template.html +2678 -0
- package/tests/crm-viewer-app/tsconfig.app.json +15 -0
- package/tests/crm-viewer-app/tsconfig.json +27 -0
- package/tests/e2e/package.json +17 -0
- package/tests/e2e/playwright.config.ts +75 -0
- package/tests/e2e/tests/api/health.spec.ts +10 -0
- package/tests/e2e/tests/app/example.spec.ts +10 -0
- package/tests/widget-test-app/.prettierignore +6 -0
- package/tests/widget-test-app/README.md +48 -0
- package/tests/widget-test-app/index.html +12 -0
- package/tests/widget-test-app/package.json +24 -0
- package/tests/widget-test-app/src/App.css +192 -0
- package/tests/widget-test-app/src/App.tsx +80 -0
- package/tests/widget-test-app/src/main.tsx +9 -0
- package/tests/widget-test-app/src/vite-env.d.ts +4 -0
- package/tests/widget-test-app/tsconfig.json +25 -0
- package/tests/widget-test-app/tsconfig.node.json +11 -0
- package/tests/widget-test-app/vite.config.ts +14 -0
|
@@ -0,0 +1,1182 @@
|
|
|
1
|
+
import { google } from "@ai-sdk/google";
|
|
2
|
+
import { formatISODate } from "@sea/util/fmt";
|
|
3
|
+
import { generateText, Output } from "ai";
|
|
4
|
+
import {
|
|
5
|
+
getMonth,
|
|
6
|
+
getYear,
|
|
7
|
+
isValid,
|
|
8
|
+
isWithinInterval,
|
|
9
|
+
parse,
|
|
10
|
+
setYear,
|
|
11
|
+
startOfDay,
|
|
12
|
+
endOfDay,
|
|
13
|
+
} from "date-fns";
|
|
14
|
+
import { z } from "zod/v4";
|
|
15
|
+
import type {
|
|
16
|
+
ValueWithReference,
|
|
17
|
+
StructuredGroup,
|
|
18
|
+
ValidationData,
|
|
19
|
+
StructuredData,
|
|
20
|
+
BankStatementReconciliation,
|
|
21
|
+
} from "../types";
|
|
22
|
+
import type {
|
|
23
|
+
PretrainedEntity,
|
|
24
|
+
DocumentLayout,
|
|
25
|
+
ReferenceMap,
|
|
26
|
+
LayoutItemType,
|
|
27
|
+
} from "@sea/schemas/core/layout";
|
|
28
|
+
import { Table, Section } from "@sea/schemas/core/layout";
|
|
29
|
+
|
|
30
|
+
const DATE_FORMATS: string[] = [
|
|
31
|
+
// Full Dates with 2-Digit Year (checked first to avoid ambiguity)
|
|
32
|
+
"dd MM yy",
|
|
33
|
+
"dd MMM yy",
|
|
34
|
+
"dd MMMM yy",
|
|
35
|
+
"d MM yy",
|
|
36
|
+
"d MMM yy",
|
|
37
|
+
"d MMMM yy",
|
|
38
|
+
"yy MM dd",
|
|
39
|
+
"yy MMM dd",
|
|
40
|
+
"yy MMMM dd",
|
|
41
|
+
"yy MM d",
|
|
42
|
+
"yy MMM d",
|
|
43
|
+
"yy MMMM d",
|
|
44
|
+
"MM dd yy",
|
|
45
|
+
"MMM dd yy",
|
|
46
|
+
"MMMM dd yy",
|
|
47
|
+
"MM d yy",
|
|
48
|
+
"MMM d yy",
|
|
49
|
+
"MMMM d yy",
|
|
50
|
+
// Full Dates with 4-Digit Year
|
|
51
|
+
"dd MM yyyy",
|
|
52
|
+
"dd MMM yyyy",
|
|
53
|
+
"dd MMMM yyyy",
|
|
54
|
+
"d MM yyyy",
|
|
55
|
+
"d MMM yyyy",
|
|
56
|
+
"d MMMM yyyy",
|
|
57
|
+
"yyyy MM dd",
|
|
58
|
+
"yyyy MMM dd",
|
|
59
|
+
"yyyy MMMM dd",
|
|
60
|
+
"yyyy MM d",
|
|
61
|
+
"yyyy MMM d",
|
|
62
|
+
"yyyy MMMM d",
|
|
63
|
+
"MM dd yyyy",
|
|
64
|
+
"MMM dd yyyy",
|
|
65
|
+
"MMMM dd yyyy",
|
|
66
|
+
"MM d yyyy",
|
|
67
|
+
"MMM d yyyy",
|
|
68
|
+
"MMMM d yyyy",
|
|
69
|
+
// Partial Dates with no Year
|
|
70
|
+
"dd MM",
|
|
71
|
+
"d M",
|
|
72
|
+
"dd MMM",
|
|
73
|
+
"d MMMM",
|
|
74
|
+
"MMM dd",
|
|
75
|
+
"MMMM dd",
|
|
76
|
+
"MMMM d",
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const BANK_TYPES: readonly string[] = ["barclays", "metro", "other"];
|
|
80
|
+
|
|
81
|
+
export const BankStatementBaseSchema = z.object({
|
|
82
|
+
bankName: z
|
|
83
|
+
.enum(BANK_TYPES)
|
|
84
|
+
.describe(
|
|
85
|
+
"Bank name from the available list. If the bank name is not in the list, return 'other'.",
|
|
86
|
+
),
|
|
87
|
+
transactionDateFormat: z.enum(DATE_FORMATS).describe(
|
|
88
|
+
`Identify the exact date-fns format string used for transaction dates within the provided bank statement table.
|
|
89
|
+
|
|
90
|
+
CRITICAL RULES:
|
|
91
|
+
|
|
92
|
+
- Year Check: Look at the transaction dates. If there is NO year digit present, you are strictly forbidden from using yyyy or yy in the output.
|
|
93
|
+
- Separator Check: You MUST replace all slashes, dashes, dots, or commas with a single space.
|
|
94
|
+
- Context: Only use the format found in the transaction table rows.
|
|
95
|
+
|
|
96
|
+
Step-by-Step Logic:
|
|
97
|
+
|
|
98
|
+
Step 1: Does the date string contain a year (e.g., 2024 or 24)?
|
|
99
|
+
Step 2: If NO, the format must only contain day and month tokens.
|
|
100
|
+
Step 3: Convert the pattern to date-fns tokens using ONLY spaces.
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
|
|
104
|
+
"31 Oct" -> "d MMM" (Correct: No year present)
|
|
105
|
+
"31 Oct 2023" -> "d MMM yyyy" (Correct: Year present)
|
|
106
|
+
"31/10" -> "dd MM" (Correct: No year present)
|
|
107
|
+
|
|
108
|
+
Final Output: Return ONLY the format string. No extra words or years.
|
|
109
|
+
`,
|
|
110
|
+
),
|
|
111
|
+
statementPeriodStart: z.iso
|
|
112
|
+
.date()
|
|
113
|
+
.describe("Bank statement period start date in YYYY-MM-DD format"),
|
|
114
|
+
statementPeriodEnd: z.iso
|
|
115
|
+
.date()
|
|
116
|
+
.describe("Bank statement period end date in YYYY-MM-DD format"),
|
|
117
|
+
openingBalance: z
|
|
118
|
+
.number()
|
|
119
|
+
.nullable()
|
|
120
|
+
.describe(
|
|
121
|
+
"Opening balance for the period. If value contains D, it means the balance is negative.",
|
|
122
|
+
),
|
|
123
|
+
closingBalance: z
|
|
124
|
+
.number()
|
|
125
|
+
.nullable()
|
|
126
|
+
.describe(
|
|
127
|
+
"Closing balance for the period. If value contains D, it means the balance is negative.",
|
|
128
|
+
),
|
|
129
|
+
isReverseOrdered: z
|
|
130
|
+
.boolean()
|
|
131
|
+
.default(false)
|
|
132
|
+
.describe(
|
|
133
|
+
"Whether the transactions are reverse ordered, namely if the most recent transactions are at the top of the list. Default to false",
|
|
134
|
+
),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Structure bank statement data.
|
|
139
|
+
*
|
|
140
|
+
* @param layout - Parsed document layout from the layout extractor
|
|
141
|
+
* @param getPdfBuffer - Optional lazy getter for the raw PDF bytes; required for VLM healing (step 3)
|
|
142
|
+
*/
|
|
143
|
+
export async function structureBankStatementData(
|
|
144
|
+
layout: DocumentLayout,
|
|
145
|
+
): Promise<{
|
|
146
|
+
data: StructuredData;
|
|
147
|
+
validationData?: ValidationData;
|
|
148
|
+
reconciliationData?: BankStatementReconciliation;
|
|
149
|
+
}> {
|
|
150
|
+
try {
|
|
151
|
+
const baseData = await getPretrainedBaseData(layout);
|
|
152
|
+
let { data, validationData } = parseAndValidateOutput(layout, baseData);
|
|
153
|
+
|
|
154
|
+
// Self-healing pipeline: attempt to fix transactions that failed balance validation
|
|
155
|
+
if (
|
|
156
|
+
validationData?.Transactions &&
|
|
157
|
+
validationData.Transactions.length > 0
|
|
158
|
+
) {
|
|
159
|
+
const transactions = data as Record<string, ValueWithReference>[];
|
|
160
|
+
validationData = await runSelfHealingPipeline(
|
|
161
|
+
transactions,
|
|
162
|
+
validationData.Transactions,
|
|
163
|
+
layout,
|
|
164
|
+
baseData,
|
|
165
|
+
);
|
|
166
|
+
data = transactions as unknown as StructuredGroup;
|
|
167
|
+
} else {
|
|
168
|
+
console.info("All transaction balances validated successfully");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Calculate reconciliation data
|
|
172
|
+
let totalCredits = 0;
|
|
173
|
+
let totalDebits = 0;
|
|
174
|
+
|
|
175
|
+
if (Array.isArray(data)) {
|
|
176
|
+
for (const transaction of data) {
|
|
177
|
+
const depositAmount =
|
|
178
|
+
(transaction.DepositAmount?.value as number | undefined) ?? 0;
|
|
179
|
+
const withdrawalAmount =
|
|
180
|
+
(transaction.WithdrawalAmount?.value as number | undefined) ?? 0;
|
|
181
|
+
totalCredits += depositAmount;
|
|
182
|
+
totalDebits += withdrawalAmount;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const openingBalance = baseData.openingBalance;
|
|
187
|
+
const closingBalance = baseData.closingBalance;
|
|
188
|
+
const calculatedClosingBalance =
|
|
189
|
+
(openingBalance ?? 0) + totalCredits - totalDebits;
|
|
190
|
+
const balanceDifference =
|
|
191
|
+
(closingBalance ?? calculatedClosingBalance) - calculatedClosingBalance;
|
|
192
|
+
const isReconciled = Math.abs(balanceDifference) < 0.01;
|
|
193
|
+
|
|
194
|
+
const reconciliationData: BankStatementReconciliation = {
|
|
195
|
+
statementPeriodStart: baseData.statementPeriodStart,
|
|
196
|
+
statementPeriodEnd: baseData.statementPeriodEnd,
|
|
197
|
+
openingBalance,
|
|
198
|
+
closingBalance,
|
|
199
|
+
totalCredits,
|
|
200
|
+
totalDebits,
|
|
201
|
+
calculatedClosingBalance,
|
|
202
|
+
balanceDifference,
|
|
203
|
+
isReconciled,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
data: { Transactions: data },
|
|
208
|
+
validationData,
|
|
209
|
+
reconciliationData,
|
|
210
|
+
};
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error("Error structuring and storing bank statement data:", error);
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Failed to structure and store bank statement data: ${error}`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// Self-healing helpers
|
|
221
|
+
// ============================================================================
|
|
222
|
+
|
|
223
|
+
/** A contiguous block of transaction indices to re-extract together. */
|
|
224
|
+
type HealingBlock = { start: number; end: number };
|
|
225
|
+
|
|
226
|
+
/** Schema for a single re-extracted transaction row. */
|
|
227
|
+
const HealedTransactionSchema = z.object({
|
|
228
|
+
date: z.iso
|
|
229
|
+
.date()
|
|
230
|
+
.nullable()
|
|
231
|
+
.describe("Transaction date in YYYY-MM-DD format, or null if not found"),
|
|
232
|
+
description: z
|
|
233
|
+
.string()
|
|
234
|
+
.nullable()
|
|
235
|
+
.describe("Transaction description/narrative"),
|
|
236
|
+
type: z
|
|
237
|
+
.string()
|
|
238
|
+
.nullable()
|
|
239
|
+
.describe("Transaction type code, or null if absent"),
|
|
240
|
+
depositAmount: z
|
|
241
|
+
.number()
|
|
242
|
+
.nullable()
|
|
243
|
+
.describe("Credit/deposit amount (positive number), or null"),
|
|
244
|
+
withdrawalAmount: z
|
|
245
|
+
.number()
|
|
246
|
+
.nullable()
|
|
247
|
+
.describe("Debit/withdrawal amount (positive number), or null"),
|
|
248
|
+
balance: z
|
|
249
|
+
.number()
|
|
250
|
+
.nullable()
|
|
251
|
+
.describe("Running balance after this transaction, or null if not present"),
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const HealedTransactionListSchema = z.object({
|
|
255
|
+
transactions: z
|
|
256
|
+
.array(HealedTransactionSchema)
|
|
257
|
+
.describe("Re-extracted transactions in the same order as the source rows"),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
type HealedTransaction = z.infer<typeof HealedTransactionSchema>;
|
|
261
|
+
|
|
262
|
+
// ─── Pipeline orchestration ──────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Logs a set of validation errors with their current transaction values.
|
|
266
|
+
*/
|
|
267
|
+
function logFailures(
|
|
268
|
+
label: string,
|
|
269
|
+
failures: { index: number; message: string }[],
|
|
270
|
+
transactions: Record<string, ValueWithReference>[],
|
|
271
|
+
): void {
|
|
272
|
+
console.warn(`[Self-heal] ── ${label} (${failures.length} failing) ──`);
|
|
273
|
+
for (const { index, message } of failures) {
|
|
274
|
+
console.warn(
|
|
275
|
+
`[Self-heal] [${index}] ${message} | ${formatTransactionSummary(index, transactions[index]!)}`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Logs the contents of a set of healing blocks.
|
|
282
|
+
*/
|
|
283
|
+
function logBlocks(
|
|
284
|
+
label: string,
|
|
285
|
+
blocks: HealingBlock[],
|
|
286
|
+
transactions: Record<string, ValueWithReference>[],
|
|
287
|
+
): void {
|
|
288
|
+
console.info(
|
|
289
|
+
`[Self-heal] ${label}: ${blocks.map((b) => `[${b.start}–${b.end}]`).join(", ")}`,
|
|
290
|
+
);
|
|
291
|
+
for (const block of blocks) {
|
|
292
|
+
console.info(
|
|
293
|
+
`[Self-heal] Block [${block.start}–${block.end}] (${block.end - block.start + 1} rows):`,
|
|
294
|
+
);
|
|
295
|
+
for (let i = block.start; i <= block.end; i++) {
|
|
296
|
+
console.info(
|
|
297
|
+
`[Self-heal] ${formatTransactionSummary(i, transactions[i]!)}`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Runs the full self-healing pipeline on a mutable transactions array.
|
|
305
|
+
*
|
|
306
|
+
* Pass 1 — text: builds blocks → calls Gemini with raw layout text → revalidates.
|
|
307
|
+
* Pass 2 — VLM: builds blocks from remaining failures → calls Gemini with
|
|
308
|
+
* the relevant PDF page → final revalidation.
|
|
309
|
+
*
|
|
310
|
+
* Mutates `transactions` in-place.
|
|
311
|
+
*
|
|
312
|
+
* @returns Updated ValidationData (undefined if fully reconciled, populated if
|
|
313
|
+
* some failures remain after both passes).
|
|
314
|
+
*/
|
|
315
|
+
async function runSelfHealingPipeline(
|
|
316
|
+
transactions: Record<string, ValueWithReference>[],
|
|
317
|
+
initialFailures: { index: number; message: string }[],
|
|
318
|
+
layout: DocumentLayout,
|
|
319
|
+
baseData: z.infer<typeof BankStatementBaseSchema>,
|
|
320
|
+
): Promise<ValidationData | undefined> {
|
|
321
|
+
const MAX_HEALING_BLOCKS = 10;
|
|
322
|
+
|
|
323
|
+
console.warn(
|
|
324
|
+
`[Self-heal] ── PIPELINE START ─────────────────────────────────────`,
|
|
325
|
+
);
|
|
326
|
+
console.warn(
|
|
327
|
+
`[Self-heal] ${initialFailures.length} failing transaction(s) out of ${transactions.length} total`,
|
|
328
|
+
);
|
|
329
|
+
logFailures("Initial failures", initialFailures, transactions);
|
|
330
|
+
|
|
331
|
+
// ── Step 0: deposit/withdrawal flip pre-pass ─────────────────────────────
|
|
332
|
+
const afterFlip = tryFlipDepositWithdrawal(
|
|
333
|
+
transactions,
|
|
334
|
+
initialFailures,
|
|
335
|
+
baseData.openingBalance ?? 0,
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
if (afterFlip.length === 0) {
|
|
339
|
+
console.info(`[Self-heal] ── STEP 0 RESULT: all reconciled by flip ✓`);
|
|
340
|
+
console.info(
|
|
341
|
+
`[Self-heal] ── PIPELINE END ────────────────────────────────`,
|
|
342
|
+
);
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
logFailures("After flip — still failing", afterFlip, transactions);
|
|
347
|
+
|
|
348
|
+
// ── Step 1: text re-extraction ──────────────────────────────────────────
|
|
349
|
+
console.info(`[Self-heal] ── STEP 1: text re-extraction ──────────────────`);
|
|
350
|
+
const textBlocks = buildHealingBlocks(afterFlip, transactions.length);
|
|
351
|
+
|
|
352
|
+
if (textBlocks.length > MAX_HEALING_BLOCKS) {
|
|
353
|
+
console.warn(
|
|
354
|
+
`[Self-heal] ${textBlocks.length} blocks exceeds limit of ${MAX_HEALING_BLOCKS} — skipping self-heal`,
|
|
355
|
+
);
|
|
356
|
+
console.info(
|
|
357
|
+
`[Self-heal] ── PIPELINE END ────────────────────────────────`,
|
|
358
|
+
);
|
|
359
|
+
return { Transactions: initialFailures };
|
|
360
|
+
}
|
|
361
|
+
logBlocks(`${textBlocks.length} text block(s)`, textBlocks, transactions);
|
|
362
|
+
|
|
363
|
+
await reextractBlocksFromText(transactions, textBlocks, layout, baseData);
|
|
364
|
+
|
|
365
|
+
// ── Step 2: revalidate ──────────────────────────────────────────────────
|
|
366
|
+
const afterText = validateTransactionBalances(
|
|
367
|
+
transactions,
|
|
368
|
+
baseData.openingBalance ?? 0,
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
if (afterText.Transactions.length === 0) {
|
|
372
|
+
console.info(`[Self-heal] ── STEP 1 RESULT: all reconciled ✓`);
|
|
373
|
+
console.info(
|
|
374
|
+
`[Self-heal] ── PIPELINE END ────────────────────────────────`,
|
|
375
|
+
);
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
logFailures(
|
|
380
|
+
"STEP 1 RESULT — still failing",
|
|
381
|
+
afterText.Transactions,
|
|
382
|
+
transactions,
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
// ── Step 3: VLM re-extraction (disabled — mupdf/WASM dependency removed) ─
|
|
386
|
+
// console.info(`[Self-heal] ── STEP 3: VLM re-extraction ───────────────────`);
|
|
387
|
+
// const vlmBlocks = buildHealingBlocks(
|
|
388
|
+
// afterText.Transactions,
|
|
389
|
+
// transactions.length,
|
|
390
|
+
// );
|
|
391
|
+
// logBlocks(`${vlmBlocks.length} VLM block(s)`, vlmBlocks, transactions);
|
|
392
|
+
// if (getPdfBuffer) {
|
|
393
|
+
// const pdfBuffer = await getPdfBuffer();
|
|
394
|
+
// await reextractBlocksFromVLM(
|
|
395
|
+
// transactions,
|
|
396
|
+
// vlmBlocks,
|
|
397
|
+
// layout.referenceMap ?? {},
|
|
398
|
+
// pdfBuffer,
|
|
399
|
+
// baseData,
|
|
400
|
+
// );
|
|
401
|
+
// } else {
|
|
402
|
+
// console.warn("[Self-heal] VLM skipped — no PDF buffer getter provided");
|
|
403
|
+
// }
|
|
404
|
+
// const afterVlm = validateTransactionBalances(
|
|
405
|
+
// transactions,
|
|
406
|
+
// baseData.openingBalance ?? 0,
|
|
407
|
+
// );
|
|
408
|
+
// if (afterVlm.Transactions.length === 0) {
|
|
409
|
+
// console.info(`[Self-heal] ── STEP 3 RESULT: all reconciled ✓`);
|
|
410
|
+
// console.info(`[Self-heal] ── PIPELINE END ────────────────────────────────`);
|
|
411
|
+
// return undefined;
|
|
412
|
+
// }
|
|
413
|
+
// logFailures("STEP 3 RESULT — giving up", afterVlm.Transactions, transactions);
|
|
414
|
+
|
|
415
|
+
logFailures(
|
|
416
|
+
"STEP 1 RESULT — giving up",
|
|
417
|
+
afterText.Transactions,
|
|
418
|
+
transactions,
|
|
419
|
+
);
|
|
420
|
+
console.info(`[Self-heal] ── PIPELINE END ────────────────────────────────`);
|
|
421
|
+
return { Transactions: afterText.Transactions };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ─── Deposit/withdrawal flip pre-pass ────────────────────────────────────────
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Clones a single transaction's DepositAmount and WithdrawalAmount, swapped.
|
|
428
|
+
*/
|
|
429
|
+
function flipTx(
|
|
430
|
+
tx: Record<string, ValueWithReference>,
|
|
431
|
+
): Record<string, ValueWithReference> {
|
|
432
|
+
const dep = tx.DepositAmount ?? null;
|
|
433
|
+
const wdl = tx.WithdrawalAmount ?? null;
|
|
434
|
+
return {
|
|
435
|
+
...tx,
|
|
436
|
+
DepositAmount: wdl ? { ...wdl } : null,
|
|
437
|
+
WithdrawalAmount: dep ? { ...dep } : null,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Returns true if tx at `index` reconciles given the balance of the preceding
|
|
443
|
+
* transaction (or openingBalance if index === 0).
|
|
444
|
+
*/
|
|
445
|
+
function txReconciles(
|
|
446
|
+
transactions: Record<string, ValueWithReference>[],
|
|
447
|
+
index: number,
|
|
448
|
+
openingBalance: number,
|
|
449
|
+
): boolean {
|
|
450
|
+
const tx = transactions[index];
|
|
451
|
+
if (!tx) return false;
|
|
452
|
+
const prevBalance =
|
|
453
|
+
index === 0
|
|
454
|
+
? openingBalance
|
|
455
|
+
: ((transactions[index - 1]?.Balance?.value ?? openingBalance) as number);
|
|
456
|
+
const balance = tx.Balance?.value as number | undefined;
|
|
457
|
+
if (balance === undefined || balance === null) return true; // null balance not our problem
|
|
458
|
+
const dep = (tx.DepositAmount?.value ?? 0) as number;
|
|
459
|
+
const wdl = (tx.WithdrawalAmount?.value ?? 0) as number;
|
|
460
|
+
return Math.abs(balance - (prevBalance + dep - wdl)) <= 0.01;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Pre-pass Step 0: for each failing transaction, try swapping its
|
|
465
|
+
* DepositAmount and WithdrawalAmount (and/or the previous transaction's)
|
|
466
|
+
* and check whether the pair(s) in question now reconcile locally.
|
|
467
|
+
*
|
|
468
|
+
* Candidates tried per failing index (in order, stopping at first fix):
|
|
469
|
+
* 1. Flip just the failing tx.
|
|
470
|
+
* 2. Flip the failing tx AND the previous tx.
|
|
471
|
+
*
|
|
472
|
+
* The check is local: does flipping make index `i` (and `i-1` if included)
|
|
473
|
+
* reconcile against their immediate neighbours? No full-list revalidation needed.
|
|
474
|
+
*
|
|
475
|
+
* Mutates `transactions` in-place for every individual fix found.
|
|
476
|
+
* Returns the remaining failures after all individual fixes have been applied.
|
|
477
|
+
*/
|
|
478
|
+
function tryFlipDepositWithdrawal(
|
|
479
|
+
transactions: Record<string, ValueWithReference>[],
|
|
480
|
+
failures: { index: number; message: string }[],
|
|
481
|
+
openingBalance: number,
|
|
482
|
+
): { index: number; message: string }[] {
|
|
483
|
+
console.info(
|
|
484
|
+
`[Self-heal flip] ── STEP 0: deposit/withdrawal flip pre-pass ──────`,
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
const stillFailing: { index: number; message: string }[] = [];
|
|
488
|
+
|
|
489
|
+
for (const failure of failures) {
|
|
490
|
+
const { index } = failure;
|
|
491
|
+
|
|
492
|
+
// Candidates: flip just this tx, or this tx + previous
|
|
493
|
+
const candidateSets: number[][] = [
|
|
494
|
+
[index],
|
|
495
|
+
...(index > 0 ? [[index, index - 1]] : []),
|
|
496
|
+
];
|
|
497
|
+
|
|
498
|
+
let fixed = false;
|
|
499
|
+
for (const candidates of candidateSets) {
|
|
500
|
+
// Apply flip to scratch copies of the candidate rows
|
|
501
|
+
const saved = candidates.map((i) => transactions[i]!);
|
|
502
|
+
for (const i of candidates) {
|
|
503
|
+
transactions[i] = flipTx(transactions[i]!);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Check local reconciliation for each candidate index
|
|
507
|
+
const allReconcile = candidates.every((i) =>
|
|
508
|
+
txReconciles(transactions, i, openingBalance),
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
if (allReconcile) {
|
|
512
|
+
console.info(
|
|
513
|
+
`[Self-heal flip] Flipping [${candidates.join(", ")}] fixes index ${index} locally ✓ — applying`,
|
|
514
|
+
);
|
|
515
|
+
fixed = true;
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Restore originals — this candidate set didn't work
|
|
520
|
+
for (let k = 0; k < candidates.length; k++) {
|
|
521
|
+
transactions[candidates[k]!] = saved[k]!;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (!fixed) {
|
|
526
|
+
console.info(
|
|
527
|
+
`[Self-heal flip] Flip did not fix index ${index} — leaving for LLM`,
|
|
528
|
+
);
|
|
529
|
+
stillFailing.push(failure);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
console.info(
|
|
534
|
+
`[Self-heal flip] ${stillFailing.length} failure(s) remain after flip pre-pass`,
|
|
535
|
+
);
|
|
536
|
+
return stillFailing;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ─── Block computation ───────────────────────────────────────────────────────
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Takes a list of validation errors and builds contiguous index blocks to heal.
|
|
543
|
+
*
|
|
544
|
+
* For each failing transaction we also pull in the one before it (to give the
|
|
545
|
+
* model context about the previous balance), then merge all overlapping or
|
|
546
|
+
* adjacent ranges into non-overlapping contiguous blocks.
|
|
547
|
+
*
|
|
548
|
+
* Example: failures at indices 2 and 4, totalCount = 7
|
|
549
|
+
* → raw ranges [1,2] and [3,4]
|
|
550
|
+
* → merged: [1,4] (adjacent, so they collapse into one block)
|
|
551
|
+
*/
|
|
552
|
+
function buildHealingBlocks(
|
|
553
|
+
failures: { index: number }[],
|
|
554
|
+
totalCount: number,
|
|
555
|
+
): HealingBlock[] {
|
|
556
|
+
if (failures.length === 0) return [];
|
|
557
|
+
|
|
558
|
+
// Expand each failing index: include the transaction before it for context
|
|
559
|
+
const ranges: HealingBlock[] = failures.map(({ index }) => ({
|
|
560
|
+
start: Math.max(0, index - 1),
|
|
561
|
+
end: Math.min(totalCount - 1, index),
|
|
562
|
+
}));
|
|
563
|
+
|
|
564
|
+
console.info(
|
|
565
|
+
`[Self-heal blocks] Raw ranges before merge: ${ranges.map((r) => `[${r.start}–${r.end}]`).join(", ")}`,
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
// Sort by start index so we can merge in a single pass
|
|
569
|
+
ranges.sort((a, b) => a.start - b.start);
|
|
570
|
+
|
|
571
|
+
const merged: HealingBlock[] = [];
|
|
572
|
+
let current = ranges[0]!;
|
|
573
|
+
|
|
574
|
+
for (let i = 1; i < ranges.length; i++) {
|
|
575
|
+
const next = ranges[i]!;
|
|
576
|
+
// Blocks overlap or are directly adjacent → merge
|
|
577
|
+
if (next.start <= current.end + 1) {
|
|
578
|
+
const merged_end = Math.max(current.end, next.end);
|
|
579
|
+
console.info(
|
|
580
|
+
`[Self-heal blocks] Merging [${current.start}–${current.end}] + [${next.start}–${next.end}] → [${current.start}–${merged_end}]`,
|
|
581
|
+
);
|
|
582
|
+
current = { start: current.start, end: merged_end };
|
|
583
|
+
} else {
|
|
584
|
+
merged.push(current);
|
|
585
|
+
current = next;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
merged.push(current);
|
|
589
|
+
|
|
590
|
+
console.info(
|
|
591
|
+
`[Self-heal blocks] Final merged blocks (${merged.length}): ${merged.map((b) => `[${b.start}–${b.end}]`).join(", ")}`,
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
return merged;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// ─── Layout context ──────────────────────────────────────────────────────────
|
|
598
|
+
|
|
599
|
+
const HEALED_SENTINELS = ["system", "none", "healed-text", "healed-vlm"];
|
|
600
|
+
|
|
601
|
+
function getTxRefIds(tx: Record<string, ValueWithReference>): string[] {
|
|
602
|
+
return Object.values(tx)
|
|
603
|
+
.map((v) => v?.refId)
|
|
604
|
+
.filter(
|
|
605
|
+
(id): id is string =>
|
|
606
|
+
typeof id === "string" && !HEALED_SENTINELS.includes(id),
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Serialises the raw layout text spanning the full contiguous block.
|
|
612
|
+
* Anchors on the first ref-text of the first transaction and the last
|
|
613
|
+
* ref-text of the last transaction, returning the lines between them
|
|
614
|
+
* with a small surrounding margin.
|
|
615
|
+
*/
|
|
616
|
+
function getBlockLayoutContext(
|
|
617
|
+
block: HealingBlock,
|
|
618
|
+
transactions: Record<string, ValueWithReference>[],
|
|
619
|
+
layout: DocumentLayout,
|
|
620
|
+
): string {
|
|
621
|
+
const refMap = layout.referenceMap ?? {};
|
|
622
|
+
const layoutText = layout.serializeAsText();
|
|
623
|
+
|
|
624
|
+
const firstTx = transactions[block.start];
|
|
625
|
+
const lastTx = transactions[block.end];
|
|
626
|
+
if (!firstTx || !lastTx) return layoutText.slice(0, 6000);
|
|
627
|
+
|
|
628
|
+
const firstRefText =
|
|
629
|
+
getTxRefIds(firstTx)
|
|
630
|
+
.map((id) => refMap[id]?.text)
|
|
631
|
+
.find(Boolean) ?? "";
|
|
632
|
+
|
|
633
|
+
const lastRefText =
|
|
634
|
+
getTxRefIds(lastTx)
|
|
635
|
+
.map((id) => refMap[id]?.text)
|
|
636
|
+
.find(Boolean) ?? "";
|
|
637
|
+
|
|
638
|
+
const pageNumber = getTxRefIds(firstTx)
|
|
639
|
+
.map((id) => refMap[id]?.boundingRegions?.[0]?.pageNumber)
|
|
640
|
+
.find((n): n is number => n !== undefined);
|
|
641
|
+
|
|
642
|
+
const pagePrefix = pageNumber !== undefined ? `[Page ${pageNumber}]\n` : "";
|
|
643
|
+
|
|
644
|
+
const startIdx = firstRefText ? layoutText.indexOf(firstRefText) : -1;
|
|
645
|
+
if (startIdx === -1) return `${pagePrefix}${layoutText.slice(0, 6000)}`;
|
|
646
|
+
|
|
647
|
+
const endSearchFrom = startIdx + firstRefText.length;
|
|
648
|
+
const endIdx =
|
|
649
|
+
lastRefText && lastRefText !== firstRefText
|
|
650
|
+
? layoutText.indexOf(lastRefText, endSearchFrom)
|
|
651
|
+
: startIdx;
|
|
652
|
+
|
|
653
|
+
const lines = layoutText.split("\n");
|
|
654
|
+
const startLine = Math.max(
|
|
655
|
+
0,
|
|
656
|
+
layoutText.slice(0, startIdx).split("\n").length - 2,
|
|
657
|
+
);
|
|
658
|
+
const endLine = Math.min(
|
|
659
|
+
lines.length - 1,
|
|
660
|
+
layoutText.slice(0, endIdx === -1 ? startIdx : endIdx).split("\n").length +
|
|
661
|
+
1,
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
return `${pagePrefix}${lines.slice(startLine, endLine + 1).join("\n")}`;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// ─── Apply healed rows ───────────────────────────────────────────────────────
|
|
668
|
+
|
|
669
|
+
function formatTransactionSummary(
|
|
670
|
+
index: number,
|
|
671
|
+
tx: Record<string, ValueWithReference>,
|
|
672
|
+
): string {
|
|
673
|
+
return (
|
|
674
|
+
` [${index}] Date=${tx.Date?.value ?? "?"} ` +
|
|
675
|
+
`Dep=${tx.DepositAmount?.value ?? "?"} ` +
|
|
676
|
+
`Wdl=${tx.WithdrawalAmount?.value ?? "?"} ` +
|
|
677
|
+
`Bal=${tx.Balance?.value ?? "?"} ` +
|
|
678
|
+
`Desc="${tx.Description?.value ?? "?"}"`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Splices healed rows back into `transactions` for the given block.
|
|
684
|
+
* Iterates back-to-front within the block so earlier indices stay stable
|
|
685
|
+
* if the caller later processes blocks in reverse order.
|
|
686
|
+
*/
|
|
687
|
+
function spliceHealedBlock(
|
|
688
|
+
transactions: Record<string, ValueWithReference>[],
|
|
689
|
+
block: HealingBlock,
|
|
690
|
+
healed: HealedTransaction[],
|
|
691
|
+
refSuffix: string,
|
|
692
|
+
): void {
|
|
693
|
+
const blockSize = block.end - block.start + 1;
|
|
694
|
+
|
|
695
|
+
if (healed.length !== blockSize) {
|
|
696
|
+
console.warn(
|
|
697
|
+
`[Self-heal] Block [${block.start}–${block.end}]: expected ${blockSize} rows, got ${healed.length} — applying field-level merge for available rows`,
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Apply from back to front within the block
|
|
702
|
+
for (let i = Math.min(healed.length, blockSize) - 1; i >= 0; i--) {
|
|
703
|
+
const txIndex = block.start + i;
|
|
704
|
+
const tx = transactions[txIndex];
|
|
705
|
+
const row = healed[i];
|
|
706
|
+
if (!tx || !row) continue;
|
|
707
|
+
|
|
708
|
+
console.info(
|
|
709
|
+
`[Self-heal splice] [${txIndex}] BEFORE: ${formatTransactionSummary(txIndex, tx)}`,
|
|
710
|
+
);
|
|
711
|
+
console.info(
|
|
712
|
+
`[Self-heal splice] [${txIndex}] MODEL OUTPUT: date=${row.date ?? "null"} dep=${row.depositAmount ?? "null"} wdl=${row.withdrawalAmount ?? "null"} bal=${row.balance ?? "null"} desc="${row.description ?? "null"}"`,
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
if (row.date !== null)
|
|
716
|
+
tx.Date = { value: row.date, refId: tx.Date?.refId ?? refSuffix };
|
|
717
|
+
if (row.description !== null)
|
|
718
|
+
tx.Description = {
|
|
719
|
+
value: row.description,
|
|
720
|
+
refId: tx.Description?.refId ?? refSuffix,
|
|
721
|
+
};
|
|
722
|
+
if (row.type !== null)
|
|
723
|
+
tx.Type = { value: row.type, refId: tx.Type?.refId ?? refSuffix };
|
|
724
|
+
if (row.depositAmount !== null)
|
|
725
|
+
tx.DepositAmount = {
|
|
726
|
+
value: Math.abs(row.depositAmount),
|
|
727
|
+
refId: tx.DepositAmount?.refId ?? refSuffix,
|
|
728
|
+
};
|
|
729
|
+
if (row.withdrawalAmount !== null)
|
|
730
|
+
tx.WithdrawalAmount = {
|
|
731
|
+
value: Math.abs(row.withdrawalAmount),
|
|
732
|
+
refId: tx.WithdrawalAmount?.refId ?? refSuffix,
|
|
733
|
+
};
|
|
734
|
+
if (row.balance !== null)
|
|
735
|
+
tx.Balance = {
|
|
736
|
+
value: row.balance,
|
|
737
|
+
refId: tx.Balance?.refId ?? refSuffix,
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
console.info(
|
|
741
|
+
`[Self-heal splice] [${txIndex}] AFTER: ${formatTransactionSummary(txIndex, tx)}`,
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// ─── Text-based healing ──────────────────────────────────────────────────────
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Walks the document layout (recursing into Sections) and returns all Table
|
|
750
|
+
* instances whose body cells include any of the given ref IDs.
|
|
751
|
+
*/
|
|
752
|
+
function findTablesContainingRefs(
|
|
753
|
+
items: LayoutItemType[],
|
|
754
|
+
refIds: Set<string>,
|
|
755
|
+
): Table[] {
|
|
756
|
+
const found: Table[] = [];
|
|
757
|
+
for (const item of items) {
|
|
758
|
+
if (item instanceof Table) {
|
|
759
|
+
const hasRef = item.content.some((row) =>
|
|
760
|
+
row.cells.some((cell) => refIds.has(cell.id)),
|
|
761
|
+
);
|
|
762
|
+
if (hasRef) found.push(item);
|
|
763
|
+
} else if (item instanceof Section) {
|
|
764
|
+
found.push(...findTablesContainingRefs(item.content, refIds));
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return found;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Returns the header row(s) of the transactions table as a comma-separated
|
|
772
|
+
* string, or null if no table / no header rows are found.
|
|
773
|
+
*/
|
|
774
|
+
function getTableHeaderForBlock(
|
|
775
|
+
block: HealingBlock,
|
|
776
|
+
transactions: Record<string, ValueWithReference>[],
|
|
777
|
+
layout: DocumentLayout,
|
|
778
|
+
): string | null {
|
|
779
|
+
const firstTx = transactions[block.start];
|
|
780
|
+
if (!firstTx) return null;
|
|
781
|
+
|
|
782
|
+
const refIds = new Set(getTxRefIds(firstTx));
|
|
783
|
+
if (refIds.size === 0) return null;
|
|
784
|
+
|
|
785
|
+
const tables = findTablesContainingRefs(layout.documentLayout, refIds);
|
|
786
|
+
console.log("Tables found:", tables);
|
|
787
|
+
if (tables.length === 0) return null;
|
|
788
|
+
|
|
789
|
+
const headerRows = tables[0]!.content.filter((r) => r.type === "head");
|
|
790
|
+
if (headerRows.length === 0) return tables[0]!.content[0]!.serializeAsText();
|
|
791
|
+
|
|
792
|
+
return headerRows.map((r) => r.serializeAsText()).join("\n");
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Step 1: Re-extract each contiguous block of failing transactions from the
|
|
797
|
+
* raw layout text using Gemini. All blocks are processed in parallel; within
|
|
798
|
+
* each block all rows are extracted in a single LLM call.
|
|
799
|
+
*
|
|
800
|
+
* Blocks are spliced back in reverse order (last block first) so indices
|
|
801
|
+
* remain stable throughout. Mutates `transactions` in-place.
|
|
802
|
+
*/
|
|
803
|
+
async function reextractBlocksFromText(
|
|
804
|
+
transactions: Record<string, ValueWithReference>[],
|
|
805
|
+
blocks: HealingBlock[],
|
|
806
|
+
layout: DocumentLayout,
|
|
807
|
+
baseData: z.infer<typeof BankStatementBaseSchema>,
|
|
808
|
+
): Promise<void> {
|
|
809
|
+
const period = `${baseData.statementPeriodStart} to ${baseData.statementPeriodEnd}`;
|
|
810
|
+
|
|
811
|
+
// Heal all blocks in parallel, collecting results keyed by block
|
|
812
|
+
const results = await Promise.all(
|
|
813
|
+
blocks.map(async (block) => {
|
|
814
|
+
const blockSize = block.end - block.start + 1;
|
|
815
|
+
const context = getBlockLayoutContext(block, transactions, layout);
|
|
816
|
+
const tableHeader = getTableHeaderForBlock(block, transactions, layout);
|
|
817
|
+
const currentRows = Array.from({ length: blockSize }, (_, i) =>
|
|
818
|
+
formatTransactionSummary(
|
|
819
|
+
block.start + i,
|
|
820
|
+
transactions[block.start + i]!,
|
|
821
|
+
),
|
|
822
|
+
).join("\n");
|
|
823
|
+
|
|
824
|
+
const headerSection = tableHeader
|
|
825
|
+
? `\nTable column headers:\n${tableHeader}\n`
|
|
826
|
+
: "";
|
|
827
|
+
|
|
828
|
+
const prompt = `You are correcting ${blockSize} bank statement transaction row(s) (indices ${block.start}–${block.end}) that failed balance reconciliation.
|
|
829
|
+
Don't forget to check if deposit and withdrawal amounts are flipped.
|
|
830
|
+
|
|
831
|
+
Statement period: ${period}
|
|
832
|
+
Opening balance: ${baseData.openingBalance ?? "unknown"}
|
|
833
|
+
Date format: ${baseData.transactionDateFormat}
|
|
834
|
+
${headerSection}
|
|
835
|
+
Currently extracted values (may contain errors):
|
|
836
|
+
${currentRows}
|
|
837
|
+
|
|
838
|
+
Raw document text for this block:
|
|
839
|
+
${context}
|
|
840
|
+
|
|
841
|
+
Re-extract all ${blockSize} transaction row(s) in order from the raw text. Return exactly ${blockSize} items in the "transactions" array.`;
|
|
842
|
+
|
|
843
|
+
console.info(
|
|
844
|
+
`[Self-heal text] Block [${block.start}–${block.end}] — table header: ${tableHeader ?? "(none)"}`,
|
|
845
|
+
);
|
|
846
|
+
console.info(
|
|
847
|
+
`[Self-heal text] Block [${block.start}–${block.end}] — sending prompt:\n${"─".repeat(60)}\n${prompt}\n${"─".repeat(60)}`,
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
try {
|
|
851
|
+
const result = await generateText({
|
|
852
|
+
model: google("gemini-3-pro-preview"),
|
|
853
|
+
system:
|
|
854
|
+
"You are a precise financial data extractor. Re-extract the given bank statement rows in order. Return null for any field absent in the source.",
|
|
855
|
+
prompt,
|
|
856
|
+
output: Output.object({ schema: HealedTransactionListSchema }),
|
|
857
|
+
maxRetries: 2,
|
|
858
|
+
temperature: 0,
|
|
859
|
+
});
|
|
860
|
+
const healed = HealedTransactionListSchema.parse(
|
|
861
|
+
result.output,
|
|
862
|
+
).transactions;
|
|
863
|
+
console.info(
|
|
864
|
+
`[Self-heal text] Block [${block.start}–${block.end}] — model returned ${healed.length} row(s):`,
|
|
865
|
+
);
|
|
866
|
+
for (const [i, row] of healed.entries()) {
|
|
867
|
+
console.info(
|
|
868
|
+
`[Self-heal text] [${block.start + i}] date=${row.date ?? "null"} dep=${row.depositAmount ?? "null"} wdl=${row.withdrawalAmount ?? "null"} bal=${row.balance ?? "null"} desc="${row.description ?? "null"}"`,
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
return { block, healed };
|
|
872
|
+
} catch (err) {
|
|
873
|
+
console.error(
|
|
874
|
+
`[Self-heal text] Failed for block [${block.start}–${block.end}]:`,
|
|
875
|
+
err,
|
|
876
|
+
);
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
}),
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
// Splice back in reverse block order (last block first) to preserve indices
|
|
883
|
+
const sorted = results
|
|
884
|
+
.filter((r): r is NonNullable<typeof r> => r !== null)
|
|
885
|
+
.sort((a, b) => b.block.start - a.block.start);
|
|
886
|
+
|
|
887
|
+
for (const { block, healed } of sorted) {
|
|
888
|
+
console.info(
|
|
889
|
+
`[Self-heal text] Splicing block [${block.start}–${block.end}] (processing back-to-front)`,
|
|
890
|
+
);
|
|
891
|
+
spliceHealedBlock(transactions, block, healed, "healed-text");
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function parseAndValidateOutput(
|
|
896
|
+
layout: DocumentLayout,
|
|
897
|
+
baseData: z.infer<typeof BankStatementBaseSchema>,
|
|
898
|
+
): {
|
|
899
|
+
data: StructuredGroup;
|
|
900
|
+
validationData?: ValidationData;
|
|
901
|
+
} {
|
|
902
|
+
const transactions: StructuredGroup = [];
|
|
903
|
+
if (!layout?.entities) {
|
|
904
|
+
return {
|
|
905
|
+
data: transactions,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Get Transactions entity from the record
|
|
910
|
+
const transactionsEntity = layout.entities["Transactions"];
|
|
911
|
+
if (!transactionsEntity) {
|
|
912
|
+
return {
|
|
913
|
+
data: transactions,
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Process transaction array
|
|
918
|
+
const transactionsList = transactionsEntity.valueArray;
|
|
919
|
+
if (!transactionsList) {
|
|
920
|
+
return {
|
|
921
|
+
data: transactions,
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const lastValidDate = new Date(baseData.statementPeriodStart);
|
|
926
|
+
if (baseData.bankName === "barclays") {
|
|
927
|
+
baseData.transactionDateFormat = "dd MMM";
|
|
928
|
+
}
|
|
929
|
+
for (const entity of transactionsList) {
|
|
930
|
+
const transaction: Record<string, ValueWithReference> = {
|
|
931
|
+
Balance: null,
|
|
932
|
+
Date: null,
|
|
933
|
+
Type: null,
|
|
934
|
+
Description: null,
|
|
935
|
+
DepositAmount: null,
|
|
936
|
+
WithdrawalAmount: null,
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
// Process properties in the object
|
|
940
|
+
if (entity.type === "object" && entity.valueObject) {
|
|
941
|
+
for (const [propType, prop] of Object.entries(entity.valueObject)) {
|
|
942
|
+
const sourceRef = prop.id;
|
|
943
|
+
switch (propType) {
|
|
944
|
+
case "Balance": {
|
|
945
|
+
const amount = parseTransactionAmount(prop);
|
|
946
|
+
transaction[propType] =
|
|
947
|
+
amount !== null ? { value: amount, refId: sourceRef } : null;
|
|
948
|
+
break;
|
|
949
|
+
}
|
|
950
|
+
case "DepositAmount":
|
|
951
|
+
case "WithdrawalAmount": {
|
|
952
|
+
const amount = parseTransactionAmount(prop);
|
|
953
|
+
transaction[propType] =
|
|
954
|
+
amount !== null
|
|
955
|
+
? { value: Math.abs(amount), refId: sourceRef }
|
|
956
|
+
: null;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
case "Date": {
|
|
960
|
+
const dateParsed = parseCompleteTransactionDate(
|
|
961
|
+
prop.content?.trim() ?? "",
|
|
962
|
+
lastValidDate,
|
|
963
|
+
new Date(baseData.statementPeriodEnd),
|
|
964
|
+
baseData.transactionDateFormat,
|
|
965
|
+
);
|
|
966
|
+
transaction.Date = dateParsed
|
|
967
|
+
? { value: formatISODate(dateParsed), refId: sourceRef }
|
|
968
|
+
: null;
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
case "Description":
|
|
972
|
+
case "Type": {
|
|
973
|
+
transaction[propType] = prop.content
|
|
974
|
+
? { value: prop.content, refId: sourceRef }
|
|
975
|
+
: null;
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
default: {
|
|
979
|
+
console.warn(`Unknown transaction type: ${propType}`);
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Filter transactions without transferred amount
|
|
987
|
+
if (
|
|
988
|
+
transaction.WithdrawalAmount ||
|
|
989
|
+
transaction.DepositAmount ||
|
|
990
|
+
transaction.Date
|
|
991
|
+
) {
|
|
992
|
+
transactions.push(transaction);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Order transactions by y-position on page
|
|
997
|
+
const refMap: ReferenceMap = layout.referenceMap ?? {};
|
|
998
|
+
transactions.sort((a, b) => {
|
|
999
|
+
const refIdA =
|
|
1000
|
+
a.WithdrawalAmount?.refId ?? a.DepositAmount?.refId ?? a.Date?.refId;
|
|
1001
|
+
const refIdB =
|
|
1002
|
+
b.WithdrawalAmount?.refId ?? b.DepositAmount?.refId ?? b.Date?.refId;
|
|
1003
|
+
if (!refIdA || !refIdB) return 0;
|
|
1004
|
+
const aRegion = refMap[refIdA]?.boundingRegions?.[0];
|
|
1005
|
+
const bRegion = refMap[refIdB]?.boundingRegions?.[0];
|
|
1006
|
+
if (!aRegion || !bRegion) return 0;
|
|
1007
|
+
const pageDiff = (aRegion.pageNumber ?? 0) - (bRegion.pageNumber ?? 0);
|
|
1008
|
+
if (pageDiff !== 0) return pageDiff;
|
|
1009
|
+
const aY = aRegion.polygon?.[1] ?? 0;
|
|
1010
|
+
const bY = bRegion.polygon?.[1] ?? 0;
|
|
1011
|
+
return aY - bY;
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
// Reverse the transaction order if the bank statement is reverse ordered
|
|
1015
|
+
if (baseData.isReverseOrdered) {
|
|
1016
|
+
transactions.reverse();
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Fill in potentially missing dates, assuming the previous one extends
|
|
1020
|
+
const statementStartDate: ValueWithReference = {
|
|
1021
|
+
value: baseData.statementPeriodStart,
|
|
1022
|
+
refId: "system",
|
|
1023
|
+
};
|
|
1024
|
+
let lastDate: ValueWithReference = statementStartDate;
|
|
1025
|
+
for (const transaction of transactions) {
|
|
1026
|
+
if (!transaction.Date) {
|
|
1027
|
+
transaction.Date = lastDate;
|
|
1028
|
+
} else {
|
|
1029
|
+
lastDate = transaction.Date;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// filter out transactions without deposit or withdrawal amount
|
|
1034
|
+
const cleanedTransactions = transactions.filter(
|
|
1035
|
+
(transaction) => transaction.WithdrawalAmount || transaction.DepositAmount,
|
|
1036
|
+
);
|
|
1037
|
+
|
|
1038
|
+
// validate transaction balances
|
|
1039
|
+
const validationData = validateTransactionBalances(
|
|
1040
|
+
cleanedTransactions,
|
|
1041
|
+
baseData.openingBalance ?? 0,
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
return {
|
|
1045
|
+
data: cleanedTransactions,
|
|
1046
|
+
validationData:
|
|
1047
|
+
validationData.Transactions.length > 0 ? validationData : undefined,
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function validateTransactionBalances(
|
|
1052
|
+
transactions: Record<string, ValueWithReference>[],
|
|
1053
|
+
openingBalance?: number,
|
|
1054
|
+
): { Transactions: { index: number; message: string }[] } {
|
|
1055
|
+
const errors: { index: number; message: string }[] = [];
|
|
1056
|
+
let prevBalance: number = openingBalance ?? 0;
|
|
1057
|
+
for (const [index, transaction] of transactions.entries()) {
|
|
1058
|
+
const currentBalance = (transaction.Balance?.value ?? null) as
|
|
1059
|
+
| number
|
|
1060
|
+
| null;
|
|
1061
|
+
const deposit = (transaction.DepositAmount?.value ?? 0) as number;
|
|
1062
|
+
const withdrawal = (transaction.WithdrawalAmount?.value ?? 0) as number;
|
|
1063
|
+
|
|
1064
|
+
if (currentBalance === null) {
|
|
1065
|
+
prevBalance = prevBalance + deposit - withdrawal;
|
|
1066
|
+
transaction.Balance = { refId: "none", value: prevBalance };
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
const tolerance = 0.01;
|
|
1071
|
+
if (
|
|
1072
|
+
Math.abs(currentBalance - (prevBalance + deposit - withdrawal)) >
|
|
1073
|
+
tolerance
|
|
1074
|
+
) {
|
|
1075
|
+
console.warn(
|
|
1076
|
+
"Balance validation failed: ",
|
|
1077
|
+
currentBalance,
|
|
1078
|
+
prevBalance,
|
|
1079
|
+
deposit,
|
|
1080
|
+
withdrawal,
|
|
1081
|
+
);
|
|
1082
|
+
errors.push({
|
|
1083
|
+
index,
|
|
1084
|
+
message: `Balance validation failed: got ${currentBalance.toFixed(2)} (deposit: ${deposit}, withdrawal: ${withdrawal})`,
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
prevBalance = currentBalance;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
return { Transactions: errors };
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function parseCompleteTransactionDate(
|
|
1095
|
+
rawDateStr: string,
|
|
1096
|
+
startDate: Date,
|
|
1097
|
+
endDate: Date,
|
|
1098
|
+
fmtStr: string,
|
|
1099
|
+
): Date | null {
|
|
1100
|
+
if (!rawDateStr) return null;
|
|
1101
|
+
|
|
1102
|
+
const cleanedDateStr = rawDateStr
|
|
1103
|
+
.replace(/(\d+)(?:st|nd|rd|th)/g, "$1")
|
|
1104
|
+
.replace(/[\/\-\.\,]/g, " ")
|
|
1105
|
+
.replace(/[\n\r\s]+/g, " ")
|
|
1106
|
+
.replace(/T\d{2}:.+/g, "")
|
|
1107
|
+
.trim()
|
|
1108
|
+
.replace(
|
|
1109
|
+
/\b([A-Z]{3,})\b/g,
|
|
1110
|
+
(match) => match.charAt(0) + match.slice(1).toLowerCase(),
|
|
1111
|
+
);
|
|
1112
|
+
|
|
1113
|
+
const tempDate = parse(cleanedDateStr, fmtStr, startDate);
|
|
1114
|
+
console.log(
|
|
1115
|
+
"PARSING DATE:",
|
|
1116
|
+
rawDateStr,
|
|
1117
|
+
"->",
|
|
1118
|
+
cleanedDateStr,
|
|
1119
|
+
"->",
|
|
1120
|
+
tempDate,
|
|
1121
|
+
"FROM:",
|
|
1122
|
+
startOfDay(startDate),
|
|
1123
|
+
"TO:",
|
|
1124
|
+
endOfDay(endDate),
|
|
1125
|
+
"FORMAT:",
|
|
1126
|
+
fmtStr,
|
|
1127
|
+
);
|
|
1128
|
+
|
|
1129
|
+
if (!isValid(tempDate)) {
|
|
1130
|
+
console.log("INVALID DATE:", tempDate);
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
let normalizedDate = startOfDay(tempDate);
|
|
1135
|
+
if (getMonth(normalizedDate) < getMonth(startDate)) {
|
|
1136
|
+
normalizedDate = setYear(normalizedDate, getYear(endDate));
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
if (
|
|
1140
|
+
isWithinInterval(normalizedDate, {
|
|
1141
|
+
start: startOfDay(startDate),
|
|
1142
|
+
end: endOfDay(endDate),
|
|
1143
|
+
})
|
|
1144
|
+
) {
|
|
1145
|
+
console.log("IN INTERVAL DATE:", normalizedDate);
|
|
1146
|
+
return normalizedDate;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
console.log("NOT IN INTERVAL DATE:", normalizedDate);
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function parseTransactionAmount(entity: PretrainedEntity): number | null {
|
|
1154
|
+
// Use valueNumber if available (normalized numeric value)
|
|
1155
|
+
if (entity.type === "number" && entity.valueNumber !== undefined) {
|
|
1156
|
+
return entity.valueNumber;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// Fall back to parsing content string
|
|
1160
|
+
const isDebit = entity.content?.toLowerCase().includes("d") ?? false;
|
|
1161
|
+
const cleanedString = entity.content?.replace(/[^0-9\.]/g, "");
|
|
1162
|
+
if (!cleanedString) return null;
|
|
1163
|
+
const amount = parseFloat(cleanedString);
|
|
1164
|
+
return isDebit ? -amount : amount;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
export async function getPretrainedBaseData(
|
|
1168
|
+
documentLayout: DocumentLayout,
|
|
1169
|
+
): Promise<z.infer<typeof BankStatementBaseSchema>> {
|
|
1170
|
+
const serializedLayout = documentLayout.serialize();
|
|
1171
|
+
const result = await generateText({
|
|
1172
|
+
model: google("gemini-3-pro-preview"),
|
|
1173
|
+
system:
|
|
1174
|
+
"Please extract all the required fields from the following text. Return dates in the ISO format YYYY-MM-DD.",
|
|
1175
|
+
prompt: serializedLayout,
|
|
1176
|
+
output: Output.object({
|
|
1177
|
+
schema: BankStatementBaseSchema,
|
|
1178
|
+
}),
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
return BankStatementBaseSchema.parse(result.output);
|
|
1182
|
+
}
|