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,4222 @@
|
|
|
1
|
+
import { db, PIPELINE_RUN_STATUS, PIPELINE_RUN_STEP_STATUS, schema } from "@sea/db";
|
|
2
|
+
import type {
|
|
3
|
+
PipelineRunStepBlockerDetails,
|
|
4
|
+
PipelineRunStepBlockerType,
|
|
5
|
+
SubmissionItemValue,
|
|
6
|
+
} from "@sea/db/schema";
|
|
7
|
+
import type {
|
|
8
|
+
Artifact,
|
|
9
|
+
FormGroupHydrated,
|
|
10
|
+
InsertDataTransformation,
|
|
11
|
+
InsertSubmissionGroup,
|
|
12
|
+
InsertSubmissionItem,
|
|
13
|
+
InsertSubmissionItemVersion,
|
|
14
|
+
Member,
|
|
15
|
+
Pipeline,
|
|
16
|
+
PipelineRun,
|
|
17
|
+
PipelineRunStep,
|
|
18
|
+
PipelineRunTimeline,
|
|
19
|
+
PipelineStep,
|
|
20
|
+
PipelineStepIntegrationConfig,
|
|
21
|
+
PipelineWithSteps,
|
|
22
|
+
Submission,
|
|
23
|
+
SubmissionGroup,
|
|
24
|
+
ToolCall,
|
|
25
|
+
} from "@sea/db/types";
|
|
26
|
+
import type { OutputData, OutputFieldTarget, OutputGroupData } from "@sea/schemas/core/pipeline";
|
|
27
|
+
import { formatISODate } from "@sea/util/fmt";
|
|
28
|
+
import { nanoid } from "@sea/util/id";
|
|
29
|
+
import { invariant } from "@sea/util/invariant";
|
|
30
|
+
import { and, asc, count, desc, eq, inArray, isNull, or, sql } from "drizzle-orm";
|
|
31
|
+
import type { DalResult } from "./result";
|
|
32
|
+
import { internalErrorResult, notFoundResult, okResult } from "./result";
|
|
33
|
+
|
|
34
|
+
function normalizeSteps<T extends { toolsAllowed: string[] | null }>(
|
|
35
|
+
steps: T[],
|
|
36
|
+
): (Omit<T, "toolsAllowed"> & { toolsAllowed: string[] })[] {
|
|
37
|
+
return steps.map((step) => ({
|
|
38
|
+
...step,
|
|
39
|
+
toolsAllowed: step.toolsAllowed ?? [],
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeSubmissionItemValue(value: unknown): SubmissionItemValue | null {
|
|
44
|
+
if (value == null) return null;
|
|
45
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
46
|
+
return String(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function getPipelineRun({
|
|
50
|
+
member,
|
|
51
|
+
runId,
|
|
52
|
+
}: {
|
|
53
|
+
member: Member;
|
|
54
|
+
runId: string;
|
|
55
|
+
}): Promise<DalResult<PipelineRun>> {
|
|
56
|
+
try {
|
|
57
|
+
const run = await db.query.pipelineRun.findFirst({
|
|
58
|
+
where: and(
|
|
59
|
+
eq(schema.pipelineRun.id, runId),
|
|
60
|
+
eq(schema.pipelineRun.organizationId, member.organizationId),
|
|
61
|
+
),
|
|
62
|
+
});
|
|
63
|
+
if (!run) return notFoundResult;
|
|
64
|
+
return okResult(run);
|
|
65
|
+
} catch {
|
|
66
|
+
return notFoundResult;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function getPipelineRunStep({
|
|
71
|
+
member,
|
|
72
|
+
runStepId,
|
|
73
|
+
}: {
|
|
74
|
+
member: Member;
|
|
75
|
+
runStepId: string;
|
|
76
|
+
}): Promise<DalResult<PipelineRunStep>> {
|
|
77
|
+
try {
|
|
78
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
79
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
80
|
+
with: { run: true },
|
|
81
|
+
});
|
|
82
|
+
if (!runStep || runStep.run.organizationId !== member.organizationId) {
|
|
83
|
+
return notFoundResult;
|
|
84
|
+
}
|
|
85
|
+
return okResult(runStep);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error("[getPipelineRunStep] Exception", err);
|
|
88
|
+
return notFoundResult;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function createPipelineRun({
|
|
93
|
+
member,
|
|
94
|
+
pipelineId,
|
|
95
|
+
dealId,
|
|
96
|
+
triggerType = "manual",
|
|
97
|
+
}: {
|
|
98
|
+
member: Member;
|
|
99
|
+
pipelineId: string;
|
|
100
|
+
dealId: string;
|
|
101
|
+
triggerType?: string;
|
|
102
|
+
}): Promise<DalResult<PipelineRun>> {
|
|
103
|
+
try {
|
|
104
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
105
|
+
where: and(
|
|
106
|
+
eq(schema.pipeline.id, pipelineId),
|
|
107
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
108
|
+
isNull(schema.pipeline.deletedAt),
|
|
109
|
+
),
|
|
110
|
+
with: {
|
|
111
|
+
steps: {
|
|
112
|
+
with: {
|
|
113
|
+
parentDependencies: true,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
if (!pipeline) return notFoundResult;
|
|
119
|
+
|
|
120
|
+
const [createdRun] = await db
|
|
121
|
+
.insert(schema.pipelineRun)
|
|
122
|
+
.values({
|
|
123
|
+
organizationId: member.organizationId,
|
|
124
|
+
pipelineId,
|
|
125
|
+
dealId,
|
|
126
|
+
status: "pending",
|
|
127
|
+
triggerType,
|
|
128
|
+
})
|
|
129
|
+
.returning();
|
|
130
|
+
|
|
131
|
+
if (!createdRun) return notFoundResult;
|
|
132
|
+
|
|
133
|
+
// Create run steps for all pipeline steps
|
|
134
|
+
for (const step of pipeline.steps) {
|
|
135
|
+
// Check if this step has any parent dependency with multiFormGroupId
|
|
136
|
+
// If so, this step should be a template that spawns instances per group
|
|
137
|
+
const shouldBeTemplate = step.parentDependencies.some((d) => d.multiFormGroupId !== null);
|
|
138
|
+
|
|
139
|
+
// Create run step - mark as template if it will spawn instances, otherwise normal
|
|
140
|
+
await db.insert(schema.pipelineRunStep).values({
|
|
141
|
+
runId: createdRun.id,
|
|
142
|
+
stepId: step.id,
|
|
143
|
+
spawnType: shouldBeTemplate ? "template" : "normal",
|
|
144
|
+
status: "pending",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return okResult(createdRun);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error("[createPipelineRun] Error:", error);
|
|
151
|
+
return notFoundResult;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function updatePipelineRunStatus({
|
|
156
|
+
member,
|
|
157
|
+
runId,
|
|
158
|
+
status,
|
|
159
|
+
errorSummary,
|
|
160
|
+
}: {
|
|
161
|
+
member: Member;
|
|
162
|
+
runId: string;
|
|
163
|
+
status: string;
|
|
164
|
+
errorSummary?: string;
|
|
165
|
+
}): Promise<DalResult<PipelineRun>> {
|
|
166
|
+
const runResult = await getPipelineRun({ member, runId });
|
|
167
|
+
if (!runResult.ok) return runResult;
|
|
168
|
+
const sets: Record<string, unknown> = { status };
|
|
169
|
+
if (errorSummary !== undefined) sets.errorSummary = errorSummary;
|
|
170
|
+
if (status === PIPELINE_RUN_STATUS.RUNNING) sets.startedAt = new Date();
|
|
171
|
+
if (
|
|
172
|
+
status === PIPELINE_RUN_STATUS.COMPLETED ||
|
|
173
|
+
status === PIPELINE_RUN_STATUS.FAILED ||
|
|
174
|
+
status === "cancelled"
|
|
175
|
+
) {
|
|
176
|
+
sets.completedAt = new Date();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const [run] = await db
|
|
181
|
+
.update(schema.pipelineRun)
|
|
182
|
+
.set(sets)
|
|
183
|
+
.where(eq(schema.pipelineRun.id, runId))
|
|
184
|
+
.returning();
|
|
185
|
+
if (!run) return notFoundResult;
|
|
186
|
+
return okResult(run);
|
|
187
|
+
} catch {
|
|
188
|
+
return notFoundResult;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function getPipelines({
|
|
193
|
+
member,
|
|
194
|
+
limit,
|
|
195
|
+
}: {
|
|
196
|
+
member: Member;
|
|
197
|
+
limit?: number;
|
|
198
|
+
}): Promise<PipelineWithSteps[]> {
|
|
199
|
+
const pipelines = await db.query.pipeline.findMany({
|
|
200
|
+
where: and(
|
|
201
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
202
|
+
isNull(schema.pipeline.deletedAt),
|
|
203
|
+
eq(schema.pipeline.isActive, true),
|
|
204
|
+
),
|
|
205
|
+
orderBy: desc(schema.pipeline.updatedAt),
|
|
206
|
+
limit,
|
|
207
|
+
with: {
|
|
208
|
+
steps: {
|
|
209
|
+
with: {
|
|
210
|
+
outputForms: true,
|
|
211
|
+
parentDependencies: true,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
return pipelines.map((pipeline) => ({
|
|
217
|
+
...pipeline,
|
|
218
|
+
steps: normalizeSteps(pipeline.steps),
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function getPipeline({
|
|
223
|
+
member,
|
|
224
|
+
pipelineId,
|
|
225
|
+
}: {
|
|
226
|
+
member: Member;
|
|
227
|
+
pipelineId: string;
|
|
228
|
+
}): Promise<DalResult<Pipeline>> {
|
|
229
|
+
try {
|
|
230
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
231
|
+
where: and(
|
|
232
|
+
eq(schema.pipeline.id, pipelineId),
|
|
233
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
234
|
+
isNull(schema.pipeline.deletedAt),
|
|
235
|
+
),
|
|
236
|
+
});
|
|
237
|
+
if (!pipeline) return notFoundResult;
|
|
238
|
+
return okResult(pipeline);
|
|
239
|
+
} catch {
|
|
240
|
+
return notFoundResult;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function getPipelineWithSteps({
|
|
245
|
+
member,
|
|
246
|
+
pipelineId,
|
|
247
|
+
}: {
|
|
248
|
+
member: Member;
|
|
249
|
+
pipelineId: string;
|
|
250
|
+
}): Promise<DalResult<PipelineWithSteps>> {
|
|
251
|
+
try {
|
|
252
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
253
|
+
where: and(
|
|
254
|
+
eq(schema.pipeline.id, pipelineId),
|
|
255
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
256
|
+
isNull(schema.pipeline.deletedAt),
|
|
257
|
+
eq(schema.pipeline.isActive, true),
|
|
258
|
+
),
|
|
259
|
+
with: {
|
|
260
|
+
steps: {
|
|
261
|
+
with: {
|
|
262
|
+
outputForms: true,
|
|
263
|
+
parentDependencies: true,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
if (!pipeline) return notFoundResult;
|
|
269
|
+
return okResult({
|
|
270
|
+
...pipeline,
|
|
271
|
+
steps: normalizeSteps(pipeline.steps),
|
|
272
|
+
});
|
|
273
|
+
} catch {
|
|
274
|
+
return notFoundResult;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function createPipeline({
|
|
279
|
+
member,
|
|
280
|
+
name = "Untitled Pipeline",
|
|
281
|
+
description = "",
|
|
282
|
+
}: {
|
|
283
|
+
member: Member;
|
|
284
|
+
name?: string;
|
|
285
|
+
description?: string;
|
|
286
|
+
}): Promise<DalResult<PipelineWithSteps>> {
|
|
287
|
+
try {
|
|
288
|
+
const [pipeline] = await db
|
|
289
|
+
.insert(schema.pipeline)
|
|
290
|
+
.values({
|
|
291
|
+
organizationId: member.organizationId,
|
|
292
|
+
name,
|
|
293
|
+
description,
|
|
294
|
+
isActive: true,
|
|
295
|
+
version: "1",
|
|
296
|
+
metadata: {},
|
|
297
|
+
})
|
|
298
|
+
.returning();
|
|
299
|
+
if (!pipeline) return notFoundResult;
|
|
300
|
+
|
|
301
|
+
return okResult({ ...pipeline, steps: [] });
|
|
302
|
+
} catch {
|
|
303
|
+
return notFoundResult;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export async function createPipelineRunStep({
|
|
308
|
+
member,
|
|
309
|
+
runId,
|
|
310
|
+
stepId,
|
|
311
|
+
spawnType = "normal",
|
|
312
|
+
submissionGroupId,
|
|
313
|
+
stepRunIndex,
|
|
314
|
+
}: {
|
|
315
|
+
member: Member;
|
|
316
|
+
runId: string;
|
|
317
|
+
stepId: string;
|
|
318
|
+
spawnType?: "normal" | "template" | "instance";
|
|
319
|
+
submissionGroupId?: string;
|
|
320
|
+
stepRunIndex?: number;
|
|
321
|
+
}): Promise<DalResult<PipelineRunStep>> {
|
|
322
|
+
const runResult = await getPipelineRun({ member, runId });
|
|
323
|
+
if (!runResult.ok) return runResult;
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const [runStep] = await db
|
|
327
|
+
.insert(schema.pipelineRunStep)
|
|
328
|
+
.values({
|
|
329
|
+
runId,
|
|
330
|
+
stepId,
|
|
331
|
+
spawnType,
|
|
332
|
+
submissionGroupId: submissionGroupId ?? null,
|
|
333
|
+
stepRunIndex: stepRunIndex ?? null,
|
|
334
|
+
status: "pending",
|
|
335
|
+
})
|
|
336
|
+
.returning();
|
|
337
|
+
if (!runStep) return notFoundResult;
|
|
338
|
+
return okResult(runStep);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error("[createPipelineRunStep] Error:", error);
|
|
341
|
+
return internalErrorResult;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get all output submissions from a parent step's run steps
|
|
347
|
+
*/
|
|
348
|
+
export async function getParentStepOutputs({
|
|
349
|
+
member,
|
|
350
|
+
runId,
|
|
351
|
+
parentStepId,
|
|
352
|
+
}: {
|
|
353
|
+
member: Member;
|
|
354
|
+
runId: string;
|
|
355
|
+
parentStepId: string;
|
|
356
|
+
}) {
|
|
357
|
+
const runResult = await getPipelineRun({ member, runId });
|
|
358
|
+
if (!runResult.ok) return runResult;
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
// Get run steps for the parent step
|
|
362
|
+
const runSteps = await db.query.pipelineRunStep.findMany({
|
|
363
|
+
where: and(
|
|
364
|
+
eq(schema.pipelineRunStep.runId, runId),
|
|
365
|
+
eq(schema.pipelineRunStep.stepId, parentStepId),
|
|
366
|
+
),
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (runSteps.length === 0) {
|
|
370
|
+
return okResult([]);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Get output submissions from the junction table
|
|
374
|
+
const runStepIds = runSteps.map((rs) => rs.id);
|
|
375
|
+
const outputMappings = await db.query.pipelineRunStepOutputSubmission.findMany({
|
|
376
|
+
with: {
|
|
377
|
+
submission: {
|
|
378
|
+
with: {
|
|
379
|
+
form: true,
|
|
380
|
+
groups: { with: { formGroup: { with: { fields: true, derived: true } }, items: true } },
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
where: inArray(schema.pipelineRunStepOutputSubmission.runStepId, runStepIds),
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const outputSubmissions = outputMappings.map((m) => m.submission);
|
|
388
|
+
|
|
389
|
+
return okResult(outputSubmissions);
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error("[getParentStepOutputs] Error:", error);
|
|
392
|
+
return internalErrorResult;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export async function updatePipelineRunStepStatus({
|
|
397
|
+
member,
|
|
398
|
+
runStepId,
|
|
399
|
+
status,
|
|
400
|
+
error,
|
|
401
|
+
}: {
|
|
402
|
+
member: Member;
|
|
403
|
+
runStepId: string;
|
|
404
|
+
status: string;
|
|
405
|
+
error?: string;
|
|
406
|
+
}): Promise<DalResult<PipelineRunStep>> {
|
|
407
|
+
const runStepResult = await getPipelineRunStep({ member, runStepId });
|
|
408
|
+
if (!runStepResult.ok) return runStepResult;
|
|
409
|
+
|
|
410
|
+
const now = new Date();
|
|
411
|
+
const sets: Record<string, unknown> = { status };
|
|
412
|
+
if (error !== undefined) sets.error = error;
|
|
413
|
+
if (status === PIPELINE_RUN_STEP_STATUS.RUNNING) sets.startedAt = now;
|
|
414
|
+
if (
|
|
415
|
+
status === PIPELINE_RUN_STEP_STATUS.COMPLETED ||
|
|
416
|
+
status === PIPELINE_RUN_STEP_STATUS.FAILED ||
|
|
417
|
+
status === "skipped"
|
|
418
|
+
) {
|
|
419
|
+
sets.completedAt = now;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
const [updated] = await db
|
|
424
|
+
.update(schema.pipelineRunStep)
|
|
425
|
+
.set(sets)
|
|
426
|
+
.where(eq(schema.pipelineRunStep.id, runStepId))
|
|
427
|
+
.returning();
|
|
428
|
+
if (!updated) return notFoundResult;
|
|
429
|
+
return okResult(updated);
|
|
430
|
+
} catch {
|
|
431
|
+
return notFoundResult;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export async function logToolCall({
|
|
436
|
+
member,
|
|
437
|
+
runStepId,
|
|
438
|
+
toolId,
|
|
439
|
+
args,
|
|
440
|
+
result,
|
|
441
|
+
status = PIPELINE_RUN_STEP_STATUS.COMPLETED,
|
|
442
|
+
latencyMs,
|
|
443
|
+
}: {
|
|
444
|
+
member: Member;
|
|
445
|
+
runStepId: string;
|
|
446
|
+
toolId: string;
|
|
447
|
+
args: object;
|
|
448
|
+
result: object;
|
|
449
|
+
status?: string;
|
|
450
|
+
latencyMs?: number;
|
|
451
|
+
}): Promise<DalResult<ToolCall>> {
|
|
452
|
+
const runStepResult = await getPipelineRunStep({ member, runStepId });
|
|
453
|
+
if (!runStepResult.ok) return runStepResult;
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
const [toolCall] = await db
|
|
457
|
+
.insert(schema.toolCall)
|
|
458
|
+
.values({
|
|
459
|
+
toolId,
|
|
460
|
+
args,
|
|
461
|
+
result,
|
|
462
|
+
status,
|
|
463
|
+
latencyMs,
|
|
464
|
+
})
|
|
465
|
+
.returning();
|
|
466
|
+
if (!toolCall) return notFoundResult;
|
|
467
|
+
return okResult(toolCall);
|
|
468
|
+
} catch {
|
|
469
|
+
return notFoundResult;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export async function getOutputFieldTargets({
|
|
474
|
+
member,
|
|
475
|
+
submissionId,
|
|
476
|
+
}: {
|
|
477
|
+
member: Member;
|
|
478
|
+
submissionId: string;
|
|
479
|
+
}): Promise<DalResult<OutputFieldTarget[]>> {
|
|
480
|
+
try {
|
|
481
|
+
const submission = await db.query.submission.findFirst({
|
|
482
|
+
where: and(
|
|
483
|
+
eq(schema.submission.id, submissionId),
|
|
484
|
+
eq(schema.submission.organizationId, member.organizationId),
|
|
485
|
+
isNull(schema.submission.deletedAt),
|
|
486
|
+
),
|
|
487
|
+
});
|
|
488
|
+
if (!submission) return notFoundResult;
|
|
489
|
+
|
|
490
|
+
const targets = await db
|
|
491
|
+
.select({
|
|
492
|
+
formFieldId: schema.formField.id,
|
|
493
|
+
fieldName: schema.formField.name,
|
|
494
|
+
groupName: schema.formGroup.name,
|
|
495
|
+
formGroupId: schema.formGroup.id,
|
|
496
|
+
multi: schema.formGroup.multi,
|
|
497
|
+
})
|
|
498
|
+
.from(schema.formField)
|
|
499
|
+
.innerJoin(schema.formGroup, eq(schema.formGroup.id, schema.formField.formGroupId))
|
|
500
|
+
.where(eq(schema.formGroup.formId, submission.formId));
|
|
501
|
+
|
|
502
|
+
return okResult(
|
|
503
|
+
targets.map((target) => ({
|
|
504
|
+
...target,
|
|
505
|
+
defaultGroupOrder: 0,
|
|
506
|
+
})),
|
|
507
|
+
);
|
|
508
|
+
} catch {
|
|
509
|
+
return notFoundResult;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Stores extracted data to the submission by creating submission groups, items, and citations.
|
|
514
|
+
* Handles both single and multi-instance form groups, validating and linking all data to source references.
|
|
515
|
+
*
|
|
516
|
+
* @param params - Configuration object
|
|
517
|
+
* @param params.submissionId - Target submission ID
|
|
518
|
+
* @param params.documentExtractionId - Source document extraction ID
|
|
519
|
+
* @param params.formGroupId - Form group being processed
|
|
520
|
+
* @param params.data - Structured extracted data
|
|
521
|
+
* @param params.referenceMap - Maps reference IDs to document locations
|
|
522
|
+
* @param params.validationData - Optional validation results for marking invalid groups
|
|
523
|
+
*/
|
|
524
|
+
export async function storePipelineOutputGroupData({
|
|
525
|
+
submissionId,
|
|
526
|
+
formGroupId,
|
|
527
|
+
data,
|
|
528
|
+
}: {
|
|
529
|
+
submissionId: string;
|
|
530
|
+
formGroupId: string;
|
|
531
|
+
data: OutputGroupData;
|
|
532
|
+
}) {
|
|
533
|
+
if (data === null) {
|
|
534
|
+
console.warn(`Deserialization aborted: data is null`);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const submissionGroups: InsertSubmissionGroup[] = [];
|
|
539
|
+
const submissionItems: InsertSubmissionItem[] = [];
|
|
540
|
+
const itemVersions: InsertSubmissionItemVersion[] = [];
|
|
541
|
+
const dataTransformations: InsertDataTransformation[] = [];
|
|
542
|
+
const versionToSourceItemIds = new Map<number, string[]>();
|
|
543
|
+
const versionToReasoning = new Map<number, string>();
|
|
544
|
+
|
|
545
|
+
const formGroup = await db.query.formGroup.findFirst({
|
|
546
|
+
where: eq(schema.formGroup.id, formGroupId),
|
|
547
|
+
with: {
|
|
548
|
+
fields: true,
|
|
549
|
+
derived: true,
|
|
550
|
+
submissionGroups: {
|
|
551
|
+
where: eq(schema.submissionGroup.submissionId, submissionId),
|
|
552
|
+
with: { items: true },
|
|
553
|
+
orderBy: asc(schema.submissionGroup.order),
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
invariant(formGroup, `No form group found for id ${formGroupId}`);
|
|
558
|
+
|
|
559
|
+
if (formGroup.multi) {
|
|
560
|
+
if (typeof data !== "object" || !Array.isArray(data)) {
|
|
561
|
+
console.warn(`Form group marked as multi, but data not an array`);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const orderOffset =
|
|
565
|
+
formGroup.submissionGroups.length > 0
|
|
566
|
+
? Math.max(...formGroup.submissionGroups.map((g) => g.order)) + 1
|
|
567
|
+
: 0;
|
|
568
|
+
|
|
569
|
+
for (const [order, groupElement] of data.entries()) {
|
|
570
|
+
const newSubmissionGroup: InsertSubmissionGroup = {
|
|
571
|
+
id: nanoid(),
|
|
572
|
+
submissionId,
|
|
573
|
+
formGroupId: formGroup.id,
|
|
574
|
+
order: order + orderOffset,
|
|
575
|
+
};
|
|
576
|
+
submissionGroups.push(newSubmissionGroup);
|
|
577
|
+
|
|
578
|
+
const newSubmissionItems = formGroup.fields.map((field) => ({
|
|
579
|
+
id: nanoid(),
|
|
580
|
+
submissionGroupId: newSubmissionGroup.id!,
|
|
581
|
+
formFieldId: field.id,
|
|
582
|
+
}));
|
|
583
|
+
submissionItems.push(...newSubmissionItems);
|
|
584
|
+
|
|
585
|
+
const result = await processGroupData(
|
|
586
|
+
formGroup,
|
|
587
|
+
newSubmissionItems,
|
|
588
|
+
itemVersions,
|
|
589
|
+
dataTransformations,
|
|
590
|
+
groupElement,
|
|
591
|
+
order,
|
|
592
|
+
);
|
|
593
|
+
// Merge the maps from this iteration
|
|
594
|
+
result.versionToSourceItemIds.forEach((value, key) => versionToSourceItemIds.set(key, value));
|
|
595
|
+
result.versionToReasoning.forEach((value, key) => versionToReasoning.set(key, value));
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
const submissionGroup = formGroup.submissionGroups[0];
|
|
599
|
+
invariant(submissionGroup);
|
|
600
|
+
|
|
601
|
+
const result = await processGroupData(
|
|
602
|
+
formGroup,
|
|
603
|
+
submissionGroup.items,
|
|
604
|
+
itemVersions,
|
|
605
|
+
dataTransformations,
|
|
606
|
+
data,
|
|
607
|
+
);
|
|
608
|
+
// Merge the maps from the single call
|
|
609
|
+
result.versionToSourceItemIds.forEach((value, key) => versionToSourceItemIds.set(key, value));
|
|
610
|
+
result.versionToReasoning.forEach((value, key) => versionToReasoning.set(key, value));
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
await db.transaction(async (tx) => {
|
|
614
|
+
if (submissionGroups.length > 0)
|
|
615
|
+
await tx.insert(schema.submissionGroup).values(submissionGroups);
|
|
616
|
+
if (submissionItems.length > 0) await tx.insert(schema.submissionItem).values(submissionItems);
|
|
617
|
+
|
|
618
|
+
// Use the collected mappings from processGroupData
|
|
619
|
+
|
|
620
|
+
if (itemVersions.length > 0 && versionToSourceItemIds) {
|
|
621
|
+
for (let index = 0; index < itemVersions.length; index++) {
|
|
622
|
+
const itemVersion = itemVersions[index];
|
|
623
|
+
if (!itemVersion) continue;
|
|
624
|
+
|
|
625
|
+
const sourceItemIds = versionToSourceItemIds.get(index);
|
|
626
|
+
|
|
627
|
+
if (!sourceItemIds || sourceItemIds.length === 0) {
|
|
628
|
+
// No transformation needed, insert version as-is
|
|
629
|
+
await tx.insert(schema.submissionItemVersion).values([itemVersion]);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Fetch the latest version IDs for each source submission item
|
|
634
|
+
const sourceVersions = await tx
|
|
635
|
+
.select({
|
|
636
|
+
id: schema.submissionItemVersion.id,
|
|
637
|
+
submissionItemId: schema.submissionItemVersion.submissionItemId,
|
|
638
|
+
})
|
|
639
|
+
.from(schema.submissionItemVersion)
|
|
640
|
+
.where(
|
|
641
|
+
and(
|
|
642
|
+
inArray(schema.submissionItemVersion.submissionItemId, sourceItemIds),
|
|
643
|
+
eq(schema.submissionItemVersion.selected, true),
|
|
644
|
+
),
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
const sourceVersionIds = sourceVersions.map((v) => v.id);
|
|
648
|
+
|
|
649
|
+
if (sourceVersionIds.length === 0) {
|
|
650
|
+
// No source versions found, insert without transformation
|
|
651
|
+
await tx.insert(schema.submissionItemVersion).values([itemVersion]);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Get reasoning for this field (if available)
|
|
656
|
+
const reasoning = versionToReasoning?.get(index);
|
|
657
|
+
|
|
658
|
+
// Create dataTransformation record
|
|
659
|
+
const transformation: InsertDataTransformation = {
|
|
660
|
+
id: nanoid(),
|
|
661
|
+
sourceItemVersionIds: sourceVersionIds,
|
|
662
|
+
description: reasoning || null,
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
await tx.insert(schema.dataTransformation).values([transformation]);
|
|
666
|
+
|
|
667
|
+
// Update item version to reference this transformation
|
|
668
|
+
const versionWithTransformation: typeof itemVersion = {
|
|
669
|
+
...itemVersion,
|
|
670
|
+
sourceId: transformation.id!,
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
await tx.insert(schema.submissionItemVersion).values([versionWithTransformation]);
|
|
674
|
+
}
|
|
675
|
+
} else if (itemVersions.length > 0) {
|
|
676
|
+
// No transformations needed, insert all versions
|
|
677
|
+
await tx.insert(schema.submissionItemVersion).values(itemVersions);
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Processes pipeline output data for a single form group and creates submission items with data transformations.
|
|
684
|
+
* Creates dataTransformation records linking output items to their source items from parent steps.
|
|
685
|
+
*
|
|
686
|
+
* @param formGroup - Form group definition with field metadata
|
|
687
|
+
* @param submissionItems - Array to populate with submission items
|
|
688
|
+
* @param itemVersions - Array to populate with submission item versions
|
|
689
|
+
* @param dataTransformations - Array to populate with data transformations
|
|
690
|
+
* @param groupData - Pipeline output data for this group
|
|
691
|
+
* @param order - Optional order index for multi-instance groups
|
|
692
|
+
*/
|
|
693
|
+
export async function processGroupData(
|
|
694
|
+
formGroup: FormGroupHydrated,
|
|
695
|
+
submissionItems: InsertSubmissionItem[],
|
|
696
|
+
itemVersions: InsertSubmissionItemVersion[],
|
|
697
|
+
dataTransformations: InsertDataTransformation[],
|
|
698
|
+
groupData: OutputGroupData,
|
|
699
|
+
order: number | null = null,
|
|
700
|
+
): Promise<{
|
|
701
|
+
versionToSourceItemIds: Map<number, string[]>;
|
|
702
|
+
versionToReasoning: Map<number, string>;
|
|
703
|
+
}> {
|
|
704
|
+
const versionToSourceItemIds = new Map<number, string[]>();
|
|
705
|
+
const versionToReasoning = new Map<number, string>();
|
|
706
|
+
|
|
707
|
+
if (!groupData || typeof groupData !== "object" || Array.isArray(groupData)) {
|
|
708
|
+
console.warn(
|
|
709
|
+
`Single group element ${formGroup.name} is missing or data is not an object, skipping...`,
|
|
710
|
+
);
|
|
711
|
+
return { versionToSourceItemIds, versionToReasoning };
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Process each field and track source item IDs and reasoning
|
|
715
|
+
|
|
716
|
+
for (const field of formGroup.fields) {
|
|
717
|
+
const submissionItem = submissionItems.find((item) => item.formFieldId === field.id);
|
|
718
|
+
const fieldData = groupData[field.name];
|
|
719
|
+
if (!fieldData || !submissionItem) continue;
|
|
720
|
+
|
|
721
|
+
const { value, refId, reasoning } = fieldData;
|
|
722
|
+
const sourceItemIds = refId.split(",").filter((id: string) => id.trim());
|
|
723
|
+
|
|
724
|
+
// Debug: Log only when reasoning is missing
|
|
725
|
+
if (!reasoning) {
|
|
726
|
+
console.warn(`[processGroupData] Field '${field.name}' missing reasoning`);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Create item version with transformation source type
|
|
730
|
+
const newItemVersion: InsertSubmissionItemVersion = {
|
|
731
|
+
submissionItemId: submissionItem.id!,
|
|
732
|
+
sourceType: "transformation" as const,
|
|
733
|
+
selected: true,
|
|
734
|
+
sourceId: null,
|
|
735
|
+
value: normalizeSubmissionItemValue(value),
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
const versionIndex = itemVersions.length;
|
|
739
|
+
itemVersions.push(newItemVersion);
|
|
740
|
+
|
|
741
|
+
// Track source item IDs and reasoning for this version to create transformation later
|
|
742
|
+
versionToSourceItemIds.set(versionIndex, sourceItemIds);
|
|
743
|
+
if (reasoning && typeof reasoning === "string") {
|
|
744
|
+
versionToReasoning.set(versionIndex, reasoning);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return { versionToSourceItemIds, versionToReasoning };
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Parses and normalizes field values into the submission item value format.
|
|
753
|
+
* Dates are converted to ISO format strings, booleans to numbers (0/1), and other values are passed through.
|
|
754
|
+
*
|
|
755
|
+
* @param value - Raw value from extraction (string, number, boolean, or Date)
|
|
756
|
+
* @returns Normalized value suitable for storing in submission items
|
|
757
|
+
*/
|
|
758
|
+
function parseItemValue(value: string | number | boolean | Date): SubmissionItemValue {
|
|
759
|
+
if (value instanceof Date) {
|
|
760
|
+
return formatISODate(value);
|
|
761
|
+
}
|
|
762
|
+
if (typeof value === "boolean") {
|
|
763
|
+
return Number(value);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return value;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
export async function storePipelineOutputData({
|
|
770
|
+
member,
|
|
771
|
+
outputSubmissionId,
|
|
772
|
+
outputData,
|
|
773
|
+
}: {
|
|
774
|
+
member: Member;
|
|
775
|
+
outputSubmissionId: string;
|
|
776
|
+
outputData: OutputData;
|
|
777
|
+
}): Promise<DalResult<{ success: true }>> {
|
|
778
|
+
try {
|
|
779
|
+
// Fetch the submission with its form and groups
|
|
780
|
+
const submission = await db.query.submission.findFirst({
|
|
781
|
+
where: and(
|
|
782
|
+
eq(schema.submission.id, outputSubmissionId),
|
|
783
|
+
eq(schema.submission.organizationId, member.organizationId),
|
|
784
|
+
isNull(schema.submission.deletedAt),
|
|
785
|
+
),
|
|
786
|
+
with: {
|
|
787
|
+
form: {
|
|
788
|
+
with: {
|
|
789
|
+
groups: {
|
|
790
|
+
with: {
|
|
791
|
+
fields: true,
|
|
792
|
+
},
|
|
793
|
+
orderBy: asc(schema.formGroup.order),
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
if (!submission) return notFoundResult;
|
|
801
|
+
|
|
802
|
+
// Process each group in the output data
|
|
803
|
+
for (const group of submission.form.groups) {
|
|
804
|
+
const groupData = outputData?.[group.name];
|
|
805
|
+
if (groupData) {
|
|
806
|
+
await storePipelineOutputGroupData({
|
|
807
|
+
submissionId: outputSubmissionId,
|
|
808
|
+
formGroupId: group.id,
|
|
809
|
+
data: groupData,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return okResult({ success: true });
|
|
815
|
+
} catch (error) {
|
|
816
|
+
console.error("[storePipelineOutputData] Error:", error);
|
|
817
|
+
return notFoundResult;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Get runtime submission (allows both extraction and transformation types).
|
|
823
|
+
* Used in pipeline execution to access both input and intermediate submissions.
|
|
824
|
+
*/
|
|
825
|
+
export async function getRuntimeSubmission({
|
|
826
|
+
member,
|
|
827
|
+
submissionId,
|
|
828
|
+
}: {
|
|
829
|
+
member: Member;
|
|
830
|
+
submissionId: string;
|
|
831
|
+
}): Promise<DalResult<Submission>> {
|
|
832
|
+
try {
|
|
833
|
+
const submission = await db.query.submission.findFirst({
|
|
834
|
+
where: and(
|
|
835
|
+
eq(schema.submission.id, submissionId),
|
|
836
|
+
eq(schema.submission.organizationId, member.organizationId),
|
|
837
|
+
isNull(schema.submission.deletedAt),
|
|
838
|
+
),
|
|
839
|
+
});
|
|
840
|
+
if (!submission) return notFoundResult;
|
|
841
|
+
return okResult(submission);
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.error("[getRuntimeSubmission] Error:", error);
|
|
844
|
+
return internalErrorResult;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Get runtime submission with denormalized data (groups, items, etc).
|
|
850
|
+
* Used in multi-group spawning and data filtering logic.
|
|
851
|
+
*/
|
|
852
|
+
export async function getRuntimeSubmissionWithData({
|
|
853
|
+
member,
|
|
854
|
+
submissionId,
|
|
855
|
+
}: {
|
|
856
|
+
member: Member;
|
|
857
|
+
submissionId: string;
|
|
858
|
+
}): Promise<
|
|
859
|
+
DalResult<
|
|
860
|
+
Submission & {
|
|
861
|
+
groups: Array<{
|
|
862
|
+
id: string;
|
|
863
|
+
formGroup: {
|
|
864
|
+
id: string;
|
|
865
|
+
name: string;
|
|
866
|
+
multi: boolean;
|
|
867
|
+
};
|
|
868
|
+
items: Array<{
|
|
869
|
+
id: string;
|
|
870
|
+
formFieldId: string;
|
|
871
|
+
}>;
|
|
872
|
+
}>;
|
|
873
|
+
}
|
|
874
|
+
>
|
|
875
|
+
> {
|
|
876
|
+
try {
|
|
877
|
+
const submission = await db.query.submission.findFirst({
|
|
878
|
+
where: and(
|
|
879
|
+
eq(schema.submission.id, submissionId),
|
|
880
|
+
eq(schema.submission.organizationId, member.organizationId),
|
|
881
|
+
isNull(schema.submission.deletedAt),
|
|
882
|
+
),
|
|
883
|
+
with: {
|
|
884
|
+
groups: {
|
|
885
|
+
orderBy: asc(schema.submissionGroup.order),
|
|
886
|
+
with: {
|
|
887
|
+
formGroup: true,
|
|
888
|
+
items: true,
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
});
|
|
893
|
+
if (!submission) return notFoundResult;
|
|
894
|
+
return okResult(submission);
|
|
895
|
+
} catch (error) {
|
|
896
|
+
console.error("[getRuntimeSubmissionWithData] Error:", error);
|
|
897
|
+
return internalErrorResult;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
export async function getPipelineRunWithSteps({
|
|
902
|
+
member,
|
|
903
|
+
runId,
|
|
904
|
+
}: {
|
|
905
|
+
member: Member;
|
|
906
|
+
runId: string;
|
|
907
|
+
}) {
|
|
908
|
+
try {
|
|
909
|
+
const run = await db.query.pipelineRun.findFirst({
|
|
910
|
+
where: and(
|
|
911
|
+
eq(schema.pipelineRun.id, runId),
|
|
912
|
+
eq(schema.pipelineRun.organizationId, member.organizationId),
|
|
913
|
+
),
|
|
914
|
+
with: {
|
|
915
|
+
pipeline: {
|
|
916
|
+
with: {
|
|
917
|
+
steps: {
|
|
918
|
+
orderBy: asc(schema.pipelineStep.createdAt),
|
|
919
|
+
},
|
|
920
|
+
},
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
if (!run) return notFoundResult;
|
|
926
|
+
return okResult(run);
|
|
927
|
+
} catch (error) {
|
|
928
|
+
console.error("[getPipelineRunWithSteps] Error:", error);
|
|
929
|
+
return notFoundResult;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
export async function getPipelineRuns({
|
|
934
|
+
member,
|
|
935
|
+
pipelineId,
|
|
936
|
+
limit = 50,
|
|
937
|
+
}: {
|
|
938
|
+
member: Member;
|
|
939
|
+
pipelineId: string;
|
|
940
|
+
limit?: number;
|
|
941
|
+
}): Promise<DalResult<PipelineRun[]>> {
|
|
942
|
+
try {
|
|
943
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
944
|
+
where: and(
|
|
945
|
+
eq(schema.pipeline.id, pipelineId),
|
|
946
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
947
|
+
isNull(schema.pipeline.deletedAt),
|
|
948
|
+
),
|
|
949
|
+
});
|
|
950
|
+
if (!pipeline) return notFoundResult;
|
|
951
|
+
|
|
952
|
+
const runs = await db.query.pipelineRun.findMany({
|
|
953
|
+
where: and(
|
|
954
|
+
eq(schema.pipelineRun.organizationId, member.organizationId),
|
|
955
|
+
eq(schema.pipelineRun.pipelineId, pipelineId),
|
|
956
|
+
),
|
|
957
|
+
orderBy: desc(schema.pipelineRun.createdAt),
|
|
958
|
+
limit,
|
|
959
|
+
with: {
|
|
960
|
+
steps: {
|
|
961
|
+
orderBy: asc(schema.pipelineRunStep.createdAt),
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
});
|
|
965
|
+
return okResult(runs);
|
|
966
|
+
} catch {
|
|
967
|
+
return notFoundResult;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Get all output submissions for a pipeline run, grouped by run step ID
|
|
973
|
+
*/
|
|
974
|
+
export async function getRunOutputSubmissions({
|
|
975
|
+
member,
|
|
976
|
+
runId,
|
|
977
|
+
}: {
|
|
978
|
+
member: Member;
|
|
979
|
+
runId: string;
|
|
980
|
+
}): Promise<DalResult<Map<string, string[]>>> {
|
|
981
|
+
const runResult = await getPipelineRun({ member, runId });
|
|
982
|
+
if (!runResult.ok) return runResult;
|
|
983
|
+
|
|
984
|
+
try {
|
|
985
|
+
// Get all run steps for this run
|
|
986
|
+
const runSteps = await db.query.pipelineRunStep.findMany({
|
|
987
|
+
where: eq(schema.pipelineRunStep.runId, runId),
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
if (runSteps.length === 0) {
|
|
991
|
+
return okResult(new Map());
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Get all output submissions
|
|
995
|
+
const runStepIds = runSteps.map((rs) => rs.id);
|
|
996
|
+
const outputMappings = await db.query.pipelineRunStepOutputSubmission.findMany({
|
|
997
|
+
where: inArray(schema.pipelineRunStepOutputSubmission.runStepId, runStepIds),
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// Group by run step ID
|
|
1001
|
+
const submissionsByRunStep = new Map<string, string[]>();
|
|
1002
|
+
for (const mapping of outputMappings) {
|
|
1003
|
+
const existing = submissionsByRunStep.get(mapping.runStepId) || [];
|
|
1004
|
+
existing.push(mapping.submissionId);
|
|
1005
|
+
submissionsByRunStep.set(mapping.runStepId, existing);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
return okResult(submissionsByRunStep);
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
console.error("[getRunOutputSubmissions] Error:", error);
|
|
1011
|
+
return internalErrorResult;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
export async function getRunTimeline({
|
|
1016
|
+
member,
|
|
1017
|
+
runId,
|
|
1018
|
+
}: {
|
|
1019
|
+
member: Member;
|
|
1020
|
+
runId: string;
|
|
1021
|
+
}): Promise<DalResult<PipelineRunTimeline>> {
|
|
1022
|
+
try {
|
|
1023
|
+
const run = await db.query.pipelineRun.findFirst({
|
|
1024
|
+
where: and(
|
|
1025
|
+
eq(schema.pipelineRun.id, runId),
|
|
1026
|
+
eq(schema.pipelineRun.organizationId, member.organizationId),
|
|
1027
|
+
),
|
|
1028
|
+
with: {
|
|
1029
|
+
steps: {
|
|
1030
|
+
orderBy: asc(schema.pipelineRunStep.createdAt),
|
|
1031
|
+
// Filter out template steps - they're just placeholders for spawning instances
|
|
1032
|
+
// Only show normal steps and actual instances
|
|
1033
|
+
where: (step, { ne }) => ne(step.spawnType, "template"),
|
|
1034
|
+
},
|
|
1035
|
+
pipeline: true,
|
|
1036
|
+
},
|
|
1037
|
+
});
|
|
1038
|
+
if (!run) return notFoundResult;
|
|
1039
|
+
|
|
1040
|
+
const stepIds = run.steps.map((s) => s.id);
|
|
1041
|
+
if (stepIds.length === 0) {
|
|
1042
|
+
// No steps, return early with empty arrays
|
|
1043
|
+
return okResult({
|
|
1044
|
+
...run,
|
|
1045
|
+
steps: [],
|
|
1046
|
+
artifacts: [],
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Load tool calls for all steps
|
|
1051
|
+
const toolCallMappings =
|
|
1052
|
+
run.steps.length > 0
|
|
1053
|
+
? await db.query.pipelineRunStepToolCall.findMany({
|
|
1054
|
+
where: inArray(
|
|
1055
|
+
schema.pipelineRunStepToolCall.runStepId,
|
|
1056
|
+
run.steps.map((s) => s.id),
|
|
1057
|
+
),
|
|
1058
|
+
with: {
|
|
1059
|
+
toolCall: true,
|
|
1060
|
+
},
|
|
1061
|
+
})
|
|
1062
|
+
: [];
|
|
1063
|
+
|
|
1064
|
+
// Load artifacts for all steps
|
|
1065
|
+
const stepArtifactMappings =
|
|
1066
|
+
run.steps.length > 0
|
|
1067
|
+
? await db.query.pipelineRunArtifact.findMany({
|
|
1068
|
+
where: and(
|
|
1069
|
+
eq(schema.pipelineRunArtifact.runId, runId),
|
|
1070
|
+
inArray(
|
|
1071
|
+
schema.pipelineRunArtifact.runStepId,
|
|
1072
|
+
run.steps.map((s) => s.id),
|
|
1073
|
+
),
|
|
1074
|
+
),
|
|
1075
|
+
with: {
|
|
1076
|
+
artifact: true,
|
|
1077
|
+
},
|
|
1078
|
+
})
|
|
1079
|
+
: [];
|
|
1080
|
+
|
|
1081
|
+
// Load run-level artifacts
|
|
1082
|
+
const runArtifactMappings = await db.query.pipelineRunArtifact.findMany({
|
|
1083
|
+
where: and(
|
|
1084
|
+
eq(schema.pipelineRunArtifact.runId, runId),
|
|
1085
|
+
isNull(schema.pipelineRunArtifact.runStepId),
|
|
1086
|
+
),
|
|
1087
|
+
with: {
|
|
1088
|
+
artifact: true,
|
|
1089
|
+
},
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
// Group by step
|
|
1093
|
+
const toolCallsByStep = new Map<string, typeof toolCallMappings>();
|
|
1094
|
+
for (const mapping of toolCallMappings) {
|
|
1095
|
+
const existing = toolCallsByStep.get(mapping.runStepId) || [];
|
|
1096
|
+
existing.push(mapping);
|
|
1097
|
+
toolCallsByStep.set(mapping.runStepId, existing);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const artifactsByStep = new Map<string, typeof stepArtifactMappings>();
|
|
1101
|
+
for (const mapping of stepArtifactMappings) {
|
|
1102
|
+
if (mapping.runStepId) {
|
|
1103
|
+
const existing = artifactsByStep.get(mapping.runStepId) || [];
|
|
1104
|
+
existing.push(mapping);
|
|
1105
|
+
artifactsByStep.set(mapping.runStepId, existing);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Collect all artifacts (both run-level and step-level)
|
|
1110
|
+
const allArtifacts = [
|
|
1111
|
+
...runArtifactMappings.map((m) => m.artifact),
|
|
1112
|
+
...stepArtifactMappings.map((m) => m.artifact),
|
|
1113
|
+
];
|
|
1114
|
+
|
|
1115
|
+
// Transform the data to match the expected type
|
|
1116
|
+
const transformedRun: PipelineRunTimeline = {
|
|
1117
|
+
...run,
|
|
1118
|
+
steps: run.steps.map((step) => ({
|
|
1119
|
+
...step,
|
|
1120
|
+
toolCalls: (toolCallsByStep.get(step.id) || []).map((m) => m.toolCall),
|
|
1121
|
+
artifacts: (artifactsByStep.get(step.id) || []).map((m) => m.artifact),
|
|
1122
|
+
})),
|
|
1123
|
+
artifacts: allArtifacts,
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
return okResult(transformedRun);
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
console.error("getRunTimeline error:", error);
|
|
1129
|
+
return notFoundResult;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
export async function getPipelineDetail({
|
|
1134
|
+
member,
|
|
1135
|
+
pipelineId,
|
|
1136
|
+
}: {
|
|
1137
|
+
member: Member;
|
|
1138
|
+
pipelineId: string;
|
|
1139
|
+
}) {
|
|
1140
|
+
try {
|
|
1141
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
1142
|
+
where: and(
|
|
1143
|
+
eq(schema.pipeline.id, pipelineId),
|
|
1144
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
1145
|
+
isNull(schema.pipeline.deletedAt),
|
|
1146
|
+
),
|
|
1147
|
+
with: {
|
|
1148
|
+
steps: {
|
|
1149
|
+
with: {
|
|
1150
|
+
outputForms: true,
|
|
1151
|
+
integrationConfig: true,
|
|
1152
|
+
parentDependencies: {
|
|
1153
|
+
with: {
|
|
1154
|
+
parentStep: true,
|
|
1155
|
+
},
|
|
1156
|
+
},
|
|
1157
|
+
},
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
});
|
|
1161
|
+
if (!pipeline) return notFoundResult;
|
|
1162
|
+
|
|
1163
|
+
// Get execution levels for all steps
|
|
1164
|
+
const levelsResult = await getPipelineStepsWithLevels({ member, pipelineId });
|
|
1165
|
+
const levelsByStepId = new Map<string, number>();
|
|
1166
|
+
if (levelsResult.ok) {
|
|
1167
|
+
for (const step of levelsResult.data) {
|
|
1168
|
+
levelsByStepId.set(step.id, step.executionLevel);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Sort steps by execution level, then by createdAt
|
|
1173
|
+
const sortedSteps = [...pipeline.steps].sort((a, b) => {
|
|
1174
|
+
const levelA = levelsByStepId.get(a.id) ?? 0;
|
|
1175
|
+
const levelB = levelsByStepId.get(b.id) ?? 0;
|
|
1176
|
+
if (levelA !== levelB) return levelA - levelB;
|
|
1177
|
+
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
const pipelineWithNormalizedSteps = {
|
|
1181
|
+
...pipeline,
|
|
1182
|
+
steps: normalizeSteps(sortedSteps),
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
return okResult(pipelineWithNormalizedSteps);
|
|
1186
|
+
} catch {
|
|
1187
|
+
return notFoundResult;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
export async function deletePipeline({
|
|
1192
|
+
member,
|
|
1193
|
+
pipelineId,
|
|
1194
|
+
}: {
|
|
1195
|
+
member: Member;
|
|
1196
|
+
pipelineId: string;
|
|
1197
|
+
}): Promise<DalResult<{ success: true }>> {
|
|
1198
|
+
try {
|
|
1199
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
1200
|
+
where: and(
|
|
1201
|
+
eq(schema.pipeline.id, pipelineId),
|
|
1202
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
1203
|
+
isNull(schema.pipeline.deletedAt),
|
|
1204
|
+
),
|
|
1205
|
+
});
|
|
1206
|
+
if (!pipeline) return notFoundResult;
|
|
1207
|
+
|
|
1208
|
+
await db
|
|
1209
|
+
.update(schema.pipeline)
|
|
1210
|
+
.set({ deletedAt: new Date() })
|
|
1211
|
+
.where(eq(schema.pipeline.id, pipelineId));
|
|
1212
|
+
|
|
1213
|
+
return okResult({ success: true });
|
|
1214
|
+
} catch {
|
|
1215
|
+
return notFoundResult;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
export async function togglePipelineActive({
|
|
1220
|
+
member,
|
|
1221
|
+
pipelineId,
|
|
1222
|
+
isActive,
|
|
1223
|
+
}: {
|
|
1224
|
+
member: Member;
|
|
1225
|
+
pipelineId: string;
|
|
1226
|
+
isActive: boolean;
|
|
1227
|
+
}): Promise<DalResult<{ success: true }>> {
|
|
1228
|
+
try {
|
|
1229
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
1230
|
+
where: and(
|
|
1231
|
+
eq(schema.pipeline.id, pipelineId),
|
|
1232
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
1233
|
+
isNull(schema.pipeline.deletedAt),
|
|
1234
|
+
),
|
|
1235
|
+
});
|
|
1236
|
+
if (!pipeline) return notFoundResult;
|
|
1237
|
+
|
|
1238
|
+
await db.update(schema.pipeline).set({ isActive }).where(eq(schema.pipeline.id, pipelineId));
|
|
1239
|
+
|
|
1240
|
+
return okResult({ success: true });
|
|
1241
|
+
} catch {
|
|
1242
|
+
return notFoundResult;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
export async function updatePipelineMetadata({
|
|
1247
|
+
member,
|
|
1248
|
+
pipelineId,
|
|
1249
|
+
name,
|
|
1250
|
+
description,
|
|
1251
|
+
}: {
|
|
1252
|
+
member: Member;
|
|
1253
|
+
pipelineId: string;
|
|
1254
|
+
name: string;
|
|
1255
|
+
description: string;
|
|
1256
|
+
}): Promise<DalResult<{ success: true }>> {
|
|
1257
|
+
try {
|
|
1258
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
1259
|
+
where: and(
|
|
1260
|
+
eq(schema.pipeline.id, pipelineId),
|
|
1261
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
1262
|
+
isNull(schema.pipeline.deletedAt),
|
|
1263
|
+
),
|
|
1264
|
+
});
|
|
1265
|
+
if (!pipeline) return notFoundResult;
|
|
1266
|
+
|
|
1267
|
+
await db
|
|
1268
|
+
.update(schema.pipeline)
|
|
1269
|
+
.set({ name, description })
|
|
1270
|
+
.where(eq(schema.pipeline.id, pipelineId));
|
|
1271
|
+
|
|
1272
|
+
return okResult({ success: true });
|
|
1273
|
+
} catch {
|
|
1274
|
+
return notFoundResult;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
export async function createPipelineStep({
|
|
1279
|
+
member,
|
|
1280
|
+
pipelineId,
|
|
1281
|
+
name,
|
|
1282
|
+
description,
|
|
1283
|
+
prompt,
|
|
1284
|
+
type,
|
|
1285
|
+
role,
|
|
1286
|
+
outputFormIds,
|
|
1287
|
+
toolsAllowed,
|
|
1288
|
+
integrationConfig,
|
|
1289
|
+
}: {
|
|
1290
|
+
member: Member;
|
|
1291
|
+
pipelineId: string;
|
|
1292
|
+
name: string;
|
|
1293
|
+
description: string;
|
|
1294
|
+
prompt?: string;
|
|
1295
|
+
type: "single" | "multi";
|
|
1296
|
+
role: "extraction" | "integration" | "analysis" | null;
|
|
1297
|
+
outputFormIds?: string[];
|
|
1298
|
+
toolsAllowed?: string[];
|
|
1299
|
+
integrationConfig?: {
|
|
1300
|
+
method: "GET" | "POST" | "PUT" | "DELETE";
|
|
1301
|
+
urlTemplate: string;
|
|
1302
|
+
headers: Record<string, string>;
|
|
1303
|
+
bodyTemplate?: string;
|
|
1304
|
+
};
|
|
1305
|
+
}): Promise<DalResult<PipelineStep>> {
|
|
1306
|
+
try {
|
|
1307
|
+
// Verify pipeline exists and member has access
|
|
1308
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
1309
|
+
where: and(
|
|
1310
|
+
eq(schema.pipeline.id, pipelineId),
|
|
1311
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
1312
|
+
isNull(schema.pipeline.deletedAt),
|
|
1313
|
+
),
|
|
1314
|
+
});
|
|
1315
|
+
if (!pipeline) return notFoundResult;
|
|
1316
|
+
|
|
1317
|
+
const [step] = await db
|
|
1318
|
+
.insert(schema.pipelineStep)
|
|
1319
|
+
.values({
|
|
1320
|
+
pipelineId,
|
|
1321
|
+
name,
|
|
1322
|
+
description,
|
|
1323
|
+
prompt: prompt ?? "",
|
|
1324
|
+
type,
|
|
1325
|
+
role,
|
|
1326
|
+
toolsAllowed: toolsAllowed ?? [],
|
|
1327
|
+
// integrationConfig deliberately NOT written - use pipelineStepIntegrationConfig table instead
|
|
1328
|
+
retryPolicy: {},
|
|
1329
|
+
})
|
|
1330
|
+
.returning();
|
|
1331
|
+
|
|
1332
|
+
if (!step) return notFoundResult;
|
|
1333
|
+
|
|
1334
|
+
// Insert into pipeline_step_output_form table
|
|
1335
|
+
if (outputFormIds && outputFormIds.length > 0) {
|
|
1336
|
+
await db.insert(schema.pipelineStepOutputForm).values(
|
|
1337
|
+
outputFormIds.map((formId) => ({
|
|
1338
|
+
stepId: step.id,
|
|
1339
|
+
formId,
|
|
1340
|
+
})),
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Insert into pipeline_step_integration_config table if provided
|
|
1345
|
+
if (integrationConfig && role === "integration") {
|
|
1346
|
+
await db.insert(schema.pipelineStepIntegrationConfig).values({
|
|
1347
|
+
id: nanoid(),
|
|
1348
|
+
stepId: step.id,
|
|
1349
|
+
method: integrationConfig.method,
|
|
1350
|
+
urlTemplate: integrationConfig.urlTemplate,
|
|
1351
|
+
headers: integrationConfig.headers,
|
|
1352
|
+
bodyTemplate: integrationConfig.bodyTemplate ?? null,
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
return okResult(step);
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
console.error("[createPipelineStep] Error:", error);
|
|
1359
|
+
return notFoundResult;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
export async function updatePipelineStepConfig({
|
|
1364
|
+
member,
|
|
1365
|
+
stepId,
|
|
1366
|
+
name,
|
|
1367
|
+
description,
|
|
1368
|
+
prompt,
|
|
1369
|
+
type,
|
|
1370
|
+
role,
|
|
1371
|
+
outputFormIds,
|
|
1372
|
+
toolsAllowed,
|
|
1373
|
+
integrationConfig,
|
|
1374
|
+
}: {
|
|
1375
|
+
member: Member;
|
|
1376
|
+
stepId: string;
|
|
1377
|
+
name?: string;
|
|
1378
|
+
description?: string;
|
|
1379
|
+
prompt?: string;
|
|
1380
|
+
type?: "single" | "multi";
|
|
1381
|
+
role?: "extraction" | "integration" | "analysis" | null;
|
|
1382
|
+
outputFormIds?: string[];
|
|
1383
|
+
toolsAllowed?: string[];
|
|
1384
|
+
integrationConfig?: {
|
|
1385
|
+
method: "GET" | "POST" | "PUT" | "DELETE";
|
|
1386
|
+
urlTemplate: string;
|
|
1387
|
+
headers: Record<string, string>;
|
|
1388
|
+
bodyTemplate?: string;
|
|
1389
|
+
} | null;
|
|
1390
|
+
}): Promise<DalResult<{ success: true }>> {
|
|
1391
|
+
try {
|
|
1392
|
+
const step = await db.query.pipelineStep.findFirst({
|
|
1393
|
+
where: eq(schema.pipelineStep.id, stepId),
|
|
1394
|
+
with: {
|
|
1395
|
+
pipeline: true,
|
|
1396
|
+
},
|
|
1397
|
+
});
|
|
1398
|
+
if (!step || step.pipeline.organizationId !== member.organizationId) return notFoundResult;
|
|
1399
|
+
|
|
1400
|
+
// Build update object with only provided fields
|
|
1401
|
+
const updateData: Record<string, unknown> = {};
|
|
1402
|
+
if (name !== undefined) updateData.name = name;
|
|
1403
|
+
if (description !== undefined) updateData.description = description;
|
|
1404
|
+
if (prompt !== undefined) updateData.prompt = prompt;
|
|
1405
|
+
if (type !== undefined) updateData.type = type;
|
|
1406
|
+
if (role !== undefined) updateData.role = role;
|
|
1407
|
+
if (toolsAllowed !== undefined) updateData.toolsAllowed = toolsAllowed;
|
|
1408
|
+
// integrationConfig deliberately NOT written - use pipelineStepIntegrationConfig table instead
|
|
1409
|
+
|
|
1410
|
+
await db.update(schema.pipelineStep).set(updateData).where(eq(schema.pipelineStep.id, stepId));
|
|
1411
|
+
|
|
1412
|
+
// Update pipeline_step_output_form table
|
|
1413
|
+
if (outputFormIds !== undefined) {
|
|
1414
|
+
// Delete existing output form associations
|
|
1415
|
+
await db
|
|
1416
|
+
.delete(schema.pipelineStepOutputForm)
|
|
1417
|
+
.where(eq(schema.pipelineStepOutputForm.stepId, stepId));
|
|
1418
|
+
|
|
1419
|
+
// Insert new output form associations
|
|
1420
|
+
if (outputFormIds.length > 0) {
|
|
1421
|
+
await db.insert(schema.pipelineStepOutputForm).values(
|
|
1422
|
+
outputFormIds.map((formId) => ({
|
|
1423
|
+
stepId,
|
|
1424
|
+
formId,
|
|
1425
|
+
})),
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// Update pipeline_step_integration_config table
|
|
1431
|
+
if (integrationConfig !== undefined) {
|
|
1432
|
+
if (integrationConfig === null) {
|
|
1433
|
+
// Delete config if explicitly set to null
|
|
1434
|
+
await db
|
|
1435
|
+
.delete(schema.pipelineStepIntegrationConfig)
|
|
1436
|
+
.where(eq(schema.pipelineStepIntegrationConfig.stepId, stepId));
|
|
1437
|
+
} else {
|
|
1438
|
+
// Upsert integration config via upsertStepIntegrationConfig
|
|
1439
|
+
const upsertResult = await upsertStepIntegrationConfig({
|
|
1440
|
+
member,
|
|
1441
|
+
stepId,
|
|
1442
|
+
config: integrationConfig,
|
|
1443
|
+
});
|
|
1444
|
+
if (!upsertResult.ok) return upsertResult;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
return okResult({ success: true });
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
console.error("[updatePipelineStepConfig] Error:", error);
|
|
1451
|
+
return notFoundResult;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
export async function getPipelineStepById({
|
|
1456
|
+
member,
|
|
1457
|
+
stepId,
|
|
1458
|
+
}: {
|
|
1459
|
+
member: Member;
|
|
1460
|
+
stepId: string;
|
|
1461
|
+
}): Promise<
|
|
1462
|
+
DalResult<
|
|
1463
|
+
PipelineStep & {
|
|
1464
|
+
pipeline: Pipeline;
|
|
1465
|
+
outputForms: Array<{ id: string; stepId: string; formId: string; createdAt: Date }>;
|
|
1466
|
+
integrationConfig: PipelineStepIntegrationConfig | null;
|
|
1467
|
+
}
|
|
1468
|
+
>
|
|
1469
|
+
> {
|
|
1470
|
+
try {
|
|
1471
|
+
const step = await db.query.pipelineStep.findFirst({
|
|
1472
|
+
where: eq(schema.pipelineStep.id, stepId),
|
|
1473
|
+
with: {
|
|
1474
|
+
pipeline: true,
|
|
1475
|
+
outputForms: true,
|
|
1476
|
+
integrationConfig: true,
|
|
1477
|
+
},
|
|
1478
|
+
});
|
|
1479
|
+
if (!step || step.pipeline.organizationId !== member.organizationId) return notFoundResult;
|
|
1480
|
+
return okResult(step);
|
|
1481
|
+
} catch (error) {
|
|
1482
|
+
console.error("[getPipelineStepById] Error:", error);
|
|
1483
|
+
return notFoundResult;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
export async function deletePipelineStep({
|
|
1488
|
+
member,
|
|
1489
|
+
stepId,
|
|
1490
|
+
}: {
|
|
1491
|
+
member: Member;
|
|
1492
|
+
stepId: string;
|
|
1493
|
+
}): Promise<DalResult<{ success: true }>> {
|
|
1494
|
+
try {
|
|
1495
|
+
const step = await db.query.pipelineStep.findFirst({
|
|
1496
|
+
where: eq(schema.pipelineStep.id, stepId),
|
|
1497
|
+
with: {
|
|
1498
|
+
pipeline: true,
|
|
1499
|
+
},
|
|
1500
|
+
});
|
|
1501
|
+
if (!step || step.pipeline.organizationId !== member.organizationId) return notFoundResult;
|
|
1502
|
+
|
|
1503
|
+
await db.delete(schema.pipelineStep).where(eq(schema.pipelineStep.id, stepId));
|
|
1504
|
+
|
|
1505
|
+
return okResult({ success: true });
|
|
1506
|
+
} catch {
|
|
1507
|
+
return notFoundResult;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
export async function getPipelineRunStepWithDetails({
|
|
1512
|
+
member,
|
|
1513
|
+
runStepId,
|
|
1514
|
+
}: {
|
|
1515
|
+
member: Member;
|
|
1516
|
+
runStepId: string;
|
|
1517
|
+
}): Promise<
|
|
1518
|
+
DalResult<{
|
|
1519
|
+
step: PipelineRunStep;
|
|
1520
|
+
run: PipelineRun & {
|
|
1521
|
+
pipeline: PipelineWithSteps | null;
|
|
1522
|
+
steps: PipelineRunStep[];
|
|
1523
|
+
resources: { submissionIds: string[]; documentIds: string[] };
|
|
1524
|
+
};
|
|
1525
|
+
}>
|
|
1526
|
+
> {
|
|
1527
|
+
try {
|
|
1528
|
+
const step = await db.query.pipelineRunStep.findFirst({
|
|
1529
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
1530
|
+
with: {
|
|
1531
|
+
run: {
|
|
1532
|
+
with: {
|
|
1533
|
+
pipeline: {
|
|
1534
|
+
with: {
|
|
1535
|
+
steps: {
|
|
1536
|
+
orderBy: asc(schema.pipelineStep.createdAt),
|
|
1537
|
+
with: {
|
|
1538
|
+
outputForms: true,
|
|
1539
|
+
parentDependencies: true,
|
|
1540
|
+
},
|
|
1541
|
+
},
|
|
1542
|
+
},
|
|
1543
|
+
},
|
|
1544
|
+
steps: {
|
|
1545
|
+
orderBy: asc(schema.pipelineRunStep.createdAt),
|
|
1546
|
+
},
|
|
1547
|
+
},
|
|
1548
|
+
},
|
|
1549
|
+
},
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
if (!step) return notFoundResult;
|
|
1553
|
+
if (step.run.organizationId !== member.organizationId) return notFoundResult;
|
|
1554
|
+
|
|
1555
|
+
// TODO: Get run submissions from appropriate table once schema is finalized
|
|
1556
|
+
const submissionIds: string[] = [];
|
|
1557
|
+
|
|
1558
|
+
return okResult({
|
|
1559
|
+
step,
|
|
1560
|
+
run: {
|
|
1561
|
+
...step.run,
|
|
1562
|
+
pipeline: step.run.pipeline
|
|
1563
|
+
? {
|
|
1564
|
+
...step.run.pipeline,
|
|
1565
|
+
steps: normalizeSteps(step.run.pipeline.steps),
|
|
1566
|
+
}
|
|
1567
|
+
: null,
|
|
1568
|
+
resources: { submissionIds, documentIds: [] },
|
|
1569
|
+
},
|
|
1570
|
+
});
|
|
1571
|
+
} catch {
|
|
1572
|
+
return notFoundResult;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
export async function getSubmissionsGroupedByForm({
|
|
1577
|
+
member,
|
|
1578
|
+
formIds,
|
|
1579
|
+
limit = 50,
|
|
1580
|
+
}: {
|
|
1581
|
+
member: Member;
|
|
1582
|
+
formIds?: string[];
|
|
1583
|
+
limit?: number;
|
|
1584
|
+
}): Promise<
|
|
1585
|
+
DalResult<
|
|
1586
|
+
Array<{
|
|
1587
|
+
formId: string;
|
|
1588
|
+
formName: string;
|
|
1589
|
+
submissions: Array<{
|
|
1590
|
+
id: string;
|
|
1591
|
+
name: string;
|
|
1592
|
+
createdAt: Date;
|
|
1593
|
+
updatedAt: Date;
|
|
1594
|
+
status: string;
|
|
1595
|
+
}>;
|
|
1596
|
+
}>
|
|
1597
|
+
>
|
|
1598
|
+
> {
|
|
1599
|
+
try {
|
|
1600
|
+
const whereConditions = [
|
|
1601
|
+
eq(schema.submission.organizationId, member.organizationId),
|
|
1602
|
+
eq(schema.submission.type, "extraction"),
|
|
1603
|
+
isNull(schema.submission.deletedAt),
|
|
1604
|
+
];
|
|
1605
|
+
|
|
1606
|
+
if (formIds && formIds.length > 0) {
|
|
1607
|
+
whereConditions.push(inArray(schema.submission.formId, formIds));
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
const submissions = await db.query.submission.findMany({
|
|
1611
|
+
where: and(...whereConditions),
|
|
1612
|
+
orderBy: desc(schema.submission.updatedAt),
|
|
1613
|
+
limit,
|
|
1614
|
+
with: {
|
|
1615
|
+
form: true,
|
|
1616
|
+
},
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
// Group submissions by form
|
|
1620
|
+
const groupedMap = new Map<
|
|
1621
|
+
string,
|
|
1622
|
+
{
|
|
1623
|
+
formId: string;
|
|
1624
|
+
formName: string;
|
|
1625
|
+
submissions: Array<{
|
|
1626
|
+
id: string;
|
|
1627
|
+
name: string;
|
|
1628
|
+
createdAt: Date;
|
|
1629
|
+
updatedAt: Date;
|
|
1630
|
+
status: string;
|
|
1631
|
+
}>;
|
|
1632
|
+
}
|
|
1633
|
+
>();
|
|
1634
|
+
|
|
1635
|
+
for (const submission of submissions) {
|
|
1636
|
+
const formId = submission.formId;
|
|
1637
|
+
const formName = submission.form.name;
|
|
1638
|
+
|
|
1639
|
+
if (!groupedMap.has(formId)) {
|
|
1640
|
+
groupedMap.set(formId, {
|
|
1641
|
+
formId,
|
|
1642
|
+
formName,
|
|
1643
|
+
submissions: [],
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
groupedMap.get(formId)!.submissions.push({
|
|
1648
|
+
id: submission.id,
|
|
1649
|
+
name: submission.name || "Untitled",
|
|
1650
|
+
createdAt: submission.createdAt,
|
|
1651
|
+
updatedAt: submission.updatedAt,
|
|
1652
|
+
status: submission.status,
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
return okResult(Array.from(groupedMap.values()));
|
|
1657
|
+
} catch {
|
|
1658
|
+
return notFoundResult;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// Placeholder functions for pipeline execution
|
|
1663
|
+
export async function createTransformation({
|
|
1664
|
+
member,
|
|
1665
|
+
runStepId,
|
|
1666
|
+
kind,
|
|
1667
|
+
expression,
|
|
1668
|
+
metadata = {},
|
|
1669
|
+
}: {
|
|
1670
|
+
member: Member;
|
|
1671
|
+
runStepId: string;
|
|
1672
|
+
kind: string;
|
|
1673
|
+
expression?: string;
|
|
1674
|
+
metadata?: object;
|
|
1675
|
+
}): Promise<DalResult<{ id: string }>> {
|
|
1676
|
+
// TODO: Implement transformation creation
|
|
1677
|
+
return notFoundResult;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
export async function upsertTransformationOutputVersions({
|
|
1681
|
+
member,
|
|
1682
|
+
outputSubmissionId,
|
|
1683
|
+
transformationId,
|
|
1684
|
+
outputData,
|
|
1685
|
+
}: {
|
|
1686
|
+
member: Member;
|
|
1687
|
+
outputSubmissionId: string;
|
|
1688
|
+
transformationId: string;
|
|
1689
|
+
outputData: OutputData;
|
|
1690
|
+
}): Promise<DalResult<{ outputVersionIds: string[]; inputVersionIds: string[] }>> {
|
|
1691
|
+
// Use the renamed function
|
|
1692
|
+
const result = await storePipelineOutputData({
|
|
1693
|
+
member,
|
|
1694
|
+
outputSubmissionId,
|
|
1695
|
+
outputData,
|
|
1696
|
+
});
|
|
1697
|
+
|
|
1698
|
+
if (!result.ok) return notFoundResult;
|
|
1699
|
+
|
|
1700
|
+
// TODO: Return actual version IDs once we track them
|
|
1701
|
+
return okResult({ outputVersionIds: [], inputVersionIds: [] });
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
export async function createPipelineArtifact({
|
|
1705
|
+
member,
|
|
1706
|
+
runId,
|
|
1707
|
+
runStepId,
|
|
1708
|
+
type = "markdown",
|
|
1709
|
+
title,
|
|
1710
|
+
content,
|
|
1711
|
+
metadata = {},
|
|
1712
|
+
}: {
|
|
1713
|
+
member: Member;
|
|
1714
|
+
runId: string;
|
|
1715
|
+
runStepId: string;
|
|
1716
|
+
type?: string;
|
|
1717
|
+
title: string;
|
|
1718
|
+
content: object;
|
|
1719
|
+
metadata?: object;
|
|
1720
|
+
}): Promise<DalResult<Artifact>> {
|
|
1721
|
+
try {
|
|
1722
|
+
// Verify run belongs to member's organization
|
|
1723
|
+
const run = await db.query.pipelineRun.findFirst({
|
|
1724
|
+
where: and(
|
|
1725
|
+
eq(schema.pipelineRun.id, runId),
|
|
1726
|
+
eq(schema.pipelineRun.organizationId, member.organizationId),
|
|
1727
|
+
),
|
|
1728
|
+
});
|
|
1729
|
+
if (!run) return notFoundResult;
|
|
1730
|
+
|
|
1731
|
+
// Verify run step exists and belongs to the run
|
|
1732
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
1733
|
+
where: and(eq(schema.pipelineRunStep.id, runStepId), eq(schema.pipelineRunStep.runId, runId)),
|
|
1734
|
+
});
|
|
1735
|
+
if (!runStep) return notFoundResult;
|
|
1736
|
+
|
|
1737
|
+
// Create the artifact
|
|
1738
|
+
const [artifact] = await db
|
|
1739
|
+
.insert(schema.artifact)
|
|
1740
|
+
.values({
|
|
1741
|
+
type,
|
|
1742
|
+
title,
|
|
1743
|
+
content,
|
|
1744
|
+
metadata,
|
|
1745
|
+
})
|
|
1746
|
+
.returning();
|
|
1747
|
+
|
|
1748
|
+
if (!artifact) return notFoundResult;
|
|
1749
|
+
|
|
1750
|
+
// Link artifact to pipeline run and step
|
|
1751
|
+
await db.insert(schema.pipelineRunArtifact).values({
|
|
1752
|
+
runId,
|
|
1753
|
+
artifactId: artifact.id,
|
|
1754
|
+
runStepId,
|
|
1755
|
+
});
|
|
1756
|
+
|
|
1757
|
+
return okResult(artifact);
|
|
1758
|
+
} catch (error) {
|
|
1759
|
+
console.error("[createPipelineArtifact] Error:", error);
|
|
1760
|
+
return notFoundResult;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
export async function logPipelineToolCall({
|
|
1765
|
+
member,
|
|
1766
|
+
runStepId,
|
|
1767
|
+
toolName,
|
|
1768
|
+
args,
|
|
1769
|
+
result,
|
|
1770
|
+
status = PIPELINE_RUN_STEP_STATUS.COMPLETED,
|
|
1771
|
+
latencyMs,
|
|
1772
|
+
}: {
|
|
1773
|
+
member: Member;
|
|
1774
|
+
runStepId: string;
|
|
1775
|
+
toolName: string;
|
|
1776
|
+
args: object;
|
|
1777
|
+
result: object;
|
|
1778
|
+
status?: string;
|
|
1779
|
+
latencyMs?: number;
|
|
1780
|
+
}): Promise<DalResult<ToolCall>> {
|
|
1781
|
+
// TODO: Implement tool call logging
|
|
1782
|
+
return notFoundResult;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// *****************************************
|
|
1786
|
+
// DAG Pipeline Execution Levels (Recursive CTE)
|
|
1787
|
+
// *****************************************
|
|
1788
|
+
|
|
1789
|
+
/**
|
|
1790
|
+
* Get pipeline steps with their execution levels using recursive CTE.
|
|
1791
|
+
* Level 0 = root steps (no dependencies)
|
|
1792
|
+
* Level 1 = depend on level 0
|
|
1793
|
+
* Level N = depend on level N-1
|
|
1794
|
+
*
|
|
1795
|
+
* This enables proper topological ordering for DAG execution.
|
|
1796
|
+
*/
|
|
1797
|
+
export async function getPipelineStepsWithLevels({
|
|
1798
|
+
member,
|
|
1799
|
+
pipelineId,
|
|
1800
|
+
}: {
|
|
1801
|
+
member: Member;
|
|
1802
|
+
pipelineId: string;
|
|
1803
|
+
}) {
|
|
1804
|
+
try {
|
|
1805
|
+
// Use raw SQL for recursive CTE since Drizzle's CTE builder has limitations with recursive joins
|
|
1806
|
+
// Validate pipeline ownership first
|
|
1807
|
+
const result = await db.execute<{
|
|
1808
|
+
id: string;
|
|
1809
|
+
name: string;
|
|
1810
|
+
created_at: Date;
|
|
1811
|
+
role: string | null;
|
|
1812
|
+
type: string;
|
|
1813
|
+
execution_level: number;
|
|
1814
|
+
}>(sql`
|
|
1815
|
+
WITH RECURSIVE step_levels AS (
|
|
1816
|
+
-- Base case: steps with no dependencies (root steps at level 0)
|
|
1817
|
+
SELECT
|
|
1818
|
+
ps.id,
|
|
1819
|
+
ps.name,
|
|
1820
|
+
ps.created_at,
|
|
1821
|
+
ps.role,
|
|
1822
|
+
ps.type,
|
|
1823
|
+
0 as level
|
|
1824
|
+
FROM pipeline_step ps
|
|
1825
|
+
INNER JOIN pipeline p ON ps.pipeline_id = p.id
|
|
1826
|
+
LEFT JOIN pipeline_step_dependency psd ON ps.id = psd.child_step_id
|
|
1827
|
+
WHERE ps.pipeline_id = ${pipelineId}
|
|
1828
|
+
AND p.organization_id = ${member.organizationId}
|
|
1829
|
+
AND p.deleted_at IS NULL
|
|
1830
|
+
AND psd.parent_step_id IS NULL
|
|
1831
|
+
|
|
1832
|
+
UNION ALL
|
|
1833
|
+
|
|
1834
|
+
-- Recursive case: steps that depend on steps already in step_levels
|
|
1835
|
+
SELECT
|
|
1836
|
+
ps.id,
|
|
1837
|
+
ps.name,
|
|
1838
|
+
ps.created_at,
|
|
1839
|
+
ps.role,
|
|
1840
|
+
ps.type,
|
|
1841
|
+
sl.level + 1
|
|
1842
|
+
FROM pipeline_step ps
|
|
1843
|
+
INNER JOIN pipeline_step_dependency psd ON ps.id = psd.child_step_id
|
|
1844
|
+
INNER JOIN step_levels sl ON psd.parent_step_id = sl.id
|
|
1845
|
+
WHERE ps.pipeline_id = ${pipelineId}
|
|
1846
|
+
)
|
|
1847
|
+
-- Get max level per step (in case multiple paths exist)
|
|
1848
|
+
SELECT
|
|
1849
|
+
id,
|
|
1850
|
+
name,
|
|
1851
|
+
created_at,
|
|
1852
|
+
role,
|
|
1853
|
+
type,
|
|
1854
|
+
MAX(level) as execution_level
|
|
1855
|
+
FROM step_levels
|
|
1856
|
+
GROUP BY id, name, created_at, role, type
|
|
1857
|
+
ORDER BY execution_level, created_at
|
|
1858
|
+
`);
|
|
1859
|
+
|
|
1860
|
+
return okResult(
|
|
1861
|
+
result.rows.map((row) => ({
|
|
1862
|
+
id: row.id,
|
|
1863
|
+
name: row.name,
|
|
1864
|
+
createdAt: row.created_at,
|
|
1865
|
+
role: row.role,
|
|
1866
|
+
type: row.type,
|
|
1867
|
+
executionLevel: row.execution_level,
|
|
1868
|
+
})),
|
|
1869
|
+
);
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
console.error("[getPipelineStepsWithLevels] Error:", error);
|
|
1872
|
+
return notFoundResult;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/**
|
|
1877
|
+
* Get unblocked steps for a pipeline run that are ready to execute.
|
|
1878
|
+
* Uses recursive CTE + SELECT FOR UPDATE SKIP LOCKED for safe concurrent worker access.
|
|
1879
|
+
*
|
|
1880
|
+
* A step is "unblocked" if:
|
|
1881
|
+
* - It has no unresolved blockers
|
|
1882
|
+
* - All parent step runs are completed
|
|
1883
|
+
* - It's currently in 'pending' status
|
|
1884
|
+
*/
|
|
1885
|
+
export async function getUnblockedStepsForRun({
|
|
1886
|
+
member,
|
|
1887
|
+
runId,
|
|
1888
|
+
limit = 10,
|
|
1889
|
+
}: {
|
|
1890
|
+
member: Member;
|
|
1891
|
+
runId: string;
|
|
1892
|
+
limit?: number;
|
|
1893
|
+
}) {
|
|
1894
|
+
try {
|
|
1895
|
+
const run = await db.query.pipelineRun.findFirst({
|
|
1896
|
+
where: and(
|
|
1897
|
+
eq(schema.pipelineRun.id, runId),
|
|
1898
|
+
eq(schema.pipelineRun.organizationId, member.organizationId),
|
|
1899
|
+
),
|
|
1900
|
+
});
|
|
1901
|
+
if (!run) return notFoundResult;
|
|
1902
|
+
|
|
1903
|
+
// Get execution levels first to understand ordering
|
|
1904
|
+
const levelsResult = await getPipelineStepsWithLevels({
|
|
1905
|
+
member,
|
|
1906
|
+
pipelineId: run.pipelineId,
|
|
1907
|
+
});
|
|
1908
|
+
if (!levelsResult.ok) return levelsResult;
|
|
1909
|
+
|
|
1910
|
+
// Find step runs that are ready to execute
|
|
1911
|
+
const readyStepRuns = await db.transaction(async (tx) => {
|
|
1912
|
+
return await tx
|
|
1913
|
+
.select({
|
|
1914
|
+
id: schema.pipelineRunStep.id,
|
|
1915
|
+
runId: schema.pipelineRunStep.runId,
|
|
1916
|
+
stepId: schema.pipelineRunStep.stepId,
|
|
1917
|
+
status: schema.pipelineRunStep.status,
|
|
1918
|
+
})
|
|
1919
|
+
.from(schema.pipelineRunStep)
|
|
1920
|
+
.where(
|
|
1921
|
+
and(
|
|
1922
|
+
eq(schema.pipelineRunStep.runId, runId),
|
|
1923
|
+
eq(schema.pipelineRunStep.status, "pending"),
|
|
1924
|
+
// Check no unresolved blockers exist
|
|
1925
|
+
sql`NOT EXISTS (
|
|
1926
|
+
SELECT 1 FROM ${schema.pipelineRunStepBlocker}
|
|
1927
|
+
WHERE ${schema.pipelineRunStepBlocker.runStepId} = ${schema.pipelineRunStep.id}
|
|
1928
|
+
AND ${schema.pipelineRunStepBlocker.resolved} = false
|
|
1929
|
+
)`,
|
|
1930
|
+
// Check all parent step runs are completed
|
|
1931
|
+
sql`NOT EXISTS (
|
|
1932
|
+
SELECT 1 FROM ${schema.pipelineStepDependency} dep
|
|
1933
|
+
INNER JOIN ${schema.pipelineRunStep} parent_run_step
|
|
1934
|
+
ON parent_run_step.step_id = dep.parent_step_id
|
|
1935
|
+
AND parent_run_step.run_id = ${schema.pipelineRunStep.runId}
|
|
1936
|
+
WHERE dep.child_step_id = ${schema.pipelineRunStep.stepId}
|
|
1937
|
+
AND parent_run_step.status != 'completed'
|
|
1938
|
+
)`,
|
|
1939
|
+
),
|
|
1940
|
+
)
|
|
1941
|
+
.limit(limit)
|
|
1942
|
+
.for("update", { skipLocked: true });
|
|
1943
|
+
});
|
|
1944
|
+
|
|
1945
|
+
return okResult(readyStepRuns);
|
|
1946
|
+
} catch (error) {
|
|
1947
|
+
console.error("[getUnblockedStepsForRun] Error:", error);
|
|
1948
|
+
return notFoundResult;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
/**
|
|
1953
|
+
* Get all parent step runs for a given step run (via dependencies)
|
|
1954
|
+
*/
|
|
1955
|
+
export async function getParentStepRuns({
|
|
1956
|
+
member,
|
|
1957
|
+
runStepId,
|
|
1958
|
+
}: {
|
|
1959
|
+
member: Member;
|
|
1960
|
+
runStepId: string;
|
|
1961
|
+
}) {
|
|
1962
|
+
try {
|
|
1963
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
1964
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
1965
|
+
with: { run: true },
|
|
1966
|
+
});
|
|
1967
|
+
if (!runStep || runStep.run.organizationId !== member.organizationId) {
|
|
1968
|
+
return notFoundResult;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// Get parent steps via dependency table
|
|
1972
|
+
const parentStepRuns = await db
|
|
1973
|
+
.select({
|
|
1974
|
+
id: schema.pipelineRunStep.id,
|
|
1975
|
+
stepId: schema.pipelineRunStep.stepId,
|
|
1976
|
+
status: schema.pipelineRunStep.status,
|
|
1977
|
+
})
|
|
1978
|
+
.from(schema.pipelineStepDependency)
|
|
1979
|
+
.innerJoin(
|
|
1980
|
+
schema.pipelineRunStep,
|
|
1981
|
+
and(
|
|
1982
|
+
eq(schema.pipelineStepDependency.parentStepId, schema.pipelineRunStep.stepId),
|
|
1983
|
+
eq(schema.pipelineRunStep.runId, runStep.runId),
|
|
1984
|
+
),
|
|
1985
|
+
)
|
|
1986
|
+
.where(eq(schema.pipelineStepDependency.childStepId, runStep.stepId!));
|
|
1987
|
+
|
|
1988
|
+
return okResult(parentStepRuns);
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
console.error("[getParentStepRuns] Error:", error);
|
|
1991
|
+
return notFoundResult;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
/**
|
|
1996
|
+
* Get parent step runs with their dependency configurations (including formIdMapping)
|
|
1997
|
+
*/
|
|
1998
|
+
export async function getParentStepRunsWithDependencies({
|
|
1999
|
+
member,
|
|
2000
|
+
runStepId,
|
|
2001
|
+
}: {
|
|
2002
|
+
member: Member;
|
|
2003
|
+
runStepId: string;
|
|
2004
|
+
}) {
|
|
2005
|
+
try {
|
|
2006
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
2007
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
2008
|
+
with: { run: true },
|
|
2009
|
+
});
|
|
2010
|
+
if (!runStep || runStep.run.organizationId !== member.organizationId) {
|
|
2011
|
+
return notFoundResult;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// Get parent steps with dependency config
|
|
2015
|
+
const parentStepRuns = await db
|
|
2016
|
+
.select({
|
|
2017
|
+
id: schema.pipelineRunStep.id,
|
|
2018
|
+
stepId: schema.pipelineRunStep.stepId,
|
|
2019
|
+
status: schema.pipelineRunStep.status,
|
|
2020
|
+
formIdMapping: schema.pipelineStepDependency.formIdMapping,
|
|
2021
|
+
multiFormGroupId: schema.pipelineStepDependency.multiFormGroupId,
|
|
2022
|
+
})
|
|
2023
|
+
.from(schema.pipelineStepDependency)
|
|
2024
|
+
.innerJoin(
|
|
2025
|
+
schema.pipelineRunStep,
|
|
2026
|
+
and(
|
|
2027
|
+
eq(schema.pipelineStepDependency.parentStepId, schema.pipelineRunStep.stepId),
|
|
2028
|
+
eq(schema.pipelineRunStep.runId, runStep.runId),
|
|
2029
|
+
),
|
|
2030
|
+
)
|
|
2031
|
+
.where(eq(schema.pipelineStepDependency.childStepId, runStep.stepId!));
|
|
2032
|
+
|
|
2033
|
+
return okResult(parentStepRuns);
|
|
2034
|
+
} catch (error) {
|
|
2035
|
+
console.error("[getParentStepRunsWithDependencies] Error:", error);
|
|
2036
|
+
return notFoundResult;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
/**
|
|
2041
|
+
* Get all child step runs for a given step run (via dependencies)
|
|
2042
|
+
*/
|
|
2043
|
+
export async function getChildStepRuns({
|
|
2044
|
+
member,
|
|
2045
|
+
runStepId,
|
|
2046
|
+
}: {
|
|
2047
|
+
member: Member;
|
|
2048
|
+
runStepId: string;
|
|
2049
|
+
}) {
|
|
2050
|
+
try {
|
|
2051
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
2052
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
2053
|
+
with: { run: true },
|
|
2054
|
+
});
|
|
2055
|
+
if (!runStep || runStep.run.organizationId !== member.organizationId) {
|
|
2056
|
+
return notFoundResult;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
// Get child steps via dependency table
|
|
2060
|
+
const childStepRuns = await db
|
|
2061
|
+
.select({
|
|
2062
|
+
id: schema.pipelineRunStep.id,
|
|
2063
|
+
stepId: schema.pipelineRunStep.stepId,
|
|
2064
|
+
status: schema.pipelineRunStep.status,
|
|
2065
|
+
})
|
|
2066
|
+
.from(schema.pipelineStepDependency)
|
|
2067
|
+
.innerJoin(
|
|
2068
|
+
schema.pipelineRunStep,
|
|
2069
|
+
and(
|
|
2070
|
+
eq(schema.pipelineStepDependency.childStepId, schema.pipelineRunStep.stepId),
|
|
2071
|
+
eq(schema.pipelineRunStep.runId, runStep.runId),
|
|
2072
|
+
),
|
|
2073
|
+
)
|
|
2074
|
+
.where(eq(schema.pipelineStepDependency.parentStepId, runStep.stepId!));
|
|
2075
|
+
|
|
2076
|
+
return okResult(childStepRuns);
|
|
2077
|
+
} catch (error) {
|
|
2078
|
+
console.error("[getChildStepRuns] Error:", error);
|
|
2079
|
+
return notFoundResult;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// *****************************************
|
|
2084
|
+
// Multi-Group → Step Run Linkage
|
|
2085
|
+
// *****************************************
|
|
2086
|
+
|
|
2087
|
+
/**
|
|
2088
|
+
* Get submission groups for a specific formGroupId.
|
|
2089
|
+
* Used to fetch groups from parent output when spawning multi-instance steps.
|
|
2090
|
+
*/
|
|
2091
|
+
export async function getSubmissionGroups({
|
|
2092
|
+
member,
|
|
2093
|
+
submissionId,
|
|
2094
|
+
formGroupId,
|
|
2095
|
+
}: {
|
|
2096
|
+
member: Member;
|
|
2097
|
+
submissionId: string;
|
|
2098
|
+
formGroupId: string;
|
|
2099
|
+
}): Promise<DalResult<SubmissionGroup[]>> {
|
|
2100
|
+
try {
|
|
2101
|
+
const submission = await db.query.submission.findFirst({
|
|
2102
|
+
where: and(
|
|
2103
|
+
eq(schema.submission.id, submissionId),
|
|
2104
|
+
eq(schema.submission.organizationId, member.organizationId),
|
|
2105
|
+
isNull(schema.submission.deletedAt),
|
|
2106
|
+
),
|
|
2107
|
+
});
|
|
2108
|
+
if (!submission) return notFoundResult;
|
|
2109
|
+
|
|
2110
|
+
const groups = await db.query.submissionGroup.findMany({
|
|
2111
|
+
where: and(
|
|
2112
|
+
eq(schema.submissionGroup.submissionId, submissionId),
|
|
2113
|
+
eq(schema.submissionGroup.formGroupId, formGroupId),
|
|
2114
|
+
),
|
|
2115
|
+
orderBy: asc(schema.submissionGroup.order),
|
|
2116
|
+
});
|
|
2117
|
+
|
|
2118
|
+
return okResult(groups);
|
|
2119
|
+
} catch (error) {
|
|
2120
|
+
console.error("[getSubmissionGroups] Error:", error);
|
|
2121
|
+
return internalErrorResult;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
/**
|
|
2126
|
+
* Create child step runs for each group produced by a multi-type parent step.
|
|
2127
|
+
* Links submission groups to their triggered step runs.
|
|
2128
|
+
*
|
|
2129
|
+
* Example: Extraction produces 3 month groups → spawns 3 child analysis runs
|
|
2130
|
+
*
|
|
2131
|
+
* @deprecated This function will be removed. Multi-instance spawning now uses template run steps.
|
|
2132
|
+
*/
|
|
2133
|
+
export async function createChildRunsFromGroups({
|
|
2134
|
+
member,
|
|
2135
|
+
parentRunStepId,
|
|
2136
|
+
submissionId,
|
|
2137
|
+
childStepId,
|
|
2138
|
+
}: {
|
|
2139
|
+
member: Member;
|
|
2140
|
+
parentRunStepId: string;
|
|
2141
|
+
submissionId: string;
|
|
2142
|
+
childStepId: string;
|
|
2143
|
+
}) {
|
|
2144
|
+
try {
|
|
2145
|
+
const parentRunStep = await db.query.pipelineRunStep.findFirst({
|
|
2146
|
+
where: eq(schema.pipelineRunStep.id, parentRunStepId),
|
|
2147
|
+
with: {
|
|
2148
|
+
run: true,
|
|
2149
|
+
step: true,
|
|
2150
|
+
},
|
|
2151
|
+
});
|
|
2152
|
+
|
|
2153
|
+
if (!parentRunStep || parentRunStep.run.organizationId !== member.organizationId) {
|
|
2154
|
+
return notFoundResult;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
// Get dependency config to find which formGroup to spawn for
|
|
2158
|
+
const dependency = await db.query.pipelineStepDependency.findFirst({
|
|
2159
|
+
where: eq(schema.pipelineStepDependency.childStepId, childStepId),
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2162
|
+
if (!dependency?.multiFormGroupId) {
|
|
2163
|
+
return okResult({ childStepRuns: [] }); // Not a multi step, no spawning
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// Get all groups matching the configured formGroup
|
|
2167
|
+
const groups = await db.query.submissionGroup.findMany({
|
|
2168
|
+
where: and(
|
|
2169
|
+
eq(schema.submissionGroup.submissionId, submissionId),
|
|
2170
|
+
eq(schema.submissionGroup.formGroupId, dependency.multiFormGroupId),
|
|
2171
|
+
),
|
|
2172
|
+
orderBy: (groups, { asc }) => [asc(groups.order)],
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
if (groups.length === 0) {
|
|
2176
|
+
return okResult({ childStepRuns: [] });
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
// Create a child step run for each group
|
|
2180
|
+
const childStepRuns = [];
|
|
2181
|
+
|
|
2182
|
+
for (const group of groups) {
|
|
2183
|
+
// Create child step run
|
|
2184
|
+
const [childStepRun] = await db
|
|
2185
|
+
.insert(schema.pipelineRunStep)
|
|
2186
|
+
.values({
|
|
2187
|
+
runId: parentRunStep.runId,
|
|
2188
|
+
stepId: childStepId,
|
|
2189
|
+
status: "pending",
|
|
2190
|
+
})
|
|
2191
|
+
.returning();
|
|
2192
|
+
|
|
2193
|
+
if (!childStepRun) continue;
|
|
2194
|
+
|
|
2195
|
+
childStepRuns.push(childStepRun);
|
|
2196
|
+
|
|
2197
|
+
// Note: Input data filtering will be handled via formIdMapping at execution time.
|
|
2198
|
+
// Each child will receive filtered parent data based on the specific group instance.
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
return okResult({ childStepRuns });
|
|
2202
|
+
} catch (error) {
|
|
2203
|
+
console.error("[createChildRunsFromGroups] Error:", error);
|
|
2204
|
+
return notFoundResult;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
/**
|
|
2209
|
+
* Get the submission group that triggered a specific step run
|
|
2210
|
+
*/
|
|
2211
|
+
export async function getGroupForStepRun({
|
|
2212
|
+
member,
|
|
2213
|
+
runStepId,
|
|
2214
|
+
}: {
|
|
2215
|
+
member: Member;
|
|
2216
|
+
runStepId: string;
|
|
2217
|
+
}) {
|
|
2218
|
+
try {
|
|
2219
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
2220
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
2221
|
+
with: { run: true, step: true },
|
|
2222
|
+
});
|
|
2223
|
+
|
|
2224
|
+
if (
|
|
2225
|
+
!runStep ||
|
|
2226
|
+
runStep.run.organizationId !== member.organizationId ||
|
|
2227
|
+
!runStep.submissionGroupId
|
|
2228
|
+
) {
|
|
2229
|
+
console.error("[getGroupForStepRun] No step associated with run step:", runStepId);
|
|
2230
|
+
return notFoundResult; // No step associated
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
// Find the unique submissionGroup in the output that matches the formGroupId
|
|
2234
|
+
const group = await db.query.submissionGroup.findFirst({
|
|
2235
|
+
with: {
|
|
2236
|
+
formGroup: true,
|
|
2237
|
+
items: {
|
|
2238
|
+
with: {
|
|
2239
|
+
formField: true,
|
|
2240
|
+
versions: {
|
|
2241
|
+
orderBy: [
|
|
2242
|
+
desc(schema.submissionItemVersion.selected),
|
|
2243
|
+
desc(schema.submissionItemVersion.updatedAt),
|
|
2244
|
+
],
|
|
2245
|
+
with: { documentSource: true },
|
|
2246
|
+
limit: 1,
|
|
2247
|
+
},
|
|
2248
|
+
},
|
|
2249
|
+
},
|
|
2250
|
+
derived: {
|
|
2251
|
+
with: {
|
|
2252
|
+
formDerived: true,
|
|
2253
|
+
},
|
|
2254
|
+
},
|
|
2255
|
+
},
|
|
2256
|
+
where: eq(schema.submissionGroup.id, runStep.submissionGroupId),
|
|
2257
|
+
});
|
|
2258
|
+
if (!group) {
|
|
2259
|
+
return notFoundResult;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
const { formGroup, items, derived, ...groupData } = group;
|
|
2263
|
+
const groupDenorm = {
|
|
2264
|
+
...groupData,
|
|
2265
|
+
items: items.map(({ formField, versions, ...itemData }) => ({
|
|
2266
|
+
...itemData,
|
|
2267
|
+
name: formField.name,
|
|
2268
|
+
type: formField.type,
|
|
2269
|
+
hint: formField.hint,
|
|
2270
|
+
value: versions.at(0)?.value ?? null,
|
|
2271
|
+
})),
|
|
2272
|
+
derived: derived.map(({ formDerived, ...derivedData }) => ({
|
|
2273
|
+
...derivedData,
|
|
2274
|
+
name: formDerived.name,
|
|
2275
|
+
prompt: formDerived.prompt,
|
|
2276
|
+
})),
|
|
2277
|
+
name: formGroup.name,
|
|
2278
|
+
hint: formGroup.hint,
|
|
2279
|
+
multi: formGroup.multi,
|
|
2280
|
+
};
|
|
2281
|
+
|
|
2282
|
+
return okResult(groupDenorm);
|
|
2283
|
+
} catch (error) {
|
|
2284
|
+
console.error("[getGroupForStepRun] Error:", error);
|
|
2285
|
+
return notFoundResult;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
// *****************************************
|
|
2290
|
+
// Data Sources: Request Calls & Manual Inputs
|
|
2291
|
+
// *****************************************
|
|
2292
|
+
|
|
2293
|
+
/**
|
|
2294
|
+
* Log an API request call made by an integration step
|
|
2295
|
+
*/
|
|
2296
|
+
export async function logRequestCall({
|
|
2297
|
+
member,
|
|
2298
|
+
runStepId,
|
|
2299
|
+
method,
|
|
2300
|
+
url,
|
|
2301
|
+
requestHeaders = {},
|
|
2302
|
+
requestBody,
|
|
2303
|
+
statusCode,
|
|
2304
|
+
responseHeaders,
|
|
2305
|
+
responseBody,
|
|
2306
|
+
durationMs,
|
|
2307
|
+
error,
|
|
2308
|
+
submissionItemVersionId,
|
|
2309
|
+
}: {
|
|
2310
|
+
member: Member;
|
|
2311
|
+
runStepId: string;
|
|
2312
|
+
method: string;
|
|
2313
|
+
url: string;
|
|
2314
|
+
requestHeaders?: Record<string, string>;
|
|
2315
|
+
requestBody?: unknown;
|
|
2316
|
+
statusCode?: number;
|
|
2317
|
+
responseHeaders?: Record<string, string>;
|
|
2318
|
+
responseBody?: unknown;
|
|
2319
|
+
durationMs?: number;
|
|
2320
|
+
error?: string;
|
|
2321
|
+
submissionItemVersionId?: string;
|
|
2322
|
+
}) {
|
|
2323
|
+
try {
|
|
2324
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
2325
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
2326
|
+
with: { run: true },
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2329
|
+
if (!runStep || runStep.run.organizationId !== member.organizationId) {
|
|
2330
|
+
return notFoundResult;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
const [requestCall] = await db
|
|
2334
|
+
.insert(schema.pipelineRunIntegrationRequest)
|
|
2335
|
+
.values({
|
|
2336
|
+
runStepId,
|
|
2337
|
+
submissionItemVersionId,
|
|
2338
|
+
method,
|
|
2339
|
+
url,
|
|
2340
|
+
requestHeaders,
|
|
2341
|
+
requestBody,
|
|
2342
|
+
statusCode,
|
|
2343
|
+
responseHeaders,
|
|
2344
|
+
responseBody,
|
|
2345
|
+
durationMs,
|
|
2346
|
+
error,
|
|
2347
|
+
})
|
|
2348
|
+
.returning();
|
|
2349
|
+
|
|
2350
|
+
if (!requestCall) return notFoundResult;
|
|
2351
|
+
return okResult(requestCall);
|
|
2352
|
+
} catch (error) {
|
|
2353
|
+
console.error("[logRequestCall] Error:", error);
|
|
2354
|
+
return notFoundResult;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
/**
|
|
2359
|
+
* Get all request calls made by a step run
|
|
2360
|
+
*/
|
|
2361
|
+
export async function getRequestCallsForStepRun({
|
|
2362
|
+
member,
|
|
2363
|
+
runStepId,
|
|
2364
|
+
}: {
|
|
2365
|
+
member: Member;
|
|
2366
|
+
runStepId: string;
|
|
2367
|
+
}) {
|
|
2368
|
+
try {
|
|
2369
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
2370
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
2371
|
+
with: { run: true },
|
|
2372
|
+
});
|
|
2373
|
+
|
|
2374
|
+
if (!runStep || runStep.run.organizationId !== member.organizationId) {
|
|
2375
|
+
return notFoundResult;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
const requestCalls = await db.query.pipelineRunIntegrationRequest.findMany({
|
|
2379
|
+
where: eq(schema.pipelineRunIntegrationRequest.runStepId, runStepId),
|
|
2380
|
+
orderBy: (calls, { desc }) => [desc(calls.createdAt)],
|
|
2381
|
+
});
|
|
2382
|
+
|
|
2383
|
+
return okResult(requestCalls);
|
|
2384
|
+
} catch (error) {
|
|
2385
|
+
console.error("[getRequestCallsForStepRun] Error:", error);
|
|
2386
|
+
return notFoundResult;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
/**
|
|
2391
|
+
* Get a specific request call by ID
|
|
2392
|
+
*/
|
|
2393
|
+
export async function getRequestCall({
|
|
2394
|
+
member,
|
|
2395
|
+
requestCallId,
|
|
2396
|
+
}: {
|
|
2397
|
+
member: Member;
|
|
2398
|
+
requestCallId: string;
|
|
2399
|
+
}) {
|
|
2400
|
+
try {
|
|
2401
|
+
const requestCall = await db.query.pipelineRunIntegrationRequest.findFirst({
|
|
2402
|
+
where: eq(schema.pipelineRunIntegrationRequest.id, requestCallId),
|
|
2403
|
+
with: {
|
|
2404
|
+
runStep: {
|
|
2405
|
+
with: {
|
|
2406
|
+
run: true,
|
|
2407
|
+
step: true,
|
|
2408
|
+
},
|
|
2409
|
+
},
|
|
2410
|
+
},
|
|
2411
|
+
});
|
|
2412
|
+
|
|
2413
|
+
if (!requestCall || requestCall.runStep.run.organizationId !== member.organizationId) {
|
|
2414
|
+
return notFoundResult;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
return okResult(requestCall);
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
console.error("[getRequestCall] Error:", error);
|
|
2420
|
+
return notFoundResult;
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
/**
|
|
2425
|
+
* Remove a manual input from a step run
|
|
2426
|
+
*/
|
|
2427
|
+
export async function removeManualInput({
|
|
2428
|
+
member,
|
|
2429
|
+
manualInputId,
|
|
2430
|
+
}: {
|
|
2431
|
+
member: Member;
|
|
2432
|
+
manualInputId: string;
|
|
2433
|
+
}) {
|
|
2434
|
+
try {
|
|
2435
|
+
// TODO: Implement manual input table
|
|
2436
|
+
// const manualInput = await db.query.pipelineRunStepManualInput.findFirst({
|
|
2437
|
+
// where: eq(schema.pipelineRunStepManualInput.id, manualInputId),
|
|
2438
|
+
// with: {
|
|
2439
|
+
// runStep: {
|
|
2440
|
+
// with: { run: true },
|
|
2441
|
+
// },
|
|
2442
|
+
// },
|
|
2443
|
+
// });
|
|
2444
|
+
|
|
2445
|
+
// if (!manualInput || manualInput.runStep.run.organizationId !== member.organizationId) {
|
|
2446
|
+
// return notFoundResult;
|
|
2447
|
+
// }
|
|
2448
|
+
|
|
2449
|
+
// await db
|
|
2450
|
+
// .delete(schema.pipelineRunStepManualInput)
|
|
2451
|
+
// .where(eq(schema.pipelineRunStepManualInput.id, manualInputId));
|
|
2452
|
+
|
|
2453
|
+
// return okResult({ success: true });
|
|
2454
|
+
return notFoundResult;
|
|
2455
|
+
} catch (error) {
|
|
2456
|
+
console.error("[removeManualInput] Error:", error);
|
|
2457
|
+
return notFoundResult;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
// *****************************************
|
|
2462
|
+
// Step Dependency Management
|
|
2463
|
+
// *****************************************
|
|
2464
|
+
|
|
2465
|
+
/**
|
|
2466
|
+
* Add a parent dependency to a step
|
|
2467
|
+
*/
|
|
2468
|
+
export async function addStepDependency({
|
|
2469
|
+
member,
|
|
2470
|
+
childStepId,
|
|
2471
|
+
parentStepId,
|
|
2472
|
+
multiFormGroupId,
|
|
2473
|
+
}: {
|
|
2474
|
+
member: Member;
|
|
2475
|
+
childStepId: string;
|
|
2476
|
+
parentStepId: string;
|
|
2477
|
+
multiFormGroupId?: string | null;
|
|
2478
|
+
}) {
|
|
2479
|
+
try {
|
|
2480
|
+
// Verify both steps exist and belong to the member's organization
|
|
2481
|
+
const childStep = await db.query.pipelineStep.findFirst({
|
|
2482
|
+
where: eq(schema.pipelineStep.id, childStepId),
|
|
2483
|
+
with: { pipeline: true },
|
|
2484
|
+
});
|
|
2485
|
+
|
|
2486
|
+
if (!childStep || childStep.pipeline.organizationId !== member.organizationId) {
|
|
2487
|
+
return notFoundResult;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
const parentStep = await db.query.pipelineStep.findFirst({
|
|
2491
|
+
where: eq(schema.pipelineStep.id, parentStepId),
|
|
2492
|
+
with: { pipeline: true },
|
|
2493
|
+
});
|
|
2494
|
+
|
|
2495
|
+
if (!parentStep || parentStep.pipeline.organizationId !== member.organizationId) {
|
|
2496
|
+
return notFoundResult;
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
// Check if they're in the same pipeline
|
|
2500
|
+
if (childStep.pipelineId !== parentStep.pipelineId) {
|
|
2501
|
+
return {
|
|
2502
|
+
ok: false,
|
|
2503
|
+
error: "validation-error",
|
|
2504
|
+
message: "Both steps must be in the same pipeline",
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
// Check if dependency already exists
|
|
2509
|
+
const existing = await db.query.pipelineStepDependency.findFirst({
|
|
2510
|
+
where: and(
|
|
2511
|
+
eq(schema.pipelineStepDependency.childStepId, childStepId),
|
|
2512
|
+
eq(schema.pipelineStepDependency.parentStepId, parentStepId),
|
|
2513
|
+
),
|
|
2514
|
+
});
|
|
2515
|
+
|
|
2516
|
+
if (existing) {
|
|
2517
|
+
return {
|
|
2518
|
+
ok: false,
|
|
2519
|
+
error: "validation-error",
|
|
2520
|
+
message: "Dependency already exists",
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// Create the dependency
|
|
2525
|
+
const [dependency] = await db
|
|
2526
|
+
.insert(schema.pipelineStepDependency)
|
|
2527
|
+
.values({
|
|
2528
|
+
id: nanoid(),
|
|
2529
|
+
childStepId,
|
|
2530
|
+
parentStepId,
|
|
2531
|
+
multiFormGroupId: multiFormGroupId ?? null,
|
|
2532
|
+
})
|
|
2533
|
+
.returning();
|
|
2534
|
+
|
|
2535
|
+
if (!dependency) return internalErrorResult;
|
|
2536
|
+
|
|
2537
|
+
return okResult({
|
|
2538
|
+
id: dependency.id,
|
|
2539
|
+
childStepId: dependency.childStepId,
|
|
2540
|
+
parentStepId: dependency.parentStepId,
|
|
2541
|
+
multiFormGroupId: dependency.multiFormGroupId,
|
|
2542
|
+
});
|
|
2543
|
+
} catch (error) {
|
|
2544
|
+
console.error("[addStepDependency] Error:", error);
|
|
2545
|
+
return internalErrorResult;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
/**
|
|
2550
|
+
* Remove a step dependency
|
|
2551
|
+
*/
|
|
2552
|
+
export async function removeStepDependency({
|
|
2553
|
+
member,
|
|
2554
|
+
dependencyId,
|
|
2555
|
+
}: {
|
|
2556
|
+
member: Member;
|
|
2557
|
+
dependencyId: string;
|
|
2558
|
+
}) {
|
|
2559
|
+
try {
|
|
2560
|
+
const dependency = await db.query.pipelineStepDependency.findFirst({
|
|
2561
|
+
where: eq(schema.pipelineStepDependency.id, dependencyId),
|
|
2562
|
+
with: {
|
|
2563
|
+
childStep: {
|
|
2564
|
+
with: { pipeline: true },
|
|
2565
|
+
},
|
|
2566
|
+
},
|
|
2567
|
+
});
|
|
2568
|
+
|
|
2569
|
+
if (!dependency || dependency.childStep.pipeline.organizationId !== member.organizationId) {
|
|
2570
|
+
return notFoundResult;
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
await db
|
|
2574
|
+
.delete(schema.pipelineStepDependency)
|
|
2575
|
+
.where(eq(schema.pipelineStepDependency.id, dependencyId));
|
|
2576
|
+
|
|
2577
|
+
return okResult({ success: true });
|
|
2578
|
+
} catch (error) {
|
|
2579
|
+
console.error("[removeStepDependency] Error:", error);
|
|
2580
|
+
return internalErrorResult;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
export async function getStepDependenciesByParentStepId({
|
|
2585
|
+
member,
|
|
2586
|
+
parentStepId,
|
|
2587
|
+
}: {
|
|
2588
|
+
member: Member;
|
|
2589
|
+
parentStepId: string;
|
|
2590
|
+
}) {
|
|
2591
|
+
try {
|
|
2592
|
+
const parentStep = await db.query.pipelineStep.findFirst({
|
|
2593
|
+
where: eq(schema.pipelineStep.id, parentStepId),
|
|
2594
|
+
with: { pipeline: true },
|
|
2595
|
+
});
|
|
2596
|
+
|
|
2597
|
+
if (!parentStep || parentStep.pipeline.organizationId !== member.organizationId) {
|
|
2598
|
+
return notFoundResult;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
const dependencies = await db.query.pipelineStepDependency.findMany({
|
|
2602
|
+
where: eq(schema.pipelineStepDependency.parentStepId, parentStepId),
|
|
2603
|
+
with: {
|
|
2604
|
+
childStep: true,
|
|
2605
|
+
},
|
|
2606
|
+
});
|
|
2607
|
+
|
|
2608
|
+
return okResult(dependencies);
|
|
2609
|
+
} catch (error) {
|
|
2610
|
+
console.error("[getStepDependenciesByParentStepId] Error:", error);
|
|
2611
|
+
return internalErrorResult;
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
// *****************************************
|
|
2616
|
+
// Document Management (Extraction Steps)
|
|
2617
|
+
// *****************************************
|
|
2618
|
+
|
|
2619
|
+
/**
|
|
2620
|
+
* Get documents linked to extraction step run
|
|
2621
|
+
*/
|
|
2622
|
+
export async function getStepDocuments({
|
|
2623
|
+
member,
|
|
2624
|
+
runStepId,
|
|
2625
|
+
}: {
|
|
2626
|
+
member: Member;
|
|
2627
|
+
runStepId: string;
|
|
2628
|
+
}) {
|
|
2629
|
+
try {
|
|
2630
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
2631
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
2632
|
+
with: {
|
|
2633
|
+
run: {
|
|
2634
|
+
with: { pipeline: true },
|
|
2635
|
+
},
|
|
2636
|
+
documents: true,
|
|
2637
|
+
},
|
|
2638
|
+
});
|
|
2639
|
+
|
|
2640
|
+
if (!runStep || runStep.run.pipeline.organizationId !== member.organizationId) {
|
|
2641
|
+
return notFoundResult;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
// Load full document records
|
|
2645
|
+
const documentIds = runStep.documents.map((d) => d.documentId);
|
|
2646
|
+
if (documentIds.length === 0) {
|
|
2647
|
+
return okResult([]);
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
const documents = await db.query.document.findMany({
|
|
2651
|
+
where: inArray(schema.document.id, documentIds),
|
|
2652
|
+
});
|
|
2653
|
+
|
|
2654
|
+
return okResult(documents);
|
|
2655
|
+
} catch (error) {
|
|
2656
|
+
console.error("[getStepDocuments] Error:", error);
|
|
2657
|
+
return internalErrorResult;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
/**
|
|
2662
|
+
* Link document to extraction step run
|
|
2663
|
+
*/
|
|
2664
|
+
export async function addStepDocument({
|
|
2665
|
+
member,
|
|
2666
|
+
runStepId,
|
|
2667
|
+
documentId,
|
|
2668
|
+
}: {
|
|
2669
|
+
member: Member;
|
|
2670
|
+
runStepId: string;
|
|
2671
|
+
documentId: string;
|
|
2672
|
+
}) {
|
|
2673
|
+
try {
|
|
2674
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
2675
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
2676
|
+
with: {
|
|
2677
|
+
run: {
|
|
2678
|
+
with: { pipeline: true },
|
|
2679
|
+
},
|
|
2680
|
+
},
|
|
2681
|
+
});
|
|
2682
|
+
|
|
2683
|
+
if (!runStep || runStep.run.pipeline.organizationId !== member.organizationId) {
|
|
2684
|
+
return notFoundResult;
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
// Verify document exists and belongs to organization
|
|
2688
|
+
const document = await db.query.document.findFirst({
|
|
2689
|
+
where: eq(schema.document.id, documentId),
|
|
2690
|
+
with: {
|
|
2691
|
+
deal: true,
|
|
2692
|
+
},
|
|
2693
|
+
});
|
|
2694
|
+
|
|
2695
|
+
if (!document || document.deal.organizationId !== member.organizationId) {
|
|
2696
|
+
return notFoundResult;
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
// Check if already linked
|
|
2700
|
+
const existing = await db.query.pipelineRunStepDocument.findFirst({
|
|
2701
|
+
where: and(
|
|
2702
|
+
eq(schema.pipelineRunStepDocument.runStepId, runStepId),
|
|
2703
|
+
eq(schema.pipelineRunStepDocument.documentId, documentId),
|
|
2704
|
+
),
|
|
2705
|
+
});
|
|
2706
|
+
|
|
2707
|
+
if (existing) {
|
|
2708
|
+
return okResult({ success: true }); // Already linked, no-op
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
await db.insert(schema.pipelineRunStepDocument).values({
|
|
2712
|
+
id: nanoid(),
|
|
2713
|
+
runStepId,
|
|
2714
|
+
documentId,
|
|
2715
|
+
});
|
|
2716
|
+
|
|
2717
|
+
return okResult({ success: true });
|
|
2718
|
+
} catch (error) {
|
|
2719
|
+
console.error("[addStepDocument] Error:", error);
|
|
2720
|
+
return internalErrorResult;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
/**
|
|
2725
|
+
* Unlink document from extraction step run
|
|
2726
|
+
*/
|
|
2727
|
+
export async function removeStepDocument({
|
|
2728
|
+
member,
|
|
2729
|
+
runStepId,
|
|
2730
|
+
documentId,
|
|
2731
|
+
}: {
|
|
2732
|
+
member: Member;
|
|
2733
|
+
runStepId: string;
|
|
2734
|
+
documentId: string;
|
|
2735
|
+
}) {
|
|
2736
|
+
try {
|
|
2737
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
2738
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
2739
|
+
with: {
|
|
2740
|
+
run: {
|
|
2741
|
+
with: { pipeline: true },
|
|
2742
|
+
},
|
|
2743
|
+
},
|
|
2744
|
+
});
|
|
2745
|
+
|
|
2746
|
+
if (!runStep || runStep.run.pipeline.organizationId !== member.organizationId) {
|
|
2747
|
+
return notFoundResult;
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
await db
|
|
2751
|
+
.delete(schema.pipelineRunStepDocument)
|
|
2752
|
+
.where(
|
|
2753
|
+
and(
|
|
2754
|
+
eq(schema.pipelineRunStepDocument.runStepId, runStepId),
|
|
2755
|
+
eq(schema.pipelineRunStepDocument.documentId, documentId),
|
|
2756
|
+
),
|
|
2757
|
+
);
|
|
2758
|
+
|
|
2759
|
+
return okResult({ success: true });
|
|
2760
|
+
} catch (error) {
|
|
2761
|
+
console.error("[removeStepDocument] Error:", error);
|
|
2762
|
+
return internalErrorResult;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
// *****************************************
|
|
2767
|
+
// Integration Config Management
|
|
2768
|
+
// *****************************************
|
|
2769
|
+
|
|
2770
|
+
/**
|
|
2771
|
+
* Get integration config for step
|
|
2772
|
+
*/
|
|
2773
|
+
export async function getStepIntegrationConfig({
|
|
2774
|
+
member,
|
|
2775
|
+
stepId,
|
|
2776
|
+
}: {
|
|
2777
|
+
member: Member;
|
|
2778
|
+
stepId: string;
|
|
2779
|
+
}): Promise<DalResult<PipelineStepIntegrationConfig | null>> {
|
|
2780
|
+
try {
|
|
2781
|
+
const step = await db.query.pipelineStep.findFirst({
|
|
2782
|
+
where: eq(schema.pipelineStep.id, stepId),
|
|
2783
|
+
with: {
|
|
2784
|
+
pipeline: true,
|
|
2785
|
+
integrationConfig: true,
|
|
2786
|
+
},
|
|
2787
|
+
});
|
|
2788
|
+
|
|
2789
|
+
if (!step || step.pipeline.organizationId !== member.organizationId) {
|
|
2790
|
+
return notFoundResult;
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
return okResult(step.integrationConfig);
|
|
2794
|
+
} catch (error) {
|
|
2795
|
+
console.error("[getStepIntegrationConfig] Error:", error);
|
|
2796
|
+
return internalErrorResult;
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
/**
|
|
2801
|
+
* Create/update integration config for step
|
|
2802
|
+
*/
|
|
2803
|
+
export async function upsertStepIntegrationConfig({
|
|
2804
|
+
member,
|
|
2805
|
+
stepId,
|
|
2806
|
+
config,
|
|
2807
|
+
}: {
|
|
2808
|
+
member: Member;
|
|
2809
|
+
stepId: string;
|
|
2810
|
+
config: {
|
|
2811
|
+
method: string;
|
|
2812
|
+
urlTemplate: string;
|
|
2813
|
+
headers: Record<string, string>;
|
|
2814
|
+
bodyTemplate?: string;
|
|
2815
|
+
};
|
|
2816
|
+
}): Promise<DalResult<PipelineStepIntegrationConfig>> {
|
|
2817
|
+
try {
|
|
2818
|
+
const step = await db.query.pipelineStep.findFirst({
|
|
2819
|
+
where: eq(schema.pipelineStep.id, stepId),
|
|
2820
|
+
with: {
|
|
2821
|
+
pipeline: true,
|
|
2822
|
+
integrationConfig: true,
|
|
2823
|
+
},
|
|
2824
|
+
});
|
|
2825
|
+
|
|
2826
|
+
if (!step || step.pipeline.organizationId !== member.organizationId) {
|
|
2827
|
+
return notFoundResult;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
if (step.integrationConfig) {
|
|
2831
|
+
// Update existing
|
|
2832
|
+
const [updated] = await db
|
|
2833
|
+
.update(schema.pipelineStepIntegrationConfig)
|
|
2834
|
+
.set({
|
|
2835
|
+
method: config.method,
|
|
2836
|
+
urlTemplate: config.urlTemplate,
|
|
2837
|
+
headers: config.headers,
|
|
2838
|
+
bodyTemplate: config.bodyTemplate,
|
|
2839
|
+
updatedAt: new Date(),
|
|
2840
|
+
})
|
|
2841
|
+
.where(eq(schema.pipelineStepIntegrationConfig.stepId, stepId))
|
|
2842
|
+
.returning();
|
|
2843
|
+
|
|
2844
|
+
if (!updated) {
|
|
2845
|
+
return internalErrorResult;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
return okResult(updated);
|
|
2849
|
+
} else {
|
|
2850
|
+
// Create new
|
|
2851
|
+
const [created] = await db
|
|
2852
|
+
.insert(schema.pipelineStepIntegrationConfig)
|
|
2853
|
+
.values({
|
|
2854
|
+
id: nanoid(),
|
|
2855
|
+
stepId,
|
|
2856
|
+
method: config.method,
|
|
2857
|
+
urlTemplate: config.urlTemplate,
|
|
2858
|
+
headers: config.headers,
|
|
2859
|
+
bodyTemplate: config.bodyTemplate,
|
|
2860
|
+
})
|
|
2861
|
+
.returning();
|
|
2862
|
+
|
|
2863
|
+
if (!created) {
|
|
2864
|
+
return internalErrorResult;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
return okResult(created);
|
|
2868
|
+
}
|
|
2869
|
+
} catch (error) {
|
|
2870
|
+
console.error("[upsertStepIntegrationConfig] Error:", error);
|
|
2871
|
+
return internalErrorResult;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
/**
|
|
2876
|
+
* Delete integration config
|
|
2877
|
+
*/
|
|
2878
|
+
export async function deleteStepIntegrationConfig({
|
|
2879
|
+
member,
|
|
2880
|
+
stepId,
|
|
2881
|
+
}: {
|
|
2882
|
+
member: Member;
|
|
2883
|
+
stepId: string;
|
|
2884
|
+
}): Promise<DalResult<{ success: true }>> {
|
|
2885
|
+
try {
|
|
2886
|
+
const step = await db.query.pipelineStep.findFirst({
|
|
2887
|
+
where: eq(schema.pipelineStep.id, stepId),
|
|
2888
|
+
with: {
|
|
2889
|
+
pipeline: true,
|
|
2890
|
+
},
|
|
2891
|
+
});
|
|
2892
|
+
|
|
2893
|
+
if (!step || step.pipeline.organizationId !== member.organizationId) {
|
|
2894
|
+
return notFoundResult;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
await db
|
|
2898
|
+
.delete(schema.pipelineStepIntegrationConfig)
|
|
2899
|
+
.where(eq(schema.pipelineStepIntegrationConfig.stepId, stepId));
|
|
2900
|
+
|
|
2901
|
+
return okResult({ success: true });
|
|
2902
|
+
} catch (error) {
|
|
2903
|
+
console.error("[deleteStepIntegrationConfig] Error:", error);
|
|
2904
|
+
return internalErrorResult;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
// *****************************************
|
|
2909
|
+
// Recursive Citation Chain
|
|
2910
|
+
// *****************************************
|
|
2911
|
+
|
|
2912
|
+
export type CitationNode =
|
|
2913
|
+
| {
|
|
2914
|
+
type: "document";
|
|
2915
|
+
documentCitationId: string;
|
|
2916
|
+
documentExtractionId: string;
|
|
2917
|
+
documentId: string;
|
|
2918
|
+
documentName: string;
|
|
2919
|
+
path: string;
|
|
2920
|
+
text: string;
|
|
2921
|
+
sourceRef: string;
|
|
2922
|
+
}
|
|
2923
|
+
| {
|
|
2924
|
+
type: "integration";
|
|
2925
|
+
requestCallId: string;
|
|
2926
|
+
method: string;
|
|
2927
|
+
url: string;
|
|
2928
|
+
statusCode: number | null;
|
|
2929
|
+
durationMs: number | null;
|
|
2930
|
+
}
|
|
2931
|
+
| {
|
|
2932
|
+
type: "transformation";
|
|
2933
|
+
submissionItemVersionId: string;
|
|
2934
|
+
sourceItemVersionIds: string[];
|
|
2935
|
+
children: CitationNode[];
|
|
2936
|
+
}
|
|
2937
|
+
| {
|
|
2938
|
+
type: "manual";
|
|
2939
|
+
submissionItemVersionId: string;
|
|
2940
|
+
value: string | number | null;
|
|
2941
|
+
};
|
|
2942
|
+
|
|
2943
|
+
/**
|
|
2944
|
+
* Recursively walk the citation chain for a submission item version.
|
|
2945
|
+
* Returns a tree showing all source data until reaching leaf nodes (documents or API calls).
|
|
2946
|
+
*
|
|
2947
|
+
* Example output:
|
|
2948
|
+
* {
|
|
2949
|
+
* type: "transformation",
|
|
2950
|
+
* children: [
|
|
2951
|
+
* { type: "document", documentName: "Q1 Report.pdf", page: 3 },
|
|
2952
|
+
* { type: "integration", url: "GET /financials/q2", statusCode: 200 }
|
|
2953
|
+
* ]
|
|
2954
|
+
* }
|
|
2955
|
+
*/
|
|
2956
|
+
export async function getCitationChain({
|
|
2957
|
+
member,
|
|
2958
|
+
submissionItemVersionId,
|
|
2959
|
+
}: {
|
|
2960
|
+
member: Member;
|
|
2961
|
+
submissionItemVersionId: string;
|
|
2962
|
+
}): Promise<DalResult<CitationNode>> {
|
|
2963
|
+
try {
|
|
2964
|
+
// First get the version, then check org access via joins
|
|
2965
|
+
const version = await db.query.submissionItemVersion.findFirst({
|
|
2966
|
+
where: eq(schema.submissionItemVersion.id, submissionItemVersionId),
|
|
2967
|
+
});
|
|
2968
|
+
|
|
2969
|
+
if (!version) return notFoundResult;
|
|
2970
|
+
|
|
2971
|
+
// Verify organization access
|
|
2972
|
+
const item = await db.query.submissionItem.findFirst({
|
|
2973
|
+
where: eq(schema.submissionItem.id, version.submissionItemId),
|
|
2974
|
+
});
|
|
2975
|
+
if (!item) return notFoundResult;
|
|
2976
|
+
|
|
2977
|
+
const group = await db.query.submissionGroup.findFirst({
|
|
2978
|
+
where: eq(schema.submissionGroup.id, item.submissionGroupId),
|
|
2979
|
+
});
|
|
2980
|
+
if (!group) return notFoundResult;
|
|
2981
|
+
|
|
2982
|
+
const submission = await db.query.submission.findFirst({
|
|
2983
|
+
where: eq(schema.submission.id, group.submissionId),
|
|
2984
|
+
});
|
|
2985
|
+
if (!submission || submission.organizationId !== member.organizationId) {
|
|
2986
|
+
return notFoundResult;
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
const node = await buildCitationNode(version);
|
|
2990
|
+
return okResult(node);
|
|
2991
|
+
} catch (error) {
|
|
2992
|
+
console.error("[getCitationChain] Error:", error);
|
|
2993
|
+
return notFoundResult;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
/**
|
|
2998
|
+
* Recursive helper to build a citation node tree
|
|
2999
|
+
*/
|
|
3000
|
+
async function buildCitationNode(
|
|
3001
|
+
version: typeof schema.submissionItemVersion.$inferSelect,
|
|
3002
|
+
): Promise<CitationNode> {
|
|
3003
|
+
switch (version.sourceType) {
|
|
3004
|
+
case "document": {
|
|
3005
|
+
if (!version.sourceId) {
|
|
3006
|
+
throw new Error("Document source missing sourceId");
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
const citation = await db.query.documentCitation.findFirst({
|
|
3010
|
+
where: eq(schema.documentCitation.id, version.sourceId),
|
|
3011
|
+
});
|
|
3012
|
+
|
|
3013
|
+
if (!citation) {
|
|
3014
|
+
throw new Error(`Document citation ${version.sourceId} not found`);
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
const extraction = await db.query.documentExtraction.findFirst({
|
|
3018
|
+
where: eq(schema.documentExtraction.id, citation.documentExtractionId),
|
|
3019
|
+
});
|
|
3020
|
+
|
|
3021
|
+
const document = extraction
|
|
3022
|
+
? await db.query.document.findFirst({
|
|
3023
|
+
where: eq(schema.document.id, extraction.documentId),
|
|
3024
|
+
})
|
|
3025
|
+
: null;
|
|
3026
|
+
|
|
3027
|
+
return {
|
|
3028
|
+
type: "document",
|
|
3029
|
+
documentCitationId: citation.id,
|
|
3030
|
+
documentExtractionId: citation.documentExtractionId,
|
|
3031
|
+
documentId: document?.id || "",
|
|
3032
|
+
documentName: document?.name || "Unknown Document",
|
|
3033
|
+
path: citation.path,
|
|
3034
|
+
text: citation.text,
|
|
3035
|
+
sourceRef: citation.sourceRef,
|
|
3036
|
+
};
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
case "integration": {
|
|
3040
|
+
if (!version.sourceId) {
|
|
3041
|
+
throw new Error("Integration source missing sourceId");
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
const requestCall = await db.query.pipelineRunIntegrationRequest.findFirst({
|
|
3045
|
+
where: eq(schema.pipelineRunIntegrationRequest.id, version.sourceId),
|
|
3046
|
+
});
|
|
3047
|
+
|
|
3048
|
+
if (!requestCall) {
|
|
3049
|
+
throw new Error(`Request call ${version.sourceId} not found`);
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
return {
|
|
3053
|
+
type: "integration",
|
|
3054
|
+
requestCallId: requestCall.id,
|
|
3055
|
+
method: requestCall.method,
|
|
3056
|
+
url: requestCall.url,
|
|
3057
|
+
statusCode: requestCall.statusCode,
|
|
3058
|
+
durationMs: requestCall.durationMs,
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
case "transformation": {
|
|
3063
|
+
// Get the transformation that produced this version using sourceId
|
|
3064
|
+
if (!version.sourceId) {
|
|
3065
|
+
return {
|
|
3066
|
+
type: "transformation",
|
|
3067
|
+
submissionItemVersionId: version.id,
|
|
3068
|
+
sourceItemVersionIds: [],
|
|
3069
|
+
children: [],
|
|
3070
|
+
};
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
const transformation = await db.query.dataTransformation.findFirst({
|
|
3074
|
+
where: eq(schema.dataTransformation.id, version.sourceId),
|
|
3075
|
+
});
|
|
3076
|
+
|
|
3077
|
+
if (!transformation || !transformation.sourceItemVersionIds) {
|
|
3078
|
+
return {
|
|
3079
|
+
type: "transformation",
|
|
3080
|
+
submissionItemVersionId: version.id,
|
|
3081
|
+
sourceItemVersionIds: [],
|
|
3082
|
+
children: [],
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// Recursively get source versions
|
|
3087
|
+
const sourceVersions = await db.query.submissionItemVersion.findMany({
|
|
3088
|
+
where: sql`${schema.submissionItemVersion.id} = ANY(ARRAY[${sql.join(
|
|
3089
|
+
transformation.sourceItemVersionIds.map((id) => sql`${id}`),
|
|
3090
|
+
sql`, `,
|
|
3091
|
+
)}]::text[])`,
|
|
3092
|
+
});
|
|
3093
|
+
|
|
3094
|
+
const children = await Promise.all(sourceVersions.map((sv) => buildCitationNode(sv)));
|
|
3095
|
+
|
|
3096
|
+
return {
|
|
3097
|
+
type: "transformation",
|
|
3098
|
+
submissionItemVersionId: version.id,
|
|
3099
|
+
sourceItemVersionIds: transformation.sourceItemVersionIds,
|
|
3100
|
+
children,
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
case "manual": {
|
|
3105
|
+
return {
|
|
3106
|
+
type: "manual",
|
|
3107
|
+
submissionItemVersionId: version.id,
|
|
3108
|
+
value: version.value,
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
case "merged":
|
|
3113
|
+
case "message":
|
|
3114
|
+
default: {
|
|
3115
|
+
// For other types, return as transformation with no children for now
|
|
3116
|
+
return {
|
|
3117
|
+
type: "transformation",
|
|
3118
|
+
submissionItemVersionId: version.id,
|
|
3119
|
+
sourceItemVersionIds: [],
|
|
3120
|
+
children: [],
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
/**
|
|
3127
|
+
* Get all leaf citations (documents and API calls) for a submission item version.
|
|
3128
|
+
* Flattens the recursive tree into a list of leaf nodes.
|
|
3129
|
+
*/
|
|
3130
|
+
export async function getLeafCitations({
|
|
3131
|
+
member,
|
|
3132
|
+
submissionItemVersionId,
|
|
3133
|
+
}: {
|
|
3134
|
+
member: Member;
|
|
3135
|
+
submissionItemVersionId: string;
|
|
3136
|
+
}) {
|
|
3137
|
+
try {
|
|
3138
|
+
const chainResult = await getCitationChain({ member, submissionItemVersionId });
|
|
3139
|
+
if (!chainResult.ok) return chainResult;
|
|
3140
|
+
|
|
3141
|
+
const leaves: CitationNode[] = [];
|
|
3142
|
+
|
|
3143
|
+
function collectLeaves(node: CitationNode) {
|
|
3144
|
+
if (node.type === "document" || node.type === "integration" || node.type === "manual") {
|
|
3145
|
+
leaves.push(node);
|
|
3146
|
+
} else if (node.type === "transformation") {
|
|
3147
|
+
for (const child of node.children) {
|
|
3148
|
+
collectLeaves(child);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
collectLeaves(chainResult.data);
|
|
3154
|
+
return okResult(leaves);
|
|
3155
|
+
} catch (error) {
|
|
3156
|
+
console.error("[getLeafCitations] Error:", error);
|
|
3157
|
+
return notFoundResult;
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
// ========================================================================================
|
|
3162
|
+
// Blocker Management
|
|
3163
|
+
// ========================================================================================
|
|
3164
|
+
|
|
3165
|
+
/**
|
|
3166
|
+
* Create a pipeline run with pre-flight validation and blocker creation.
|
|
3167
|
+
* Checks all steps for potential blockers before execution begins.
|
|
3168
|
+
*/
|
|
3169
|
+
export async function createPipelineRunWithBlockers({
|
|
3170
|
+
member,
|
|
3171
|
+
pipelineId,
|
|
3172
|
+
dealId,
|
|
3173
|
+
inputDocumentIds = [],
|
|
3174
|
+
inputSubmissionIds = [],
|
|
3175
|
+
triggerType = "manual",
|
|
3176
|
+
}: {
|
|
3177
|
+
member: Member;
|
|
3178
|
+
pipelineId: string;
|
|
3179
|
+
dealId: string;
|
|
3180
|
+
inputDocumentIds?: string[];
|
|
3181
|
+
inputSubmissionIds?: string[];
|
|
3182
|
+
triggerType?: string;
|
|
3183
|
+
}): Promise<
|
|
3184
|
+
DalResult<{ runId: string; blockers: Array<{ stepId: string; type: string; details: object }> }>
|
|
3185
|
+
> {
|
|
3186
|
+
try {
|
|
3187
|
+
// First create the run using existing function
|
|
3188
|
+
const runResult = await createPipelineRun({
|
|
3189
|
+
member,
|
|
3190
|
+
pipelineId,
|
|
3191
|
+
dealId,
|
|
3192
|
+
triggerType,
|
|
3193
|
+
});
|
|
3194
|
+
|
|
3195
|
+
if (!runResult.ok) return runResult;
|
|
3196
|
+
const run = runResult.data;
|
|
3197
|
+
|
|
3198
|
+
// Get pipeline with steps
|
|
3199
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
3200
|
+
where: and(
|
|
3201
|
+
eq(schema.pipeline.id, pipelineId),
|
|
3202
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
3203
|
+
isNull(schema.pipeline.deletedAt),
|
|
3204
|
+
),
|
|
3205
|
+
with: {
|
|
3206
|
+
steps: {
|
|
3207
|
+
orderBy: asc(schema.pipelineStep.createdAt),
|
|
3208
|
+
with: {
|
|
3209
|
+
outputForms: true,
|
|
3210
|
+
integrationConfig: true,
|
|
3211
|
+
},
|
|
3212
|
+
},
|
|
3213
|
+
},
|
|
3214
|
+
});
|
|
3215
|
+
|
|
3216
|
+
if (!pipeline) return notFoundResult;
|
|
3217
|
+
|
|
3218
|
+
const blockers: Array<{ stepId: string; type: string; details: object }> = [];
|
|
3219
|
+
|
|
3220
|
+
// Get all dependencies for all steps
|
|
3221
|
+
const allDependencies = await db.query.pipelineStepDependency.findMany({
|
|
3222
|
+
where: inArray(
|
|
3223
|
+
schema.pipelineStepDependency.childStepId,
|
|
3224
|
+
pipeline.steps.map((s) => s.id),
|
|
3225
|
+
),
|
|
3226
|
+
});
|
|
3227
|
+
|
|
3228
|
+
// Group dependencies by child step
|
|
3229
|
+
const dependenciesByStep = new Map<string, typeof allDependencies>();
|
|
3230
|
+
for (const dep of allDependencies) {
|
|
3231
|
+
const existing = dependenciesByStep.get(dep.childStepId) || [];
|
|
3232
|
+
existing.push(dep);
|
|
3233
|
+
dependenciesByStep.set(dep.childStepId, existing);
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
// Create run steps for all pipeline steps
|
|
3237
|
+
const runSteps = await Promise.all(
|
|
3238
|
+
pipeline.steps.map(async (step) => {
|
|
3239
|
+
const runStepResult = await createPipelineRunStep({
|
|
3240
|
+
member,
|
|
3241
|
+
runId: run.id,
|
|
3242
|
+
stepId: step.id,
|
|
3243
|
+
});
|
|
3244
|
+
return runStepResult.ok ? runStepResult.data : null;
|
|
3245
|
+
}),
|
|
3246
|
+
);
|
|
3247
|
+
|
|
3248
|
+
// Check each step for blockers
|
|
3249
|
+
for (let i = 0; i < pipeline.steps.length; i++) {
|
|
3250
|
+
const step = pipeline.steps[i];
|
|
3251
|
+
const runStep = runSteps[i];
|
|
3252
|
+
|
|
3253
|
+
if (!runStep || !step) continue;
|
|
3254
|
+
|
|
3255
|
+
// Check extraction steps have input documents
|
|
3256
|
+
if (step.role === "extraction") {
|
|
3257
|
+
if (inputDocumentIds.length === 0 && inputSubmissionIds.length === 0) {
|
|
3258
|
+
await createStepRunBlocker({
|
|
3259
|
+
runStepId: runStep.id,
|
|
3260
|
+
blockerType: "missing_upload",
|
|
3261
|
+
blockerDetails: {
|
|
3262
|
+
type: "missing_upload",
|
|
3263
|
+
message: "No input documents or submissions provided",
|
|
3264
|
+
},
|
|
3265
|
+
});
|
|
3266
|
+
blockers.push({
|
|
3267
|
+
stepId: step.id,
|
|
3268
|
+
type: "missing_upload",
|
|
3269
|
+
details: { message: "No input documents or submissions provided" },
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
// Check integration steps have config
|
|
3275
|
+
if (step.role === "integration" && !step.integrationConfig) {
|
|
3276
|
+
await createStepRunBlocker({
|
|
3277
|
+
runStepId: runStep.id,
|
|
3278
|
+
blockerType: "missing_config",
|
|
3279
|
+
blockerDetails: {
|
|
3280
|
+
type: "missing_config",
|
|
3281
|
+
message: "Integration step missing configuration",
|
|
3282
|
+
},
|
|
3283
|
+
});
|
|
3284
|
+
blockers.push({
|
|
3285
|
+
stepId: step.id,
|
|
3286
|
+
type: "missing_config",
|
|
3287
|
+
details: { message: "Integration step missing configuration" },
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
// Check analysis steps have output form
|
|
3292
|
+
if ((step.role === "analysis" || step.role === null) && step.outputForms.length === 0) {
|
|
3293
|
+
await createStepRunBlocker({
|
|
3294
|
+
runStepId: runStep.id,
|
|
3295
|
+
blockerType: "missing_config",
|
|
3296
|
+
blockerDetails: { type: "missing_config", message: "Analysis step missing output form" },
|
|
3297
|
+
});
|
|
3298
|
+
blockers.push({
|
|
3299
|
+
stepId: step.id,
|
|
3300
|
+
type: "missing_config",
|
|
3301
|
+
details: { message: "Analysis step missing output form" },
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
// Check manual steps have output forms and create blockers for each required form
|
|
3306
|
+
if (step.role === "manual") {
|
|
3307
|
+
if (step.outputForms.length === 0) {
|
|
3308
|
+
await createStepRunBlocker({
|
|
3309
|
+
runStepId: runStep.id,
|
|
3310
|
+
blockerType: "missing_config",
|
|
3311
|
+
blockerDetails: {
|
|
3312
|
+
type: "missing_config",
|
|
3313
|
+
message: "Manual step missing output form configuration",
|
|
3314
|
+
},
|
|
3315
|
+
});
|
|
3316
|
+
blockers.push({
|
|
3317
|
+
stepId: step.id,
|
|
3318
|
+
type: "missing_config",
|
|
3319
|
+
details: { message: "Manual step missing output form configuration" },
|
|
3320
|
+
});
|
|
3321
|
+
} else {
|
|
3322
|
+
// Create a blocker for each required output form
|
|
3323
|
+
const forms = await db.query.form.findMany({
|
|
3324
|
+
where: inArray(
|
|
3325
|
+
schema.form.id,
|
|
3326
|
+
step.outputForms.map((f) => f.formId),
|
|
3327
|
+
),
|
|
3328
|
+
});
|
|
3329
|
+
const formIdToName = new Map(forms.map((f) => [f.id, f.name]));
|
|
3330
|
+
|
|
3331
|
+
for (const outputForm of step.outputForms) {
|
|
3332
|
+
await createStepRunBlocker({
|
|
3333
|
+
runStepId: runStep.id,
|
|
3334
|
+
blockerType: "missing_manual_input",
|
|
3335
|
+
blockerDetails: {
|
|
3336
|
+
type: "missing_manual_input",
|
|
3337
|
+
stepName: step.name,
|
|
3338
|
+
stepDescription: step.description,
|
|
3339
|
+
requiredFormId: outputForm.formId,
|
|
3340
|
+
requiredFormName: formIdToName.get(outputForm.formId) || outputForm.formId,
|
|
3341
|
+
},
|
|
3342
|
+
});
|
|
3343
|
+
blockers.push({
|
|
3344
|
+
stepId: step.id,
|
|
3345
|
+
type: "missing_manual_input",
|
|
3346
|
+
details: {
|
|
3347
|
+
stepName: step.name,
|
|
3348
|
+
stepDescription: step.description,
|
|
3349
|
+
requiredFormId: outputForm.formId,
|
|
3350
|
+
requiredFormName: formIdToName.get(outputForm.formId) || outputForm.formId,
|
|
3351
|
+
},
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
// Mark steps with dependencies as blocked by parent_incomplete
|
|
3358
|
+
const stepDependencies = dependenciesByStep.get(step.id) || [];
|
|
3359
|
+
if (stepDependencies.length > 0) {
|
|
3360
|
+
await createStepRunBlocker({
|
|
3361
|
+
runStepId: runStep.id,
|
|
3362
|
+
blockerType: "parent_incomplete",
|
|
3363
|
+
blockerDetails: {
|
|
3364
|
+
type: "parent_incomplete",
|
|
3365
|
+
parentStepIds: stepDependencies.map((d) => d.parentStepId),
|
|
3366
|
+
message: "Waiting for parent steps to complete",
|
|
3367
|
+
},
|
|
3368
|
+
});
|
|
3369
|
+
blockers.push({
|
|
3370
|
+
stepId: step.id,
|
|
3371
|
+
type: "parent_incomplete",
|
|
3372
|
+
details: {
|
|
3373
|
+
type: "parent_incomplete",
|
|
3374
|
+
parentStepIds: stepDependencies.map((d) => d.parentStepId),
|
|
3375
|
+
message: "Waiting for parent steps to complete",
|
|
3376
|
+
},
|
|
3377
|
+
});
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
return okResult({ runId: run.id, blockers });
|
|
3382
|
+
} catch (error) {
|
|
3383
|
+
console.error("[createPipelineRunWithBlockers] Error:", error);
|
|
3384
|
+
return notFoundResult;
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
/**
|
|
3389
|
+
* Sync blockers for an existing pipeline run
|
|
3390
|
+
* Removes outdated blockers and creates new ones based on current state
|
|
3391
|
+
*/
|
|
3392
|
+
export async function syncPipelineRunBlockers({
|
|
3393
|
+
member,
|
|
3394
|
+
runId,
|
|
3395
|
+
}: {
|
|
3396
|
+
member: Member;
|
|
3397
|
+
runId: string;
|
|
3398
|
+
}) {
|
|
3399
|
+
try {
|
|
3400
|
+
// Get the run with pipeline and steps
|
|
3401
|
+
const run = await db.query.pipelineRun.findFirst({
|
|
3402
|
+
where: and(
|
|
3403
|
+
eq(schema.pipelineRun.id, runId),
|
|
3404
|
+
eq(schema.pipelineRun.organizationId, member.organizationId),
|
|
3405
|
+
),
|
|
3406
|
+
with: {
|
|
3407
|
+
pipeline: {
|
|
3408
|
+
with: {
|
|
3409
|
+
steps: {
|
|
3410
|
+
orderBy: asc(schema.pipelineStep.createdAt),
|
|
3411
|
+
with: {
|
|
3412
|
+
outputForms: true,
|
|
3413
|
+
integrationConfig: true,
|
|
3414
|
+
},
|
|
3415
|
+
},
|
|
3416
|
+
},
|
|
3417
|
+
},
|
|
3418
|
+
steps: {
|
|
3419
|
+
with: {
|
|
3420
|
+
documents: true,
|
|
3421
|
+
},
|
|
3422
|
+
},
|
|
3423
|
+
},
|
|
3424
|
+
});
|
|
3425
|
+
|
|
3426
|
+
if (!run) return notFoundResult;
|
|
3427
|
+
|
|
3428
|
+
// Get all dependencies for pipeline steps
|
|
3429
|
+
const allDependencies = await db.query.pipelineStepDependency.findMany({
|
|
3430
|
+
where: inArray(
|
|
3431
|
+
schema.pipelineStepDependency.childStepId,
|
|
3432
|
+
run.pipeline.steps.map((s) => s.id),
|
|
3433
|
+
),
|
|
3434
|
+
});
|
|
3435
|
+
|
|
3436
|
+
// Group dependencies by child step
|
|
3437
|
+
const dependenciesByStep = new Map<string, typeof allDependencies>();
|
|
3438
|
+
for (const dep of allDependencies) {
|
|
3439
|
+
const existing = dependenciesByStep.get(dep.childStepId) || [];
|
|
3440
|
+
existing.push(dep);
|
|
3441
|
+
dependenciesByStep.set(dep.childStepId, existing);
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
// TODO: Get existing run submissions once schema is finalized
|
|
3445
|
+
const hasInputs = false;
|
|
3446
|
+
let syncedCount = 0;
|
|
3447
|
+
|
|
3448
|
+
// Delete all existing unresolved blockers (we'll recreate them if still needed)
|
|
3449
|
+
await db.delete(schema.pipelineRunStepBlocker).where(
|
|
3450
|
+
and(
|
|
3451
|
+
inArray(
|
|
3452
|
+
schema.pipelineRunStepBlocker.runStepId,
|
|
3453
|
+
run.steps.map((s) => s.id),
|
|
3454
|
+
),
|
|
3455
|
+
eq(schema.pipelineRunStepBlocker.resolved, false),
|
|
3456
|
+
),
|
|
3457
|
+
);
|
|
3458
|
+
|
|
3459
|
+
// Check each run step for blockers
|
|
3460
|
+
for (const runStep of run.steps) {
|
|
3461
|
+
const step = run.pipeline.steps.find((s) => s.id === runStep.stepId);
|
|
3462
|
+
if (!step) continue;
|
|
3463
|
+
|
|
3464
|
+
// Skip completed or failed steps
|
|
3465
|
+
if (
|
|
3466
|
+
runStep.status === PIPELINE_RUN_STEP_STATUS.COMPLETED ||
|
|
3467
|
+
runStep.status === PIPELINE_RUN_STEP_STATUS.FAILED
|
|
3468
|
+
) {
|
|
3469
|
+
continue;
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
// Check extraction steps have input documents
|
|
3473
|
+
if (step.role === "extraction" && runStep.documents.length === 0) {
|
|
3474
|
+
await createStepRunBlocker({
|
|
3475
|
+
runStepId: runStep.id,
|
|
3476
|
+
blockerType: "missing_upload",
|
|
3477
|
+
blockerDetails: {
|
|
3478
|
+
type: "missing_upload",
|
|
3479
|
+
message: "No documents linked to extraction step",
|
|
3480
|
+
},
|
|
3481
|
+
});
|
|
3482
|
+
syncedCount++;
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
// Check integration steps have config
|
|
3486
|
+
if (step.role === "integration" && !step.integrationConfig) {
|
|
3487
|
+
await createStepRunBlocker({
|
|
3488
|
+
runStepId: runStep.id,
|
|
3489
|
+
blockerType: "missing_config",
|
|
3490
|
+
blockerDetails: {
|
|
3491
|
+
type: "missing_config",
|
|
3492
|
+
message: "Integration step missing configuration",
|
|
3493
|
+
},
|
|
3494
|
+
});
|
|
3495
|
+
syncedCount++;
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
// Check analysis steps have output form
|
|
3499
|
+
if ((step.role === "analysis" || step.role === null) && step.outputForms.length === 0) {
|
|
3500
|
+
await createStepRunBlocker({
|
|
3501
|
+
runStepId: runStep.id,
|
|
3502
|
+
blockerType: "missing_config",
|
|
3503
|
+
blockerDetails: { type: "missing_config", message: "Analysis step missing output form" },
|
|
3504
|
+
});
|
|
3505
|
+
syncedCount++;
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
// Check manual steps have output submissions for all required forms
|
|
3509
|
+
if (step.role === "manual") {
|
|
3510
|
+
if (step.outputForms.length === 0) {
|
|
3511
|
+
await createStepRunBlocker({
|
|
3512
|
+
runStepId: runStep.id,
|
|
3513
|
+
blockerType: "missing_config",
|
|
3514
|
+
blockerDetails: {
|
|
3515
|
+
type: "missing_config",
|
|
3516
|
+
message: "Manual step missing output form configuration",
|
|
3517
|
+
},
|
|
3518
|
+
});
|
|
3519
|
+
syncedCount++;
|
|
3520
|
+
} else {
|
|
3521
|
+
// Get existing output submissions for this run step
|
|
3522
|
+
const outputSubmissions = await db.query.pipelineRunStepOutputSubmission.findMany({
|
|
3523
|
+
where: eq(schema.pipelineRunStepOutputSubmission.runStepId, runStep.id),
|
|
3524
|
+
});
|
|
3525
|
+
|
|
3526
|
+
// Load submission forms for all outputs
|
|
3527
|
+
const outputSubmissionDetails = await Promise.all(
|
|
3528
|
+
outputSubmissions.map(async (output) => {
|
|
3529
|
+
const submission = await db.query.submission.findFirst({
|
|
3530
|
+
where: eq(schema.submission.id, output.submissionId),
|
|
3531
|
+
});
|
|
3532
|
+
return { ...output, formId: submission?.formId };
|
|
3533
|
+
}),
|
|
3534
|
+
);
|
|
3535
|
+
|
|
3536
|
+
// Load form names
|
|
3537
|
+
const forms = await db.query.form.findMany({
|
|
3538
|
+
where: inArray(
|
|
3539
|
+
schema.form.id,
|
|
3540
|
+
step.outputForms.map((f) => f.formId),
|
|
3541
|
+
),
|
|
3542
|
+
});
|
|
3543
|
+
const formIdToName = new Map(forms.map((f) => [f.id, f.name]));
|
|
3544
|
+
|
|
3545
|
+
// Create a blocker for each required form that doesn't have a submission
|
|
3546
|
+
for (const outputForm of step.outputForms) {
|
|
3547
|
+
const hasSubmissionForForm = outputSubmissionDetails.some(
|
|
3548
|
+
(output) => output.formId === outputForm.formId,
|
|
3549
|
+
);
|
|
3550
|
+
|
|
3551
|
+
if (!hasSubmissionForForm) {
|
|
3552
|
+
await createStepRunBlocker({
|
|
3553
|
+
runStepId: runStep.id,
|
|
3554
|
+
blockerType: "missing_manual_input",
|
|
3555
|
+
blockerDetails: {
|
|
3556
|
+
type: "missing_manual_input",
|
|
3557
|
+
stepName: step.name,
|
|
3558
|
+
stepDescription: step.description,
|
|
3559
|
+
requiredFormId: outputForm.formId,
|
|
3560
|
+
requiredFormName: formIdToName.get(outputForm.formId) || outputForm.formId,
|
|
3561
|
+
},
|
|
3562
|
+
});
|
|
3563
|
+
syncedCount++;
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
// Mark steps with dependencies as blocked by parent_incomplete
|
|
3570
|
+
const stepDependencies = dependenciesByStep.get(step.id) || [];
|
|
3571
|
+
if (stepDependencies.length > 0) {
|
|
3572
|
+
// Check if any parent steps are incomplete
|
|
3573
|
+
const parentRunSteps = run.steps.filter((rs) =>
|
|
3574
|
+
stepDependencies.some((d) => d.parentStepId === rs.stepId),
|
|
3575
|
+
);
|
|
3576
|
+
const hasIncompleteParents = parentRunSteps.some(
|
|
3577
|
+
(ps) =>
|
|
3578
|
+
ps.status !== PIPELINE_RUN_STEP_STATUS.COMPLETED &&
|
|
3579
|
+
ps.status !== PIPELINE_RUN_STEP_STATUS.FAILED,
|
|
3580
|
+
);
|
|
3581
|
+
|
|
3582
|
+
if (hasIncompleteParents) {
|
|
3583
|
+
await createStepRunBlocker({
|
|
3584
|
+
runStepId: runStep.id,
|
|
3585
|
+
blockerType: "parent_incomplete",
|
|
3586
|
+
blockerDetails: {
|
|
3587
|
+
type: "parent_incomplete",
|
|
3588
|
+
parentStepIds: stepDependencies.map((d) => d.parentStepId),
|
|
3589
|
+
message: "Waiting for parent steps to complete",
|
|
3590
|
+
},
|
|
3591
|
+
});
|
|
3592
|
+
syncedCount++;
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
return okResult({ blockersSynced: syncedCount });
|
|
3598
|
+
} catch (error) {
|
|
3599
|
+
console.error("[syncPipelineRunBlockers] Error:", error);
|
|
3600
|
+
return internalErrorResult;
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
/**
|
|
3605
|
+
* Resolve blockers for a step when it completes successfully
|
|
3606
|
+
*/
|
|
3607
|
+
export async function resolveStepBlockersOnCompletion({
|
|
3608
|
+
member,
|
|
3609
|
+
runStepId,
|
|
3610
|
+
}: {
|
|
3611
|
+
member: Member;
|
|
3612
|
+
runStepId: string;
|
|
3613
|
+
}) {
|
|
3614
|
+
try {
|
|
3615
|
+
const runStep = await db.query.pipelineRunStep.findFirst({
|
|
3616
|
+
where: eq(schema.pipelineRunStep.id, runStepId),
|
|
3617
|
+
with: {
|
|
3618
|
+
run: true,
|
|
3619
|
+
step: true,
|
|
3620
|
+
},
|
|
3621
|
+
});
|
|
3622
|
+
|
|
3623
|
+
if (!runStep || runStep.run.organizationId !== member.organizationId) {
|
|
3624
|
+
return notFoundResult;
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3627
|
+
let resolvedCount = 0;
|
|
3628
|
+
|
|
3629
|
+
console.log(`[resolveStepBlockersOnCompletion] Starting for runStepId: ${runStepId}`);
|
|
3630
|
+
console.log(`[resolveStepBlockersOnCompletion] Run step details:`, {
|
|
3631
|
+
runStepId: runStep.id,
|
|
3632
|
+
stepId: runStep.stepId,
|
|
3633
|
+
status: runStep.status,
|
|
3634
|
+
});
|
|
3635
|
+
|
|
3636
|
+
// 1. Resolve this step's own blockers
|
|
3637
|
+
const ownBlockers = await db
|
|
3638
|
+
.update(schema.pipelineRunStepBlocker)
|
|
3639
|
+
.set({
|
|
3640
|
+
resolved: true,
|
|
3641
|
+
resolvedAt: new Date(),
|
|
3642
|
+
})
|
|
3643
|
+
.where(
|
|
3644
|
+
and(
|
|
3645
|
+
eq(schema.pipelineRunStepBlocker.runStepId, runStepId),
|
|
3646
|
+
eq(schema.pipelineRunStepBlocker.resolved, false),
|
|
3647
|
+
),
|
|
3648
|
+
)
|
|
3649
|
+
.returning({ id: schema.pipelineRunStepBlocker.id });
|
|
3650
|
+
|
|
3651
|
+
resolvedCount += ownBlockers.length;
|
|
3652
|
+
console.log(`[resolveStepBlockersOnCompletion] Resolved ${ownBlockers.length} own blocker(s)`);
|
|
3653
|
+
|
|
3654
|
+
// 2. Find child steps and check if their parent_incomplete blockers can be resolved
|
|
3655
|
+
console.log(
|
|
3656
|
+
`[resolveStepBlockersOnCompletion] Looking for child steps with parentStepId: ${runStep.stepId}`,
|
|
3657
|
+
);
|
|
3658
|
+
|
|
3659
|
+
const childStepRuns = await db
|
|
3660
|
+
.select({
|
|
3661
|
+
id: schema.pipelineRunStep.id,
|
|
3662
|
+
stepId: schema.pipelineRunStep.stepId,
|
|
3663
|
+
})
|
|
3664
|
+
.from(schema.pipelineStepDependency)
|
|
3665
|
+
.innerJoin(
|
|
3666
|
+
schema.pipelineRunStep,
|
|
3667
|
+
and(
|
|
3668
|
+
eq(schema.pipelineStepDependency.childStepId, schema.pipelineRunStep.stepId),
|
|
3669
|
+
eq(schema.pipelineRunStep.runId, runStep.runId),
|
|
3670
|
+
),
|
|
3671
|
+
)
|
|
3672
|
+
.where(eq(schema.pipelineStepDependency.parentStepId, runStep.stepId!));
|
|
3673
|
+
|
|
3674
|
+
console.log(
|
|
3675
|
+
`[resolveStepBlockersOnCompletion] Found ${childStepRuns.length} child run step(s)`,
|
|
3676
|
+
);
|
|
3677
|
+
if (childStepRuns.length > 0) {
|
|
3678
|
+
console.log(
|
|
3679
|
+
`[resolveStepBlockersOnCompletion] Child run steps:`,
|
|
3680
|
+
childStepRuns.map((c) => ({ id: c.id, stepId: c.stepId })),
|
|
3681
|
+
);
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
for (const childStepRun of childStepRuns) {
|
|
3685
|
+
console.log(
|
|
3686
|
+
`[resolveStepBlockersOnCompletion] Checking child run step: ${childStepRun.id} (stepId: ${childStepRun.stepId})`,
|
|
3687
|
+
);
|
|
3688
|
+
|
|
3689
|
+
// Check if all parents of this child are completed
|
|
3690
|
+
const parentStepRuns = await db
|
|
3691
|
+
.select({
|
|
3692
|
+
id: schema.pipelineRunStep.id,
|
|
3693
|
+
status: schema.pipelineRunStep.status,
|
|
3694
|
+
})
|
|
3695
|
+
.from(schema.pipelineStepDependency)
|
|
3696
|
+
.innerJoin(
|
|
3697
|
+
schema.pipelineRunStep,
|
|
3698
|
+
and(
|
|
3699
|
+
eq(schema.pipelineStepDependency.parentStepId, schema.pipelineRunStep.stepId),
|
|
3700
|
+
eq(schema.pipelineRunStep.runId, runStep.runId),
|
|
3701
|
+
),
|
|
3702
|
+
)
|
|
3703
|
+
.where(eq(schema.pipelineStepDependency.childStepId, childStepRun.stepId!));
|
|
3704
|
+
|
|
3705
|
+
console.log(
|
|
3706
|
+
`[resolveStepBlockersOnCompletion] Found ${parentStepRuns.length} parent run step(s) for child ${childStepRun.id}`,
|
|
3707
|
+
);
|
|
3708
|
+
console.log(
|
|
3709
|
+
`[resolveStepBlockersOnCompletion] Parent statuses:`,
|
|
3710
|
+
parentStepRuns.map((p) => ({ id: p.id, status: p.status })),
|
|
3711
|
+
);
|
|
3712
|
+
|
|
3713
|
+
const allParentsComplete = parentStepRuns.every(
|
|
3714
|
+
(p) => p.status === PIPELINE_RUN_STEP_STATUS.COMPLETED,
|
|
3715
|
+
);
|
|
3716
|
+
|
|
3717
|
+
console.log(`[resolveStepBlockersOnCompletion] All parents complete? ${allParentsComplete}`);
|
|
3718
|
+
|
|
3719
|
+
if (allParentsComplete) {
|
|
3720
|
+
// Resolve parent_incomplete blockers for this child
|
|
3721
|
+
console.log(
|
|
3722
|
+
`[resolveStepBlockersOnCompletion] Resolving parent_incomplete blockers for child ${childStepRun.id}`,
|
|
3723
|
+
);
|
|
3724
|
+
const childBlockers = await db
|
|
3725
|
+
.update(schema.pipelineRunStepBlocker)
|
|
3726
|
+
.set({
|
|
3727
|
+
resolved: true,
|
|
3728
|
+
resolvedAt: new Date(),
|
|
3729
|
+
})
|
|
3730
|
+
.where(
|
|
3731
|
+
and(
|
|
3732
|
+
eq(schema.pipelineRunStepBlocker.runStepId, childStepRun.id),
|
|
3733
|
+
eq(schema.pipelineRunStepBlocker.blockerType, "parent_incomplete"),
|
|
3734
|
+
eq(schema.pipelineRunStepBlocker.resolved, false),
|
|
3735
|
+
),
|
|
3736
|
+
)
|
|
3737
|
+
.returning({ id: schema.pipelineRunStepBlocker.id });
|
|
3738
|
+
|
|
3739
|
+
console.log(
|
|
3740
|
+
`[resolveStepBlockersOnCompletion] Resolved ${childBlockers.length} blocker(s) for child ${childStepRun.id}`,
|
|
3741
|
+
);
|
|
3742
|
+
resolvedCount += childBlockers.length;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
console.log(`[resolveStepBlockersOnCompletion] Total resolved: ${resolvedCount}`);
|
|
3747
|
+
return okResult({ resolvedCount });
|
|
3748
|
+
} catch (error) {
|
|
3749
|
+
console.error("[resolveStepBlockersOnCompletion] Error:", error);
|
|
3750
|
+
return notFoundResult;
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3754
|
+
/**
|
|
3755
|
+
* Create a blocker for a pipeline run step
|
|
3756
|
+
*/
|
|
3757
|
+
export async function createStepRunBlocker<T extends PipelineRunStepBlockerType>(params: {
|
|
3758
|
+
runStepId: string;
|
|
3759
|
+
blockerType: T;
|
|
3760
|
+
blockerDetails: Extract<PipelineRunStepBlockerDetails, { type: T }>;
|
|
3761
|
+
}) {
|
|
3762
|
+
try {
|
|
3763
|
+
const [blocker] = await db
|
|
3764
|
+
.insert(schema.pipelineRunStepBlocker)
|
|
3765
|
+
.values({
|
|
3766
|
+
runStepId: params.runStepId,
|
|
3767
|
+
blockerType: params.blockerType,
|
|
3768
|
+
blockerDetails: params.blockerDetails,
|
|
3769
|
+
})
|
|
3770
|
+
.returning({ id: schema.pipelineRunStepBlocker.id });
|
|
3771
|
+
|
|
3772
|
+
if (!blocker) return notFoundResult;
|
|
3773
|
+
return okResult(blocker);
|
|
3774
|
+
} catch (error) {
|
|
3775
|
+
console.error("[createStepRunBlocker] Error:", error);
|
|
3776
|
+
return notFoundResult;
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
/**
|
|
3781
|
+
* Resolve a blocker by ID
|
|
3782
|
+
*/
|
|
3783
|
+
export async function resolveBlocker(blockerId: string) {
|
|
3784
|
+
try {
|
|
3785
|
+
const [updated] = await db
|
|
3786
|
+
.update(schema.pipelineRunStepBlocker)
|
|
3787
|
+
.set({
|
|
3788
|
+
resolved: true,
|
|
3789
|
+
resolvedAt: new Date(),
|
|
3790
|
+
})
|
|
3791
|
+
.where(eq(schema.pipelineRunStepBlocker.id, blockerId))
|
|
3792
|
+
.returning({ id: schema.pipelineRunStepBlocker.id });
|
|
3793
|
+
|
|
3794
|
+
if (!updated) return notFoundResult;
|
|
3795
|
+
return okResult({ success: true });
|
|
3796
|
+
} catch (error) {
|
|
3797
|
+
console.error("[resolveBlocker] Error:", error);
|
|
3798
|
+
return notFoundResult;
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
/**
|
|
3803
|
+
* Get all blockers for a step run (optionally filter by resolved status)
|
|
3804
|
+
*/
|
|
3805
|
+
export async function getStepRunBlockers(params: { runStepId: string; resolved?: boolean }) {
|
|
3806
|
+
try {
|
|
3807
|
+
const conditions = [eq(schema.pipelineRunStepBlocker.runStepId, params.runStepId)];
|
|
3808
|
+
|
|
3809
|
+
if (params.resolved !== undefined) {
|
|
3810
|
+
conditions.push(eq(schema.pipelineRunStepBlocker.resolved, params.resolved));
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
const blockers = await db
|
|
3814
|
+
.select({
|
|
3815
|
+
id: schema.pipelineRunStepBlocker.id,
|
|
3816
|
+
blockerType: schema.pipelineRunStepBlocker.blockerType,
|
|
3817
|
+
blockerDetails: schema.pipelineRunStepBlocker.blockerDetails,
|
|
3818
|
+
resolved: schema.pipelineRunStepBlocker.resolved,
|
|
3819
|
+
resolvedAt: schema.pipelineRunStepBlocker.resolvedAt,
|
|
3820
|
+
createdAt: schema.pipelineRunStepBlocker.createdAt,
|
|
3821
|
+
})
|
|
3822
|
+
.from(schema.pipelineRunStepBlocker)
|
|
3823
|
+
.where(and(...conditions));
|
|
3824
|
+
|
|
3825
|
+
return okResult(blockers);
|
|
3826
|
+
} catch (error) {
|
|
3827
|
+
console.error("[getStepRunBlockers] Error:", error);
|
|
3828
|
+
return notFoundResult;
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
/**
|
|
3833
|
+
* Check if a step run has any unresolved blockers
|
|
3834
|
+
*/
|
|
3835
|
+
export async function hasUnresolvedBlockers(runStepId: string) {
|
|
3836
|
+
try {
|
|
3837
|
+
const [result] = await db
|
|
3838
|
+
.select({ count: count() })
|
|
3839
|
+
.from(schema.pipelineRunStepBlocker)
|
|
3840
|
+
.where(
|
|
3841
|
+
and(
|
|
3842
|
+
eq(schema.pipelineRunStepBlocker.runStepId, runStepId),
|
|
3843
|
+
eq(schema.pipelineRunStepBlocker.resolved, false),
|
|
3844
|
+
),
|
|
3845
|
+
);
|
|
3846
|
+
|
|
3847
|
+
return okResult((result?.count ?? 0) > 0);
|
|
3848
|
+
} catch (error) {
|
|
3849
|
+
console.error("[hasUnresolvedBlockers] Error:", error);
|
|
3850
|
+
return notFoundResult;
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
/**
|
|
3855
|
+
* Get all blockers for all steps in a pipeline run, grouped by step.
|
|
3856
|
+
* Only returns level 0 blockers (user-actionable) - filters out parent_incomplete
|
|
3857
|
+
* since those are automatically resolved when dependencies complete.
|
|
3858
|
+
*/
|
|
3859
|
+
export async function getAllBlockersForRun({ member, runId }: { member: Member; runId: string }) {
|
|
3860
|
+
try {
|
|
3861
|
+
// Verify run belongs to member's organization
|
|
3862
|
+
const run = await db.query.pipelineRun.findFirst({
|
|
3863
|
+
where: eq(schema.pipelineRun.id, runId),
|
|
3864
|
+
});
|
|
3865
|
+
|
|
3866
|
+
if (!run || run.organizationId !== member.organizationId) {
|
|
3867
|
+
return notFoundResult;
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
// Get all run steps with their blockers (exclude templates - they're just placeholders)
|
|
3871
|
+
const runSteps = await db.query.pipelineRunStep.findMany({
|
|
3872
|
+
where: and(
|
|
3873
|
+
eq(schema.pipelineRunStep.runId, runId),
|
|
3874
|
+
// Filter out template steps - only show normal and instance steps
|
|
3875
|
+
or(
|
|
3876
|
+
eq(schema.pipelineRunStep.spawnType, "normal"),
|
|
3877
|
+
eq(schema.pipelineRunStep.spawnType, "instance"),
|
|
3878
|
+
),
|
|
3879
|
+
),
|
|
3880
|
+
with: {
|
|
3881
|
+
step: true,
|
|
3882
|
+
blockers: {
|
|
3883
|
+
orderBy: [desc(schema.pipelineRunStepBlocker.createdAt)],
|
|
3884
|
+
},
|
|
3885
|
+
},
|
|
3886
|
+
orderBy: [asc(schema.pipelineRunStep.createdAt)],
|
|
3887
|
+
});
|
|
3888
|
+
|
|
3889
|
+
const result = runSteps.map((runStep) => ({
|
|
3890
|
+
runStepId: runStep.id,
|
|
3891
|
+
stepName: runStep.step?.name ?? "Unknown Step",
|
|
3892
|
+
stepOrder: runStep.stepRunIndex ?? 0,
|
|
3893
|
+
// Filter out parent_incomplete blockers - they're not user-actionable
|
|
3894
|
+
// and are automatically resolved when dependencies complete
|
|
3895
|
+
blockers: runStep.blockers
|
|
3896
|
+
.filter((blocker) => blocker.blockerType !== "parent_incomplete")
|
|
3897
|
+
.map((blocker) => ({
|
|
3898
|
+
id: blocker.id,
|
|
3899
|
+
blockerType: blocker.blockerType,
|
|
3900
|
+
blockerDetails: blocker.blockerDetails,
|
|
3901
|
+
resolved: blocker.resolved,
|
|
3902
|
+
resolvedAt: blocker.resolvedAt,
|
|
3903
|
+
createdAt: blocker.createdAt,
|
|
3904
|
+
})),
|
|
3905
|
+
}));
|
|
3906
|
+
|
|
3907
|
+
return okResult(result);
|
|
3908
|
+
} catch (error) {
|
|
3909
|
+
console.error("[getAllBlockersForRun] Error:", error);
|
|
3910
|
+
return internalErrorResult;
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
/**
|
|
3915
|
+
* Get a pipeline by ID with basic fields (for verification)
|
|
3916
|
+
*/
|
|
3917
|
+
export async function getPipelineById({
|
|
3918
|
+
member,
|
|
3919
|
+
pipelineId,
|
|
3920
|
+
}: {
|
|
3921
|
+
member: Member;
|
|
3922
|
+
pipelineId: string;
|
|
3923
|
+
}) {
|
|
3924
|
+
try {
|
|
3925
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
3926
|
+
where: and(
|
|
3927
|
+
eq(schema.pipeline.id, pipelineId),
|
|
3928
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
3929
|
+
isNull(schema.pipeline.deletedAt),
|
|
3930
|
+
),
|
|
3931
|
+
columns: {
|
|
3932
|
+
id: true,
|
|
3933
|
+
name: true,
|
|
3934
|
+
description: true,
|
|
3935
|
+
organizationId: true,
|
|
3936
|
+
},
|
|
3937
|
+
});
|
|
3938
|
+
|
|
3939
|
+
if (!pipeline) {
|
|
3940
|
+
return notFoundResult;
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3943
|
+
return okResult(pipeline);
|
|
3944
|
+
} catch (error) {
|
|
3945
|
+
console.error("[getPipelineById] Error:", error);
|
|
3946
|
+
return internalErrorResult;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3950
|
+
/**
|
|
3951
|
+
* Get a pipeline with its input forms
|
|
3952
|
+
*/
|
|
3953
|
+
export async function getPipelineWithInputForms({
|
|
3954
|
+
member,
|
|
3955
|
+
pipelineId,
|
|
3956
|
+
}: {
|
|
3957
|
+
member: Member;
|
|
3958
|
+
pipelineId: string;
|
|
3959
|
+
}) {
|
|
3960
|
+
try {
|
|
3961
|
+
const pipeline = await db.query.pipeline.findFirst({
|
|
3962
|
+
where: and(
|
|
3963
|
+
eq(schema.pipeline.id, pipelineId),
|
|
3964
|
+
eq(schema.pipeline.organizationId, member.organizationId),
|
|
3965
|
+
isNull(schema.pipeline.deletedAt),
|
|
3966
|
+
),
|
|
3967
|
+
columns: {
|
|
3968
|
+
id: true,
|
|
3969
|
+
name: true,
|
|
3970
|
+
},
|
|
3971
|
+
});
|
|
3972
|
+
|
|
3973
|
+
if (!pipeline) {
|
|
3974
|
+
return notFoundResult;
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
return okResult(pipeline);
|
|
3978
|
+
} catch (error) {
|
|
3979
|
+
console.error("[getPipelineWithInputForms] Error:", error);
|
|
3980
|
+
return internalErrorResult;
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
/**
|
|
3985
|
+
* Create an empty pipeline with basic metadata
|
|
3986
|
+
*/
|
|
3987
|
+
export async function createEmptyPipeline({
|
|
3988
|
+
member,
|
|
3989
|
+
name = "Untitled Pipeline",
|
|
3990
|
+
description = "",
|
|
3991
|
+
}: {
|
|
3992
|
+
member: Member;
|
|
3993
|
+
name?: string;
|
|
3994
|
+
description?: string;
|
|
3995
|
+
}): Promise<
|
|
3996
|
+
DalResult<{
|
|
3997
|
+
id: string;
|
|
3998
|
+
name: string;
|
|
3999
|
+
}>
|
|
4000
|
+
> {
|
|
4001
|
+
try {
|
|
4002
|
+
const [pipeline] = await db
|
|
4003
|
+
.insert(schema.pipeline)
|
|
4004
|
+
.values({
|
|
4005
|
+
organizationId: member.organizationId,
|
|
4006
|
+
name,
|
|
4007
|
+
description,
|
|
4008
|
+
isActive: true,
|
|
4009
|
+
version: "1",
|
|
4010
|
+
metadata: {},
|
|
4011
|
+
})
|
|
4012
|
+
.returning({ id: schema.pipeline.id, name: schema.pipeline.name });
|
|
4013
|
+
invariant(pipeline);
|
|
4014
|
+
|
|
4015
|
+
return okResult(pipeline);
|
|
4016
|
+
} catch (error) {
|
|
4017
|
+
console.error("[createEmptyPipeline] Error:", error);
|
|
4018
|
+
return internalErrorResult;
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
/**
|
|
4023
|
+
* Create a complete pipeline with first step and conversation (transaction)
|
|
4024
|
+
*/
|
|
4025
|
+
export async function createPipelineWithStepAndConversation({
|
|
4026
|
+
member,
|
|
4027
|
+
name,
|
|
4028
|
+
description,
|
|
4029
|
+
stepPrompt,
|
|
4030
|
+
}: {
|
|
4031
|
+
member: Member;
|
|
4032
|
+
name: string;
|
|
4033
|
+
description: string;
|
|
4034
|
+
stepPrompt: string;
|
|
4035
|
+
}) {
|
|
4036
|
+
try {
|
|
4037
|
+
const created = await db.transaction(async (tx) => {
|
|
4038
|
+
const [pipeline] = await tx
|
|
4039
|
+
.insert(schema.pipeline)
|
|
4040
|
+
.values({
|
|
4041
|
+
organizationId: member.organizationId,
|
|
4042
|
+
name,
|
|
4043
|
+
description,
|
|
4044
|
+
metadata: { createdBy: "pipeline_ui" },
|
|
4045
|
+
})
|
|
4046
|
+
.returning();
|
|
4047
|
+
invariant(pipeline);
|
|
4048
|
+
|
|
4049
|
+
// Create conversation for this pipeline
|
|
4050
|
+
const [conversation] = await tx
|
|
4051
|
+
.insert(schema.conversation)
|
|
4052
|
+
.values({
|
|
4053
|
+
organizationId: member.organizationId,
|
|
4054
|
+
agent: true,
|
|
4055
|
+
name: `Pipeline: ${name}`,
|
|
4056
|
+
model: "openai/gpt-5.2",
|
|
4057
|
+
memory: {},
|
|
4058
|
+
})
|
|
4059
|
+
.returning();
|
|
4060
|
+
invariant(conversation);
|
|
4061
|
+
|
|
4062
|
+
// Link conversation to pipeline via conversation_resource
|
|
4063
|
+
await tx.insert(schema.conversationResource).values({
|
|
4064
|
+
conversationId: conversation.id,
|
|
4065
|
+
resourceType: "pipeline",
|
|
4066
|
+
resourceId: pipeline.id,
|
|
4067
|
+
source: "system",
|
|
4068
|
+
});
|
|
4069
|
+
|
|
4070
|
+
// TODO: Create pipeline-form mapping once pipelineForm table is created
|
|
4071
|
+
|
|
4072
|
+
const [step] = await tx
|
|
4073
|
+
.insert(schema.pipelineStep)
|
|
4074
|
+
.values({
|
|
4075
|
+
pipelineId: pipeline.id,
|
|
4076
|
+
name: "Step 1",
|
|
4077
|
+
description,
|
|
4078
|
+
prompt: stepPrompt,
|
|
4079
|
+
toolsAllowed: [],
|
|
4080
|
+
retryPolicy: {},
|
|
4081
|
+
})
|
|
4082
|
+
.returning();
|
|
4083
|
+
invariant(step);
|
|
4084
|
+
|
|
4085
|
+
return pipeline;
|
|
4086
|
+
});
|
|
4087
|
+
|
|
4088
|
+
return okResult({
|
|
4089
|
+
id: created.id,
|
|
4090
|
+
name: created.name,
|
|
4091
|
+
description: created.description,
|
|
4092
|
+
});
|
|
4093
|
+
} catch (error) {
|
|
4094
|
+
console.error("[createPipelineWithStepAndConversation] Error:", error);
|
|
4095
|
+
return internalErrorResult;
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
// *****************************************
|
|
4100
|
+
// Blocker Resolution Functions
|
|
4101
|
+
// *****************************************
|
|
4102
|
+
|
|
4103
|
+
/**
|
|
4104
|
+
* Link documents to a pipeline run step.
|
|
4105
|
+
* Used when resolving missing_upload blockers.
|
|
4106
|
+
*/
|
|
4107
|
+
export async function linkDocumentsToRunStep({
|
|
4108
|
+
member,
|
|
4109
|
+
runStepId,
|
|
4110
|
+
documentIds,
|
|
4111
|
+
}: {
|
|
4112
|
+
member: Member;
|
|
4113
|
+
runStepId: string;
|
|
4114
|
+
documentIds: string[];
|
|
4115
|
+
}): Promise<DalResult<void>> {
|
|
4116
|
+
try {
|
|
4117
|
+
// Verify the run step exists and belongs to the member's org
|
|
4118
|
+
const runStepResult = await getPipelineRunStep({ member, runStepId });
|
|
4119
|
+
if (!runStepResult.ok) return runStepResult;
|
|
4120
|
+
|
|
4121
|
+
// Insert documents into pipelineRunStepDocument
|
|
4122
|
+
if (documentIds.length > 0) {
|
|
4123
|
+
await db.insert(schema.pipelineRunStepDocument).values(
|
|
4124
|
+
documentIds.map((documentId) => ({
|
|
4125
|
+
runStepId,
|
|
4126
|
+
documentId,
|
|
4127
|
+
})),
|
|
4128
|
+
);
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
return okResult(undefined);
|
|
4132
|
+
} catch (error) {
|
|
4133
|
+
console.error("[linkDocumentsToRunStep] Error:", error);
|
|
4134
|
+
return internalErrorResult;
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
/**
|
|
4139
|
+
* Link manual input submissions to a pipeline run step as output submissions.
|
|
4140
|
+
* Used when resolving missing_manual_input blockers.
|
|
4141
|
+
*/
|
|
4142
|
+
export async function linkManualInputsToRunStep({
|
|
4143
|
+
member,
|
|
4144
|
+
runStepId,
|
|
4145
|
+
submissionIds,
|
|
4146
|
+
addedBy,
|
|
4147
|
+
reason,
|
|
4148
|
+
}: {
|
|
4149
|
+
member: Member;
|
|
4150
|
+
runStepId: string;
|
|
4151
|
+
submissionIds: string[];
|
|
4152
|
+
addedBy: string;
|
|
4153
|
+
reason?: string;
|
|
4154
|
+
}): Promise<DalResult<void>> {
|
|
4155
|
+
try {
|
|
4156
|
+
// Verify the run step exists and belongs to the member's org
|
|
4157
|
+
const runStepResult = await getPipelineRunStep({ member, runStepId });
|
|
4158
|
+
if (!runStepResult.ok) return runStepResult;
|
|
4159
|
+
|
|
4160
|
+
// Insert submissions into pipelineRunStepOutputSubmission
|
|
4161
|
+
if (submissionIds.length > 0) {
|
|
4162
|
+
// Check for existing entries to avoid duplicates
|
|
4163
|
+
const existing = await db.query.pipelineRunStepOutputSubmission.findMany({
|
|
4164
|
+
where: and(
|
|
4165
|
+
eq(schema.pipelineRunStepOutputSubmission.runStepId, runStepId),
|
|
4166
|
+
inArray(schema.pipelineRunStepOutputSubmission.submissionId, submissionIds),
|
|
4167
|
+
),
|
|
4168
|
+
});
|
|
4169
|
+
|
|
4170
|
+
const existingSubmissionIds = new Set(existing.map((e) => e.submissionId));
|
|
4171
|
+
const newSubmissionIds = submissionIds.filter((id) => !existingSubmissionIds.has(id));
|
|
4172
|
+
|
|
4173
|
+
if (newSubmissionIds.length > 0) {
|
|
4174
|
+
await db.insert(schema.pipelineRunStepOutputSubmission).values(
|
|
4175
|
+
newSubmissionIds.map((submissionId) => ({
|
|
4176
|
+
runStepId,
|
|
4177
|
+
submissionId,
|
|
4178
|
+
})),
|
|
4179
|
+
);
|
|
4180
|
+
console.log(
|
|
4181
|
+
`[linkManualInputsToRunStep] Added ${newSubmissionIds.length} output submission(s) to step ${runStepId}`,
|
|
4182
|
+
);
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
return okResult(undefined);
|
|
4187
|
+
} catch (error) {
|
|
4188
|
+
console.error("[linkManualInputsToRunStep] Error:", error);
|
|
4189
|
+
return internalErrorResult;
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
/**
|
|
4194
|
+
* Retry a pipeline run step by marking it as pending.
|
|
4195
|
+
* This allows the workflow engine to pick it up again.
|
|
4196
|
+
*/
|
|
4197
|
+
export async function retryRunStep({
|
|
4198
|
+
member,
|
|
4199
|
+
runStepId,
|
|
4200
|
+
}: {
|
|
4201
|
+
member: Member;
|
|
4202
|
+
runStepId: string;
|
|
4203
|
+
}): Promise<DalResult<void>> {
|
|
4204
|
+
try {
|
|
4205
|
+
// Verify the run step exists and belongs to the member's org
|
|
4206
|
+
const runStepResult = await getPipelineRunStep({ member, runStepId });
|
|
4207
|
+
if (!runStepResult.ok) return runStepResult;
|
|
4208
|
+
|
|
4209
|
+
// Update the run step status to pending
|
|
4210
|
+
await db
|
|
4211
|
+
.update(schema.pipelineRunStep)
|
|
4212
|
+
.set({
|
|
4213
|
+
status: PIPELINE_RUN_STEP_STATUS.PENDING,
|
|
4214
|
+
})
|
|
4215
|
+
.where(eq(schema.pipelineRunStep.id, runStepId));
|
|
4216
|
+
|
|
4217
|
+
return okResult(undefined);
|
|
4218
|
+
} catch (error) {
|
|
4219
|
+
console.error("[retryRunStep] Error:", error);
|
|
4220
|
+
return internalErrorResult;
|
|
4221
|
+
}
|
|
4222
|
+
}
|